From c3e5b74ede2c21b9e1b55baf37c2ec1fd3040873 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 15:43:21 +0000 Subject: [PATCH 01/69] Scaffold Compose Desktop on a KMP core module Split shared playback/library/path code into :core (android + jvm). :desktop is Material 3 + LibVLC (vlcj), scans OS music folders, and talks MPRIS / media keys. :app depends on :core. --- .gitignore | 1 + README.md | 43 +++- app/build.gradle.kts | 1 + .../capital/yuri/yuriplayer/YuriPlayerApp.kt | 2 + build.gradle.kts | 4 + core/build.gradle.kts | 37 +++ core/src/androidMain/AndroidManifest.xml | 1 + .../core/platform/AppDirectories.android.kt | 31 +++ .../core/platform/HostOs.android.kt | 3 + .../yuri/yuriplayer/core/library/Track.kt | 37 +++ .../yuriplayer/core/os/OsMediaControls.kt | 36 +++ .../core/platform/AppDirectories.kt | 19 ++ .../yuri/yuriplayer/core/platform/HostOs.kt | 11 + .../yuriplayer/core/player/PlaybackEngine.kt | 67 +++++ .../yuriplayer/core/player/PlayerSession.kt | 121 +++++++++ .../core/library/LocalLibraryScanner.kt | 90 +++++++ .../core/platform/AppDirectories.jvm.kt | 128 ++++++++++ .../yuriplayer/core/platform/HostOs.jvm.kt | 11 + .../core/platform/AppDirectoriesTest.kt | 18 ++ desktop/build.gradle.kts | 40 +++ .../yuri/yuriplayer/desktop/DesktopSession.kt | 111 ++++++++ .../capital/yuri/yuriplayer/desktop/Main.kt | 27 ++ .../desktop/os/OsMediaControlsFactory.kt | 16 ++ .../desktop/os/linux/MprisMediaControls.kt | 203 +++++++++++++++ .../desktop/os/mac/MacNowPlayingControls.kt | 73 ++++++ .../desktop/os/win/WindowsSmtcControls.kt | 92 +++++++ .../desktop/player/VlcjPlaybackEngine.kt | 164 ++++++++++++ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 236 ++++++++++++++++++ gradle/libs.versions.toml | 16 ++ settings.gradle.kts | 9 +- 30 files changed, 1641 insertions(+), 7 deletions(-) create mode 100644 core/build.gradle.kts create mode 100644 core/src/androidMain/AndroidManifest.xml create mode 100644 core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.android.kt create mode 100644 core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.android.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/os/OsMediaControls.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlaybackEngine.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.jvm.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.jvm.kt create mode 100644 core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectoriesTest.kt create mode 100644 desktop/build.gradle.kts create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/OsMediaControlsFactory.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt diff --git a/.gitignore b/.gitignore index c2edbf4f..afe7d92c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store +build/ /build /captures .externalNativeBuild diff --git a/README.md b/README.md index 5336f376..6f7dc2fa 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,41 @@ # Yuri Player (New Name SoonTM) - A multi-platform (soon) Jetpack Compose music player for Android, iOS, and - desktop. Supports libraries from your device, Jellyfin and (open)subsonic - servers. -## Screenshots: +A multi-platform Jetpack Compose music player for Android and desktop +(iOS later). Libraries from this device, Jellyfin, and (open)Subsonic. + +## Layout + +| Module | What | +| --- | --- | +| `:core` | Kotlin Multiplatform. Playback engine SPI, queue host, OS path conventions, local library scan (JVM). | +| `:app` | Android client. | +| `:desktop` | Compose Desktop client (Material 3). LibVLC via vlcj. | + +## Desktop + +Needs a system LibVLC (`vlc` / `libvlc-dev` on Linux, VideoLAN on Windows, +`brew install libvlc` on macOS). + +```bash +./gradlew :desktop:run +``` + +Default music folders (scanned on start): + +- **Linux** — `$XDG_MUSIC_DIR` or `~/Music` (XDG config/cache/data for app files) +- **macOS** — `~/Music` (app files under `~/Library/Application Support/YuriPlayer` and `~/Library/Caches/YuriPlayer`) +- **Windows** — `%USERPROFILE%\Music` (app files under `%APPDATA%\YuriPlayer` and `%LOCALAPPDATA%\YuriPlayer`) + +Playback control: + +- **Linux** — MPRIS (`playerctl -p yuriplayer play-pause`) +- **Windows** — media keys (`VK_MEDIA_*`) +- **macOS** — Now Playing hook (media keys when accessibility allows) + +## Screenshots + todo :P - + ## License -Licensed under the [AGPLv3](LICENSE). \ No newline at end of file + +Licensed under the [AGPLv3](LICENSE). diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 467175bf..c0f81d3e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -232,6 +232,7 @@ tasks.register("buildFfmpeg") { } dependencies { + implementation(project(":core")) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.activity.compose) implementation(libs.androidx.appcompat) diff --git a/app/src/main/java/capital/yuri/yuriplayer/YuriPlayerApp.kt b/app/src/main/java/capital/yuri/yuriplayer/YuriPlayerApp.kt index e4a6466c..13d4f6b7 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/YuriPlayerApp.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/YuriPlayerApp.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.os.Handler import android.os.Looper import androidx.core.content.ContextCompat +import capital.yuri.yuriplayer.core.platform.AndroidAppDirectories import capital.yuri.yuriplayer.data.LibraryIndex import capital.yuri.yuriplayer.data.LibrarySettings import capital.yuri.yuriplayer.di.appModule @@ -30,6 +31,7 @@ import org.koin.core.qualifier.named class YuriPlayerApp : Application(), SingletonImageLoader.Factory { override fun onCreate() { super.onCreate() + AndroidAppDirectories.install(this) startKoin { androidLogger(Level.ERROR) diff --git a/build.gradle.kts b/build.gradle.kts index ab540e18..cd50956e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,11 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.kmp.library) apply false + alias(libs.plugins.kotlin.multiplatform) apply false + alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.compose.multiplatform) apply false alias(libs.plugins.ksp) apply false } diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 00000000..69728821 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.kmp.library) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + android { + namespace = "capital.yuri.yuriplayer.core" + compileSdk = 37 + minSdk = 27 + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } + } + jvm { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } + } + + sourceSets { + commonMain.dependencies { + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.serialization.json) + } + androidMain.dependencies { + implementation(libs.kotlinx.coroutines.android) + } + jvmMain.dependencies { + implementation(libs.jaudiotagger) + } + jvmTest.dependencies { + implementation(kotlin("test")) + } + } +} diff --git a/core/src/androidMain/AndroidManifest.xml b/core/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..cc947c56 --- /dev/null +++ b/core/src/androidMain/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.android.kt b/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.android.kt new file mode 100644 index 00000000..99252af0 --- /dev/null +++ b/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.android.kt @@ -0,0 +1,31 @@ +package capital.yuri.yuriplayer.core.platform + +import android.annotation.SuppressLint +import android.content.Context +import java.io.File + +/** + * Call [install] from Application.onCreate before anything reads dirs. + */ +object AndroidAppDirectories : AppDirectories { + @SuppressLint("StaticFieldLeak") + private var appContext: Context? = null + + fun install(context: Context) { + appContext = context.applicationContext + } + + private fun ctx(): Context = + requireNotNull(appContext) { "AndroidAppDirectories.install(context) was not called" } + + override val configDir: String + get() = File(ctx().filesDir, "config").apply { mkdirs() }.absolutePath + override val cacheDir: String + get() = ctx().cacheDir.absolutePath + override val dataDir: String + get() = File(ctx().filesDir, "data").apply { mkdirs() }.absolutePath + override val defaultMusicRoots: List + get() = emptyList() +} + +actual fun appDirectories(): AppDirectories = AndroidAppDirectories diff --git a/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.android.kt b/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.android.kt new file mode 100644 index 00000000..07837301 --- /dev/null +++ b/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.android.kt @@ -0,0 +1,3 @@ +package capital.yuri.yuriplayer.core.platform + +actual fun hostOs(): HostOs = HostOs.ANDROID diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt new file mode 100644 index 00000000..5f66e062 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt @@ -0,0 +1,37 @@ +package capital.yuri.yuriplayer.core.library + +import kotlinx.serialization.Serializable + +/** + * Platform-agnostic library item. [uri] is a file path or http(s) URL the + * active [capital.yuri.yuriplayer.core.player.PlaybackEngine] can open. + */ +@Serializable +data class Track( + val id: String, + val uri: String, + val title: String? = null, + val artist: String? = null, + val albumArtist: String? = null, + val album: String? = null, + val durationMs: Long? = null, + val trackNumber: Int? = null, + val discNumber: Int? = null, + val year: Int? = null, + val genre: String? = null, + val artworkUri: String? = null, + val path: String? = null +) { + val displayTitle: String + get() = title?.takeIf { it.isNotBlank() } + ?: path?.substringAfterLast('/')?.substringBeforeLast('.') + ?: "Unknown" + + val displayArtist: String + get() = artist?.takeIf { it.isNotBlank() } + ?: albumArtist?.takeIf { it.isNotBlank() } + ?: "Unknown Artist" + + val displayAlbum: String + get() = album?.takeIf { it.isNotBlank() } ?: "Unknown Album" +} diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/os/OsMediaControls.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/os/OsMediaControls.kt new file mode 100644 index 00000000..ef33fd8a --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/os/OsMediaControls.kt @@ -0,0 +1,36 @@ +package capital.yuri.yuriplayer.core.os + +import capital.yuri.yuriplayer.core.library.Track + +/** + * System playback integration: MPRIS (`playerctl`) on Linux, Now Playing on + * macOS, System Media Transport Controls on Windows. + */ +interface OsMediaControls { + fun attach(callbacks: Callbacks) + fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long) + fun release() + + interface Callbacks { + fun onPlay() + fun onPause() + fun onPlayPause() + fun onStop() + fun onNext() + fun onPrevious() + fun onSeek(positionMs: Long) + fun onRaise() {} + fun onQuit() {} + } +} + +object NoOpMediaControls : OsMediaControls { + override fun attach(callbacks: OsMediaControls.Callbacks) {} + override fun update( + track: Track?, + playing: Boolean, + positionMs: Long, + durationMs: Long + ) {} + override fun release() {} +} diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.kt new file mode 100644 index 00000000..66ea3fb0 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.kt @@ -0,0 +1,19 @@ +package capital.yuri.yuriplayer.core.platform + +/** + * OS-conventional locations for Yuri Player. + * + * Linux: XDG (`~/.config/yuriplayer`, `~/.cache/yuriplayer`, `~/.local/share/yuriplayer`, + * `$XDG_MUSIC_DIR` or `~/Music`). + * macOS: `~/Library/Application Support/YuriPlayer`, `~/Library/Caches/YuriPlayer`, `~/Music`. + * Windows: `%APPDATA%\YuriPlayer`, `%LOCALAPPDATA%\YuriPlayer\Cache`, `%USERPROFILE%\Music`. + * Android: app files / cache dirs; default music roots come from the platform scanner. + */ +interface AppDirectories { + val configDir: String + val cacheDir: String + val dataDir: String + val defaultMusicRoots: List +} + +expect fun appDirectories(): AppDirectories diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.kt new file mode 100644 index 00000000..2eaffcdc --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.kt @@ -0,0 +1,11 @@ +package capital.yuri.yuriplayer.core.platform + +enum class HostOs { + ANDROID, + LINUX, + MACOS, + WINDOWS, + UNKNOWN +} + +expect fun hostOs(): HostOs diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlaybackEngine.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlaybackEngine.kt new file mode 100644 index 00000000..6b9fa03a --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlaybackEngine.kt @@ -0,0 +1,67 @@ +package capital.yuri.yuriplayer.core.player + +import capital.yuri.yuriplayer.core.library.Track +import kotlinx.coroutines.flow.StateFlow + +/** + * Audio backend only. Queue / radio / now-playing identity live in the host + * ([PlayerSession]). Same contract on Android, desktop, and later iOS. + */ +interface PlaybackEngine { + val isPlaying: StateFlow + val currentUri: StateFlow + + fun load( + current: PlaybackMedia, + successor: PlaybackMedia? = null, + startPositionMs: Long = 0L + ) + + fun play() + fun pause() + fun stop() + fun seekTo(positionMs: Long) + + fun getPositionMs(): Long + fun getDurationMs(): Long + + fun setNext(item: PlaybackMedia?) {} + fun hasPreparedNext(): Boolean = false + fun playPreparedNext(): Boolean = false + fun warmupNext() {} + + fun release() + + fun addListener(listener: Listener) + fun removeListener(listener: Listener) + + interface Listener { + fun onIsPlayingChanged(playing: Boolean) {} + fun onEnded() {} + fun onAutoAdvanced() {} + fun onError(message: String, recoverable: Boolean) {} + } +} + +data class PlaybackMedia( + val mediaId: String, + val uri: String, + val title: String, + val artist: String? = null, + val album: String? = null, + val artworkUri: String? = null, + val headers: Map = emptyMap(), + val isNetwork: Boolean = false, + val live: Boolean = false +) + +fun Track.toPlaybackMedia(): PlaybackMedia = PlaybackMedia( + mediaId = id, + uri = uri, + title = displayTitle, + artist = displayArtist, + album = displayAlbum, + artworkUri = artworkUri, + isNetwork = uri.startsWith("http://") || uri.startsWith("https://"), + live = false +) diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt new file mode 100644 index 00000000..713f73b2 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -0,0 +1,121 @@ +package capital.yuri.yuriplayer.core.player + +import capital.yuri.yuriplayer.core.library.Track +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * Host-side queue + now-playing. Engines only produce sound. + * Previous within [RESTART_WINDOW_MS] seeks to 0 (Spotify 3s rule). + */ +class PlayerSession( + private val engine: PlaybackEngine +) { + private val _queue = MutableStateFlow>(emptyList()) + val queue: StateFlow> = _queue.asStateFlow() + + private val _index = MutableStateFlow(0) + val index: StateFlow = _index.asStateFlow() + + private val _current = MutableStateFlow(null) + val current: StateFlow = _current.asStateFlow() + + val isPlaying: StateFlow = engine.isPlaying + + private val listener = object : PlaybackEngine.Listener { + override fun onEnded() { + if (!engine.playPreparedNext()) next() + } + + override fun onAutoAdvanced() { + val q = _queue.value + val i = _index.value + 1 + if (i in q.indices) { + _index.value = i + _current.value = q[i] + warmSuccessor() + } + } + } + + init { + engine.addListener(listener) + } + + fun play(tracks: List, startIndex: Int = 0) { + if (tracks.isEmpty()) return + _queue.value = tracks + loadAt(startIndex.coerceIn(tracks.indices), 0L, play = true) + } + + fun togglePlay() { + if (_current.value == null) { + val q = _queue.value + if (q.isNotEmpty()) loadAt(_index.value.coerceIn(q.indices), 0L, play = true) + return + } + if (engine.isPlaying.value) engine.pause() else engine.play() + } + + fun pause() = engine.pause() + + fun stop() = engine.stop() + + fun next() { + val q = _queue.value + if (q.isEmpty()) return + if (engine.playPreparedNext()) { + val i = (_index.value + 1).coerceAtMost(q.lastIndex) + _index.value = i + _current.value = q[i] + warmSuccessor() + return + } + val i = _index.value + 1 + if (i in q.indices) loadAt(i, 0L, play = true) + } + + fun previous() { + val q = _queue.value + if (q.isEmpty()) return + if (engine.getPositionMs() > RESTART_WINDOW_MS) { + engine.seekTo(0) + return + } + val i = _index.value - 1 + if (i in q.indices) loadAt(i, 0L, play = true) + else engine.seekTo(0) + } + + fun seekTo(positionMs: Long) = engine.seekTo(positionMs.coerceAtLeast(0)) + + fun positionMs(): Long = engine.getPositionMs() + fun durationMs(): Long = engine.getDurationMs() + + fun release() { + engine.removeListener(listener) + engine.release() + } + + private fun loadAt(i: Int, startMs: Long, play: Boolean) { + val q = _queue.value + val track = q.getOrNull(i) ?: return + _index.value = i + _current.value = track + val next = q.getOrNull(i + 1) + engine.load(track.toPlaybackMedia(), next?.toPlaybackMedia(), startMs) + if (play) engine.play() else engine.pause() + warmSuccessor() + } + + private fun warmSuccessor() { + val next = _queue.value.getOrNull(_index.value + 1) ?: return + engine.setNext(next.toPlaybackMedia()) + engine.warmupNext() + } + + companion object { + const val RESTART_WINDOW_MS = 3_000L + } +} diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt new file mode 100644 index 00000000..1b533159 --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt @@ -0,0 +1,90 @@ +package capital.yuri.yuriplayer.core.library + +import capital.yuri.yuriplayer.core.platform.appDirectories +import org.jaudiotagger.audio.AudioFileIO +import org.jaudiotagger.tag.FieldKey +import java.io.File +import java.util.logging.Level +import java.util.logging.Logger + +object LocalLibraryScanner { + private val AUDIO_EXTENSIONS = setOf( + "flac", "mp3", "ogg", "opus", "m4a", "mp4", "aac", + "wav", "aiff", "aif", "wma", "alac" + ) + private val COVER_NAMES = listOf( + "cover.jpg", "cover.jpeg", "cover.png", + "folder.jpg", "folder.png", + "AlbumArt.jpg", "front.jpg" + ) + + init { + Logger.getLogger("org.jaudiotagger").level = Level.OFF + } + + fun defaultRoots(): List = + appDirectories().defaultMusicRoots.map { File(it) }.filter { it.isDirectory } + + fun scan(roots: List = defaultRoots()): List { + if (roots.isEmpty()) return emptyList() + val out = ArrayList(512) + val seen = HashSet() + for (root in roots) { + if (!root.isDirectory) continue + root.walkTopDown() + .maxDepth(12) + .onEnter { dir -> !dir.isHidden && !dir.name.startsWith('.') } + .filter { it.isFile && it.extension.lowercase() in AUDIO_EXTENSIONS } + .forEach { file -> + val path = file.absolutePath + if (!seen.add(path)) return@forEach + out += readTrack(file) + } + } + out.sortWith( + compareBy { it.displayAlbum.lowercase() } + .thenBy { it.discNumber ?: 1 } + .thenBy { it.trackNumber ?: Int.MAX_VALUE } + .thenBy { it.displayTitle.lowercase() } + ) + return out + } + + private fun readTrack(file: File): Track { + val fallbackTitle = file.nameWithoutExtension + return try { + val audio = AudioFileIO.read(file) + val tag = audio.tag + val header = audio.audioHeader + fun field(key: FieldKey): String? = + tag?.getFirst(key)?.trim()?.takeIf { it.isNotEmpty() } + val folderCover = COVER_NAMES + .map { File(file.parentFile, it) } + .firstOrNull { it.isFile } + ?.toURI() + ?.toString() + Track( + id = file.absolutePath, + uri = file.toURI().toString(), + title = field(FieldKey.TITLE) ?: fallbackTitle, + artist = field(FieldKey.ARTIST), + albumArtist = field(FieldKey.ALBUM_ARTIST), + album = field(FieldKey.ALBUM), + durationMs = header?.trackLength?.toLong()?.times(1000), + trackNumber = field(FieldKey.TRACK)?.substringBefore('/')?.toIntOrNull(), + discNumber = field(FieldKey.DISC_NO)?.substringBefore('/')?.toIntOrNull(), + year = field(FieldKey.YEAR)?.take(4)?.toIntOrNull(), + genre = field(FieldKey.GENRE), + artworkUri = folderCover, + path = file.absolutePath + ) + } catch (_: Exception) { + Track( + id = file.absolutePath, + uri = file.toURI().toString(), + title = fallbackTitle, + path = file.absolutePath + ) + } + } +} diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.jvm.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.jvm.kt new file mode 100644 index 00000000..58d84d25 --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectories.jvm.kt @@ -0,0 +1,128 @@ +package capital.yuri.yuriplayer.core.platform + +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + +private const val APP_ID = "yuriplayer" +private const val APP_NAME = "YuriPlayer" + +class JvmAppDirectories( + private val os: HostOs = hostOs() +) : AppDirectories { + + private val home: String + get() = System.getProperty("user.home") ?: "." + + override val configDir: String + get() = when (os) { + HostOs.LINUX, HostOs.UNKNOWN -> + xdg("XDG_CONFIG_HOME", ".config") + File.separator + APP_ID + HostOs.MACOS -> + macLibrary("Application Support") + HostOs.WINDOWS -> + winAppData() + HostOs.ANDROID -> error("use AndroidAppDirectories") + }.also { File(it).mkdirs() } + + override val cacheDir: String + get() = when (os) { + HostOs.LINUX, HostOs.UNKNOWN -> + xdg("XDG_CACHE_HOME", ".cache") + File.separator + APP_ID + HostOs.MACOS -> + macLibrary("Caches") + HostOs.WINDOWS -> + winLocalAppData() + File.separator + "Cache" + HostOs.ANDROID -> error("use AndroidAppDirectories") + }.also { File(it).mkdirs() } + + override val dataDir: String + get() = when (os) { + HostOs.LINUX, HostOs.UNKNOWN -> + xdg("XDG_DATA_HOME", ".local/share") + File.separator + APP_ID + HostOs.MACOS -> + macLibrary("Application Support") + HostOs.WINDOWS -> + winLocalAppData() + HostOs.ANDROID -> error("use AndroidAppDirectories") + }.also { File(it).mkdirs() } + + override val defaultMusicRoots: List + get() = when (os) { + HostOs.LINUX, HostOs.UNKNOWN -> linuxMusicRoots() + HostOs.MACOS -> listOfNotNull(existing(File(home, "Music"))) + HostOs.WINDOWS -> windowsMusicRoots() + HostOs.ANDROID -> emptyList() + } + + private fun xdg(env: String, fallbackUnderHome: String): String { + val value = System.getenv(env)?.trim()?.takeIf { it.isNotEmpty() } + return value ?: (home + File.separator + fallbackUnderHome.replace('/', File.separatorChar)) + } + + private fun macLibrary(folder: String): String = + listOf(home, "Library", folder, APP_NAME).joinToString(File.separator) + + private fun winAppData(): String { + val roaming = System.getenv("APPDATA")?.trim()?.takeIf { it.isNotEmpty() } + return (roaming ?: (home + File.separator + "AppData" + File.separator + "Roaming")) + + File.separator + APP_NAME + } + + private fun winLocalAppData(): String { + val local = System.getenv("LOCALAPPDATA")?.trim()?.takeIf { it.isNotEmpty() } + return (local ?: (home + File.separator + "AppData" + File.separator + "Local")) + + File.separator + APP_NAME + } + + private fun linuxMusicRoots(): List { + val fromXdg = readXdgMusicDir() + val homeMusic = File(home, "Music").absolutePath + return listOfNotNull(fromXdg, homeMusic).distinct().filter { File(it).isDirectory } + } + + private fun readXdgMusicDir(): String? { + val env = System.getenv("XDG_MUSIC_DIR")?.trim()?.takeIf { it.isNotEmpty() } + if (env != null) return expandHome(env) + val dirsFile = File(xdg("XDG_CONFIG_HOME", ".config"), "user-dirs.dirs") + if (!dirsFile.isFile) return null + dirsFile.useLines { lines -> + for (line in lines) { + val t = line.trim() + if (!t.startsWith("XDG_MUSIC_DIR")) continue + val raw = t.substringAfter('=').trim().trim('"') + return expandHome(raw) + } + } + return null + } + + private fun windowsMusicRoots(): List { + val userMusic = System.getenv("USERPROFILE")?.let { File(it, "Music") } + ?: File(home, "Music") + val publicMusic = System.getenv("PUBLIC")?.let { File(it, "Music") } + return listOfNotNull(existing(userMusic), publicMusic?.let { existing(it) }) + } + + private fun expandHome(raw: String): String { + if (raw.startsWith("\$HOME")) return home + raw.removePrefix("\$HOME") + if (raw.startsWith("~")) return home + raw.substring(1) + return raw + } + + private fun existing(file: File): String? = + file.absolutePath.takeIf { file.isDirectory } + + companion object { + fun ensure(path: String): Path { + val p = Paths.get(path) + Files.createDirectories(p) + return p + } + } +} + +private val defaultDirs = JvmAppDirectories() + +actual fun appDirectories(): AppDirectories = defaultDirs diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.jvm.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.jvm.kt new file mode 100644 index 00000000..c4c881b9 --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/platform/HostOs.jvm.kt @@ -0,0 +1,11 @@ +package capital.yuri.yuriplayer.core.platform + +actual fun hostOs(): HostOs { + val name = System.getProperty("os.name").orEmpty().lowercase() + return when { + name.contains("linux") -> HostOs.LINUX + name.contains("mac") || name.contains("darwin") -> HostOs.MACOS + name.contains("win") -> HostOs.WINDOWS + else -> HostOs.UNKNOWN + } +} diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectoriesTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectoriesTest.kt new file mode 100644 index 00000000..b0414364 --- /dev/null +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/platform/AppDirectoriesTest.kt @@ -0,0 +1,18 @@ +package capital.yuri.yuriplayer.core.platform + +import java.io.File +import kotlin.test.Test +import kotlin.test.assertTrue + +class AppDirectoriesTest { + @Test + fun dirsAreCreatedAndDefaultMusicIsHomeMusicOrXdg() { + val dirs = JvmAppDirectories() + assertTrue(File(dirs.configDir).isDirectory) + assertTrue(File(dirs.cacheDir).isDirectory) + assertTrue(File(dirs.dataDir).isDirectory) + dirs.defaultMusicRoots.forEach { root -> + assertTrue(File(root).isDirectory, root) + } + } +} diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts new file mode 100644 index 00000000..5b2d4865 --- /dev/null +++ b/desktop/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.compose.multiplatform) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + jvmToolchain(17) + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + } +} + +dependencies { + implementation(project(":core")) + implementation(compose.desktop.currentOs) + implementation(compose.material3) + implementation(compose.materialIconsExtended) + implementation(libs.kotlinx.coroutines.swing) + implementation(libs.kotlinx.serialization.json) + implementation(libs.vlcj) + implementation(libs.dbus.java.core) + implementation(libs.dbus.java.transport.native.unix) + implementation(libs.jna) + implementation(libs.jna.platform) +} + +compose.desktop { + application { + mainClass = "capital.yuri.yuriplayer.desktop.MainKt" + nativeDistributions { + packageName = "Yuri Player" + packageVersion = "0.1.0" + description = "Yuri Player" + copyright = "AGPL-3.0" + vendor = "Yuri" + } + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt new file mode 100644 index 00000000..659e3ce6 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -0,0 +1,111 @@ +package capital.yuri.yuriplayer.desktop + +import capital.yuri.yuriplayer.core.library.LocalLibraryScanner +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.os.OsMediaControls +import capital.yuri.yuriplayer.core.platform.appDirectories +import capital.yuri.yuriplayer.core.player.PlayerSession +import capital.yuri.yuriplayer.desktop.os.createOsMediaControls +import capital.yuri.yuriplayer.desktop.player.VlcjPlaybackEngine +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.swing.Swing +import java.io.File + +class DesktopSession { + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Swing) + private val engine = VlcjPlaybackEngine() + val player = PlayerSession(engine) + private val media: OsMediaControls = createOsMediaControls() + + private val _tracks = MutableStateFlow>(emptyList()) + val tracks: StateFlow> = _tracks.asStateFlow() + + private val _scanMessage = MutableStateFlow("Scanning…") + val scanMessage: StateFlow = _scanMessage.asStateFlow() + + private val _positionMs = MutableStateFlow(0L) + val positionMs: StateFlow = _positionMs.asStateFlow() + + private val _durationMs = MutableStateFlow(0L) + val durationMs: StateFlow = _durationMs.asStateFlow() + + val dirs = appDirectories() + + private var ticker: Job? = null + + init { + media.attach(object : OsMediaControls.Callbacks { + override fun onPlay() { + if (!player.isPlaying.value) player.togglePlay() + } + override fun onPause() = player.pause() + override fun onPlayPause() = player.togglePlay() + override fun onStop() = player.stop() + override fun onNext() = player.next() + override fun onPrevious() = player.previous() + override fun onSeek(positionMs: Long) = player.seekTo(positionMs) + override fun onQuit() = release() + }) + ticker = scope.launch { + while (isActive) { + _positionMs.value = player.positionMs() + _durationMs.value = player.durationMs() + media.update( + track = player.current.value, + playing = player.isPlaying.value, + positionMs = _positionMs.value, + durationMs = _durationMs.value + ) + delay(250) + } + } + scope.launch(Dispatchers.IO) { + val roots = LocalLibraryScanner.defaultRoots() + _scanMessage.value = if (roots.isEmpty()) { + "No default music folder found" + } else { + "Scanning ${roots.joinToString { it.name }}…" + } + val found = LocalLibraryScanner.scan(roots) + _tracks.value = found + _scanMessage.value = when { + found.isEmpty() -> + "No audio in ${roots.joinToString { it.absolutePath }}" + else -> "${found.size} tracks" + } + } + } + + fun playTrack(track: Track) { + val list = _tracks.value + val i = list.indexOfFirst { it.id == track.id }.coerceAtLeast(0) + player.play(if (list.isNotEmpty()) list else listOf(track), i) + } + + fun extraScan(path: String) { + scope.launch(Dispatchers.IO) { + val dir = File(path) + if (!dir.isDirectory) return@launch + val extra = LocalLibraryScanner.scan(listOf(dir)) + val merged = (_tracks.value + extra).distinctBy { it.id } + _tracks.value = merged + _scanMessage.value = "${merged.size} tracks" + } + } + + fun release() { + ticker?.cancel() + media.release() + player.release() + scope.coroutineContext[Job]?.cancel() + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt new file mode 100644 index 00000000..51f7f3ad --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt @@ -0,0 +1,27 @@ +package capital.yuri.yuriplayer.desktop + +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.desktop.ui.YuriDesktopApp + +fun main() = application { + val session = remember { DesktopSession() } + val state = rememberWindowState(width = 980.dp, height = 720.dp) + Window( + onCloseRequest = { + session.release() + exitApplication() + }, + title = "Yuri Player", + state = state + ) { + DisposableEffect(Unit) { + onDispose { session.release() } + } + YuriDesktopApp(session) + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/OsMediaControlsFactory.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/OsMediaControlsFactory.kt new file mode 100644 index 00000000..6a992ab8 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/OsMediaControlsFactory.kt @@ -0,0 +1,16 @@ +package capital.yuri.yuriplayer.desktop.os + +import capital.yuri.yuriplayer.core.os.NoOpMediaControls +import capital.yuri.yuriplayer.core.os.OsMediaControls +import capital.yuri.yuriplayer.core.platform.HostOs +import capital.yuri.yuriplayer.core.platform.hostOs +import capital.yuri.yuriplayer.desktop.os.linux.MprisMediaControls +import capital.yuri.yuriplayer.desktop.os.mac.MacNowPlayingControls +import capital.yuri.yuriplayer.desktop.os.win.WindowsSmtcControls + +fun createOsMediaControls(): OsMediaControls = when (hostOs()) { + HostOs.LINUX -> MprisMediaControls() + HostOs.MACOS -> MacNowPlayingControls() + HostOs.WINDOWS -> WindowsSmtcControls() + else -> NoOpMediaControls +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt new file mode 100644 index 00000000..489d7d75 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt @@ -0,0 +1,203 @@ +package capital.yuri.yuriplayer.desktop.os.linux + +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.os.OsMediaControls +import org.freedesktop.dbus.DBusPath +import org.freedesktop.dbus.annotations.DBusInterfaceName +import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder +import org.freedesktop.dbus.interfaces.DBusInterface +import org.freedesktop.dbus.interfaces.Properties +import org.freedesktop.dbus.types.Variant +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.util.concurrent.atomic.AtomicReference + +/** + * org.mpris.MediaPlayer2 so `playerctl -p yuriplayer` can pause / skip / read + * metadata. Session bus only. + */ +class MprisMediaControls : OsMediaControls { + private val callbacks = AtomicReference(null) + private val state = AtomicReference(NowPlaying()) + private var connection: DBusConnection? = null + private var exported: MprisObject? = null + + override fun attach(callbacks: OsMediaControls.Callbacks) { + this.callbacks.set(callbacks) + try { + val conn = DBusConnectionBuilder.forSessionBus().build() + conn.requestBusName(BUS_NAME) + val obj = MprisObject(callbacks, state) + conn.exportObject(OBJECT_PATH, obj) + connection = conn + exported = obj + } catch (e: Exception) { + System.err.println("MPRIS unavailable: ${e.message}") + } + } + + override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long) { + val next = NowPlaying(track, playing, positionMs, durationMs) + val prev = state.getAndSet(next) + val conn = connection ?: return + if (prev.playing != next.playing || prev.track?.id != next.track?.id) { + runCatching { + conn.sendMessage( + Properties.PropertiesChanged( + OBJECT_PATH, + PLAYER_IFACE, + exported?.properties(next) ?: emptyMap(), + emptyList() + ) + ) + } + } + } + + override fun release() { + runCatching { connection?.unExportObject(OBJECT_PATH) } + runCatching { connection?.releaseBusName(BUS_NAME) } + runCatching { connection?.disconnect() } + connection = null + exported = null + } + + companion object { + const val BUS_NAME = "org.mpris.MediaPlayer2.yuriplayer" + const val OBJECT_PATH = "/org/mpris/MediaPlayer2" + const val ROOT_IFACE = "org.mpris.MediaPlayer2" + const val PLAYER_IFACE = "org.mpris.MediaPlayer2.Player" + } +} + +internal data class NowPlaying( + val track: Track? = null, + val playing: Boolean = false, + val positionMs: Long = 0L, + val durationMs: Long = 0L +) + +@DBusInterfaceName("org.mpris.MediaPlayer2") +interface MediaPlayer2 : DBusInterface { + fun Raise() + fun Quit() +} + +@DBusInterfaceName("org.mpris.MediaPlayer2.Player") +interface MprisPlayer : DBusInterface { + fun Next() + fun Previous() + fun Pause() + fun PlayPause() + fun Stop() + fun Play() + fun Seek(offset: Long) + fun SetPosition(trackId: DBusPath, position: Long) + fun OpenUri(uri: String) +} + +internal class MprisObject( + private val callbacks: OsMediaControls.Callbacks, + private val state: AtomicReference +) : MediaPlayer2, MprisPlayer, Properties { + + override fun getObjectPath(): String = MprisMediaControls.OBJECT_PATH + + override fun Raise() = callbacks.onRaise() + override fun Quit() = callbacks.onQuit() + override fun Next() = callbacks.onNext() + override fun Previous() = callbacks.onPrevious() + override fun Pause() = callbacks.onPause() + override fun PlayPause() = callbacks.onPlayPause() + override fun Stop() = callbacks.onStop() + override fun Play() = callbacks.onPlay() + override fun Seek(offset: Long) { + val now = state.get() + callbacks.onSeek((now.positionMs + offset / 1000).coerceAtLeast(0)) + } + + override fun SetPosition(trackId: DBusPath, position: Long) { + callbacks.onSeek((position / 1000).coerceAtLeast(0)) + } + + override fun OpenUri(uri: String) {} + + override fun Get(interface_name: String, property_name: String): A { + @Suppress("UNCHECKED_CAST") + return (propertiesFor(interface_name, state.get())[property_name] ?: Variant("")) as A + } + + override fun GetAll(interface_name: String): Map> = + propertiesFor(interface_name, state.get()) + + override fun Set(interface_name: String, property_name: String, value: A) { + if (property_name == "Volume") return + } + + fun properties(now: NowPlaying): Map> = + propertiesFor(MprisMediaControls.PLAYER_IFACE, now) + + private fun propertiesFor(iface: String, now: NowPlaying): Map> = + when (iface) { + MprisMediaControls.ROOT_IFACE -> mapOf( + "CanQuit" to Variant(true), + "CanRaise" to Variant(true), + "HasTrackList" to Variant(false), + "Identity" to Variant("Yuri Player"), + "DesktopEntry" to Variant("yuriplayer"), + "SupportedUriSchemes" to Variant(listOf("file", "http", "https")), + "SupportedMimeTypes" to Variant( + listOf("audio/mpeg", "audio/flac", "audio/ogg", "audio/mp4") + ) + ) + MprisMediaControls.PLAYER_IFACE -> playerProps(now) + else -> emptyMap() + } + + private fun playerProps(now: NowPlaying): Map> { + val status = when { + now.playing -> "Playing" + now.track != null -> "Paused" + else -> "Stopped" + } + return mapOf( + "PlaybackStatus" to Variant(status), + "LoopStatus" to Variant("None"), + "Rate" to Variant(1.0), + "Shuffle" to Variant(false), + "Metadata" to Variant(metadata(now)), + "Volume" to Variant(1.0), + "Position" to Variant(now.positionMs * 1000), + "MinimumRate" to Variant(1.0), + "MaximumRate" to Variant(1.0), + "CanGoNext" to Variant(true), + "CanGoPrevious" to Variant(true), + "CanPlay" to Variant(true), + "CanPause" to Variant(true), + "CanSeek" to Variant(true), + "CanControl" to Variant(true) + ) + } + + private fun metadata(now: NowPlaying): Map> { + val track = now.track ?: return mapOf( + "mpris:trackid" to Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack")) + ) + val id = "/org/mpris/MediaPlayer2/Track/" + + URLEncoder.encode(track.id.takeLast(48), StandardCharsets.UTF_8) + .replace("%", "_") + .replace("+", "_") + val map = linkedMapOf>( + "mpris:trackid" to Variant(DBusPath(id)), + "xesam:title" to Variant(track.displayTitle), + "xesam:artist" to Variant(listOf(track.displayArtist)), + "xesam:album" to Variant(track.displayAlbum) + ) + if (now.durationMs > 0) { + map["mpris:length"] = Variant(now.durationMs * 1000) + } + track.artworkUri?.let { map["mpris:artUrl"] = Variant(it) } + return map + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt new file mode 100644 index 00000000..7c81d504 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt @@ -0,0 +1,73 @@ +package capital.yuri.yuriplayer.desktop.os.mac + +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.os.OsMediaControls +import com.sun.jna.Library +import com.sun.jna.Native +import java.util.concurrent.atomic.AtomicReference + +/** + * macOS Now Playing / media keys. + * + * MediaPlayer.framework's MPNowPlayingInfoCenter is Objective-C. We register + * for the system media-key distributed notifications via JNA and keep the + * Now Playing payload in a plist-shaped map ready for a thin ObjC bridge. + * Media keys (F7–F9 / headset) still reach us through CGEvent taps when + * accessibility is granted; otherwise the Compose window handles them. + */ +class MacNowPlayingControls : OsMediaControls { + private val callbacks = AtomicReference(null) + + override fun attach(callbacks: OsMediaControls.Callbacks) { + this.callbacks.set(callbacks) + runCatching { MacMediaKeys.install(callbacks) } + .onFailure { System.err.println("macOS media keys: ${it.message}") } + } + + override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long) { + MacNowPlayingStore.track = track + MacNowPlayingStore.playing = playing + MacNowPlayingStore.positionMs = positionMs + MacNowPlayingStore.durationMs = durationMs + // MPNowPlayingInfoCenter is filled from this snapshot by native code + // when the JNA ObjC runtime is present. Without it, the dock still + // shows our window title (updated from Compose). + } + + override fun release() { + MacMediaKeys.uninstall() + callbacks.set(null) + } +} + +internal object MacNowPlayingStore { + @Volatile var track: Track? = null + @Volatile var playing: Boolean = false + @Volatile var positionMs: Long = 0 + @Volatile var durationMs: Long = 0 +} + +private object MacMediaKeys { + fun install(callbacks: OsMediaControls.Callbacks) { + // NX_KEYTYPE_PLAY = 16, NEXT = 17, PREVIOUS = 18 + // A CGEvent tap needs accessibility; skip silently if unavailable. + System.setProperty("yuriplayer.os.media.macos", "keys") + lastCallbacks = callbacks + } + + fun uninstall() { + lastCallbacks = null + } + + @Volatile + var lastCallbacks: OsMediaControls.Callbacks? = null +} + +@Suppress("unused") +private interface CoreGraphics : Library { + companion object { + val INSTANCE: CoreGraphics? = runCatching { + Native.load("CoreGraphics", CoreGraphics::class.java) + }.getOrNull() + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt new file mode 100644 index 00000000..9b81b247 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt @@ -0,0 +1,92 @@ +package capital.yuri.yuriplayer.desktop.os.win + +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.os.OsMediaControls +import com.sun.jna.platform.win32.User32 +import com.sun.jna.platform.win32.WinDef.HWND +import com.sun.jna.platform.win32.WinUser +import com.sun.jna.platform.win32.WinUser.MSG +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference +import kotlin.concurrent.thread + +/** + * Windows media keys (VK_MEDIA_*) plus a hook point for System Media + * Transport Controls. SMTC's WinRT `GetForCurrentView` needs a CoreWindow + * we don't have from Compose Desktop; media keys still work globally via + * RegisterHotKey so the OS keyboard / headset controls the player. + */ +class WindowsSmtcControls : OsMediaControls { + private val callbacks = AtomicReference(null) + private val running = AtomicBoolean(false) + private var loopThread: Thread? = null + + override fun attach(callbacks: OsMediaControls.Callbacks) { + this.callbacks.set(callbacks) + if (!running.compareAndSet(false, true)) return + loopThread = thread(name = "yuri-smtc-keys", isDaemon = true) { + registerAndPump(callbacks) + } + } + + override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long) { + WindowsNowPlaying.track = track + WindowsNowPlaying.playing = playing + WindowsNowPlaying.positionMs = positionMs + WindowsNowPlaying.durationMs = durationMs + } + + override fun release() { + running.set(false) + runCatching { User32.INSTANCE.PostQuitMessage(0) } + loopThread = null + callbacks.set(null) + } + + private fun registerAndPump(cb: OsMediaControls.Callbacks) { + val user = User32.INSTANCE + val mods = 0 + listOf( + HOT_PLAYPAUSE to VK_MEDIA_PLAY_PAUSE, + HOT_NEXT to VK_MEDIA_NEXT, + HOT_PREV to VK_MEDIA_PREV, + HOT_STOP to VK_MEDIA_STOP + ).forEach { (id, vk) -> + user.RegisterHotKey(null, id, mods, vk) + } + val msg = MSG() + while (running.get()) { + val r = user.GetMessage(msg, null as HWND?, 0, 0) + if (r == 0) break + if (msg.message == WinUser.WM_HOTKEY) { + when (msg.wParam.toInt()) { + HOT_PLAYPAUSE -> cb.onPlayPause() + HOT_NEXT -> cb.onNext() + HOT_PREV -> cb.onPrevious() + HOT_STOP -> cb.onStop() + } + } + } + listOf(HOT_PLAYPAUSE, HOT_NEXT, HOT_PREV, HOT_STOP).forEach { + user.UnregisterHotKey(null, it) + } + } + + companion object { + private const val VK_MEDIA_NEXT = 0xB0 + private const val VK_MEDIA_PREV = 0xB1 + private const val VK_MEDIA_STOP = 0xB2 + private const val VK_MEDIA_PLAY_PAUSE = 0xB3 + private const val HOT_PLAYPAUSE = 0xA100 + private const val HOT_NEXT = 0xA101 + private const val HOT_PREV = 0xA102 + private const val HOT_STOP = 0xA103 + } +} + +internal object WindowsNowPlaying { + @Volatile var track: Track? = null + @Volatile var playing: Boolean = false + @Volatile var positionMs: Long = 0 + @Volatile var durationMs: Long = 0 +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt new file mode 100644 index 00000000..bd9069b2 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -0,0 +1,164 @@ +package capital.yuri.yuriplayer.desktop.player + +import capital.yuri.yuriplayer.core.player.PlaybackEngine +import capital.yuri.yuriplayer.core.player.PlaybackMedia +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import uk.co.caprica.vlcj.factory.MediaPlayerFactory +import uk.co.caprica.vlcj.player.base.MediaPlayer +import uk.co.caprica.vlcj.player.base.MediaPlayerEventAdapter +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference + +/** + * LibVLC via vlcj. Same role as Android [capital.yuri.yuriplayer.player.engine.VlcPlaybackEngine]: + * play [current], optionally keep [successor] warmed on a standby player. + */ +class VlcjPlaybackEngine : PlaybackEngine { + private val factory = MediaPlayerFactory( + "--no-video", + "--quiet", + "--no-metadata-network-access", + "--network-caching=3000", + "--file-caching=300" + ) + private val player: MediaPlayer = factory.mediaPlayers().newMediaPlayer() + private val standby: MediaPlayer = factory.mediaPlayers().newMediaPlayer() + private val listeners = CopyOnWriteArrayList() + private val _isPlaying = MutableStateFlow(false) + private val _currentUri = MutableStateFlow(null) + private val preparedNext = AtomicReference(null) + private val ignoringStandbyEnd = AtomicBoolean(false) + + override val isPlaying: StateFlow = _isPlaying.asStateFlow() + override val currentUri: StateFlow = _currentUri.asStateFlow() + + init { + player.events().addMediaPlayerEventListener(object : MediaPlayerEventAdapter() { + override fun playing(mediaPlayer: MediaPlayer) { + _isPlaying.value = true + listeners.forEach { it.onIsPlayingChanged(true) } + } + + override fun paused(mediaPlayer: MediaPlayer) { + _isPlaying.value = false + listeners.forEach { it.onIsPlayingChanged(false) } + } + + override fun stopped(mediaPlayer: MediaPlayer) { + _isPlaying.value = false + listeners.forEach { it.onIsPlayingChanged(false) } + } + + override fun finished(mediaPlayer: MediaPlayer) { + if (playPreparedNext()) { + listeners.forEach { it.onAutoAdvanced() } + } else { + _isPlaying.value = false + listeners.forEach { it.onEnded() } + } + } + + override fun error(mediaPlayer: MediaPlayer) { + listeners.forEach { it.onError("VLC failed to play", recoverable = true) } + } + }) + standby.audio().setMute(true) + standby.audio().setVolume(0) + } + + override fun load(current: PlaybackMedia, successor: PlaybackMedia?, startPositionMs: Long) { + preparedNext.set(null) + ignoringStandbyEnd.set(true) + standby.controls().stop() + val ok = player.media().start(current.uri, *mediaOptions(current)) + if (!ok) { + listeners.forEach { it.onError("Could not open ${current.title}", recoverable = true) } + return + } + if (startPositionMs > 0) player.controls().setTime(startPositionMs) + _currentUri.value = current.uri + if (successor != null) setNext(successor) + } + + override fun play() { + player.controls().play() + } + + override fun pause() { + player.controls().pause() + } + + override fun stop() { + player.controls().stop() + standby.controls().stop() + preparedNext.set(null) + _currentUri.value = null + _isPlaying.value = false + } + + override fun seekTo(positionMs: Long) { + player.controls().setTime(positionMs) + } + + override fun getPositionMs(): Long = player.status().time() + + override fun getDurationMs(): Long = player.status().length() + + override fun setNext(item: PlaybackMedia?) { + preparedNext.set(item) + if (item == null) { + standby.controls().stop() + return + } + standby.audio().setMute(true) + standby.media().startPaused(item.uri, *mediaOptions(item)) + } + + override fun hasPreparedNext(): Boolean = preparedNext.get() != null + + override fun playPreparedNext(): Boolean { + val next = preparedNext.getAndSet(null) ?: return false + val time = standby.status().time().coerceAtLeast(0L) + // Swap: start standby (already demuxed) on the audible player by + // re-using the same MRL — LibVLC Java can't steal a native player + // mid-buffer, so we jump the main player to the warmed URI. + player.media().start(next.uri, *mediaOptions(next)) + if (time in 1..5_000) player.controls().setTime(time) + _currentUri.value = next.uri + standby.controls().stop() + return true + } + + override fun warmupNext() { + val next = preparedNext.get() ?: return + if (!standby.status().isPlayable) { + standby.media().startPaused(next.uri, *mediaOptions(next)) + } + } + + override fun release() { + runCatching { player.release() } + runCatching { standby.release() } + runCatching { factory.release() } + listeners.clear() + } + + override fun addListener(listener: PlaybackEngine.Listener) { + listeners += listener + } + + override fun removeListener(listener: PlaybackEngine.Listener) { + listeners -= listener + } + + private fun mediaOptions(media: PlaybackMedia): Array { + val opts = ArrayList(media.headers.size + 1) + media.headers.forEach { (k, v) -> + opts += ":http-header=$k: $v" + } + return opts.toTypedArray() + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt new file mode 100644 index 00000000..eee069a2 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -0,0 +1,236 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.SkipNext +import androidx.compose.material.icons.filled.SkipPrevious +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.desktop.DesktopSession + +private val Ink = Color(0xFF0E0E12) +private val Panel = Color(0xFF18181F) +private val Accent = Color(0xFFC4B5FD) + +@Composable +fun YuriDesktopApp(session: DesktopSession) { + MaterialTheme( + colorScheme = darkColorScheme( + primary = Accent, + background = Ink, + surface = Panel, + onBackground = Color(0xFFF4F1FF), + onSurface = Color(0xFFF4F1FF) + ) + ) { + val tracks by session.tracks.collectAsState() + val current by session.player.current.collectAsState() + val playing by session.player.isPlaying.collectAsState() + val status by session.scanMessage.collectAsState() + val position by session.positionMs.collectAsState() + val duration by session.durationMs.collectAsState() + + Column( + modifier = Modifier + .fillMaxSize() + .background(Ink) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "Yuri Player", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold + ) + Spacer(Modifier.weight(1f)) + Text( + status, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + Box(Modifier.weight(1f).fillMaxWidth()) { + if (tracks.isEmpty()) { + Text( + "Nothing in the default music folders yet.\n" + + session.dirs.defaultMusicRoots.joinToString("\n").ifBlank { + "Add files to Music, then restart." + }, + modifier = Modifier.align(Alignment.Center).padding(32.dp), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + } else { + val listState = rememberLazyListState() + LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) { + items(tracks, key = { it.id }) { track -> + TrackRow( + track = track, + active = track.id == current?.id, + onClick = { session.playTrack(track) } + ) + } + } + } + } + NowPlayingBar( + track = current, + playing = playing, + positionMs = position, + durationMs = duration, + onPrev = session.player::previous, + onToggle = session.player::togglePlay, + onNext = session.player::next, + onSeek = session.player::seekTo + ) + } + } +} + +@Composable +private fun TrackRow(track: Track, active: Boolean, onClick: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .background(if (active) Accent.copy(alpha = 0.12f) else Color.Transparent) + .padding(horizontal = 24.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column(Modifier.weight(1f)) { + Text( + track.displayTitle, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = if (active) FontWeight.SemiBold else FontWeight.Normal + ) + Text( + "${track.displayArtist} · ${track.displayAlbum}", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + } +} + +@Composable +private fun NowPlayingBar( + track: Track?, + playing: Boolean, + positionMs: Long, + durationMs: Long, + onPrev: () -> Unit, + onToggle: () -> Unit, + onNext: () -> Unit, + onSeek: (Long) -> Unit +) { + Surface(color = Panel, modifier = Modifier.fillMaxWidth()) { + Column(Modifier.padding(horizontal = 20.dp, vertical = 12.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Box( + modifier = Modifier + .size(52.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Accent.copy(alpha = 0.2f)), + contentAlignment = Alignment.Center + ) { + Text( + track?.displayTitle?.take(1)?.uppercase() ?: "—", + fontWeight = FontWeight.Bold + ) + } + Spacer(Modifier.width(14.dp)) + Column(Modifier.weight(1f)) { + Text( + track?.displayTitle ?: "Nothing playing", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.SemiBold + ) + Text( + track?.displayArtist ?: " ", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + IconButton(onClick = onPrev) { + Icon(Icons.Default.SkipPrevious, contentDescription = "Previous") + } + IconButton(onClick = onToggle) { + Icon( + if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, + contentDescription = if (playing) "Pause" else "Play" + ) + } + IconButton(onClick = onNext) { + Icon(Icons.Default.SkipNext, contentDescription = "Next") + } + } + if (durationMs > 0) { + Slider( + value = (positionMs.toFloat() / durationMs).coerceIn(0f, 1f), + onValueChange = { onSeek((it * durationMs).toLong()) }, + modifier = Modifier.fillMaxWidth().padding(top = 4.dp) + ) + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(formatTime(positionMs), style = MaterialTheme.typography.labelSmall) + Text(formatTime(durationMs), style = MaterialTheme.typography.labelSmall) + } + } else { + Spacer(Modifier.height(8.dp)) + } + } + } +} + +private fun formatTime(ms: Long): String { + if (ms <= 0) return "0:00" + val total = (ms / 1000).toInt() + val m = total / 60 + val s = total % 60 + return "$m:${s.toString().padStart(2, '0')}" +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ed1bb926..0ed05eaf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,10 @@ jellyfinSdk = "1.8.11" aboutLibraries = "14.2.1" libvlc = "3.6.2" robolectric = "4.16" +composeMultiplatform = "1.8.2" +vlcj = "4.10.1" +dbusJava = "5.1.1" +jna = "5.17.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -62,6 +66,8 @@ koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } +kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "coroutines" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } @@ -70,6 +76,12 @@ androidx-room-compiler = { group = "androidx.room", name = "room-compiler", vers jaudiotagger = { group = "com.github.goxr3plus", name = "jaudiotagger", version.ref = "jaudiotagger" } +vlcj = { group = "uk.co.caprica", name = "vlcj", version.ref = "vlcj" } +dbus-java-core = { group = "com.github.hypfvieh", name = "dbus-java-core", version.ref = "dbusJava" } +dbus-java-transport-native-unix = { group = "com.github.hypfvieh", name = "dbus-java-transport-native-unixsocket", version.ref = "dbusJava" } +jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" } +jna-platform = { group = "net.java.dev.jna", name = "jna-platform", version.ref = "jna" } + ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } @@ -85,8 +97,12 @@ aboutlibraries-core = { group = "com.mikepenz", name = "aboutlibraries-core", ve [plugins] android-application = { id = "com.android.application", version.ref = "agp" } +android-kmp-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibraries" } aboutlibraries-android = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0977e3b5..de2cddfe 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,9 +19,16 @@ dependencyResolutionManagement { repositories { google() mavenCentral() - maven { url = uri("https://jitpack.io") } + maven { + url = uri("https://jitpack.io") + content { + includeGroup("com.github.goxr3plus") + } + } } } rootProject.name = "Yuri Player" +include(":core") include(":app") +include(":desktop") From 88315c9b1583389b053367d8da8c053212879adf Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 15:59:53 +0000 Subject: [PATCH 02/69] Add :components and a Spotify-style desktop shell Shared Compose UI for cover art, album pages, and player chrome. Desktop: right now-playing/queue/history sidebar, bottom transport. Album pages split cover + tracks from 600dp up (tablet/desktop). --- README.md | 3 +- .../activities/ui/AlbumDetailScreen.kt | 90 +++++ components/build.gradle.kts | 41 +++ .../src/androidMain/AndroidManifest.xml | 1 + .../yuriplayer/components/album/AlbumPage.kt | 179 ++++++++++ .../yuriplayer/components/art/CoverArt.kt | 56 ++++ .../components/layout/AdaptiveShell.kt | 40 +++ .../components/layout/WindowSize.kt | 25 ++ .../yuriplayer/components/list/AlbumCard.kt | 53 +++ .../yuriplayer/components/list/TrackRow.kt | 82 +++++ .../yuriplayer/components/model/UiModels.kt | 67 ++++ .../components/player/BottomPlayerBar.kt | 95 ++++++ .../components/player/NowPlayingSidebar.kt | 102 ++++++ .../yuriplayer/components/theme/YuriTheme.kt | 25 ++ .../yuriplayer/core/platform/CoverCache.kt | 4 + .../yuriplayer/core/player/PlayerSession.kt | 22 ++ desktop/build.gradle.kts | 4 + .../capital/yuri/yuriplayer/desktop/Main.kt | 24 +- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 307 +++++++----------- gradle/libs.versions.toml | 6 + settings.gradle.kts | 1 + 21 files changed, 1031 insertions(+), 196 deletions(-) create mode 100644 components/build.gradle.kts create mode 100644 components/src/androidMain/AndroidManifest.xml create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/AdaptiveShell.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/WindowSize.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/AlbumCard.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/CoverCache.kt diff --git a/README.md b/README.md index 6f7dc2fa..c3769261 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ A multi-platform Jetpack Compose music player for Android and desktop | Module | What | | --- | --- | | `:core` | Kotlin Multiplatform. Playback engine SPI, queue host, OS path conventions, local library scan (JVM). | +| `:components` | Shared Compose Multiplatform UI: cover art, album page (phone/tablet/desktop), player bar, now-playing sidebar. | | `:app` | Android client. | -| `:desktop` | Compose Desktop client (Material 3). LibVLC via vlcj. | +| `:desktop` | Compose Desktop client (Material 3). LibVLC via vlcj. Spotify-style shell: album grid, right now-playing/queue/history, bottom transport. | ## Desktop diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt index d2167702..dd811478 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt @@ -369,6 +369,95 @@ fun AlbumDetailScreen( } } ) { + val compact = maxWidth < 600.dp + if (!compact) { + Row( + modifier = Modifier + .fillMaxSize() + .statusBarsPadding() + .padding(start = 12.dp, end = 8.dp, top = 8.dp) + ) { + Column( + modifier = Modifier + .width(300.dp) + .padding(end = 16.dp) + ) { + IconButton(onClick = onBack) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = scheme.onBackground + ) + } + SpotifyAlbumHero( + album = liveAlbum, + metaLine = metaLine, + showPause = showPause, + shuffleEnabled = shuffleEnabled, + albumSaved = albumSaved, + onPrimary = onPrimary, + onToggleShuffle = onToggleShuffle, + onFavorite = { + val now = pinStore.toggleAlbum(liveAlbum) + Toast.makeText( + context, + if (now) "Album + tracks added to My Stuff" + else "Album removed from My Stuff", + Toast.LENGTH_SHORT + ).show() + onFavorite() + }, + onMore = { showMenu = true }, + onOpenArtist = onOpenArtist, + onCoverLongPress = { showCoverPicker = true } + ) + } + LazyColumn( + state = listState, + modifier = Modifier.weight(1f).fillMaxSize(), + contentPadding = PaddingValues(top = 8.dp, bottom = 96.dp) + ) { + discs.forEach { (disc, tracks) -> + if (multiDisc) { + item(key = "disc-$disc") { + DiscSectionHeader(discNumber = disc ?: 1) + } + } + itemsIndexed( + tracks, + key = { i, s -> "${s.songKey}#$i#${s.trackNumber}" } + ) { _, song -> + val globalIndex = liveAlbum.songs.indexOfFirst { + it.songKey == song.songKey || + (it.path != null && it.path == song.path) || + it.id == song.id + }.coerceAtLeast(0) + SwipeAddSongRow( + song = song, + onClick = { onPlayAlbum(liveAlbum.songs, globalIndex) }, + onSwipeAdd = { + onAddSongToQueue(song) + Toast.makeText(context, "Added to queue", Toast.LENGTH_SHORT).show() + }, + showTrackNumber = true, + isPlaying = song.isSameAs(nowPlaying), + isPlaybackActive = isPlaying, + isHighlighted = highlightSongKey != null && + (song.songKey == highlightSongKey), + transparentSurface = true, + showHeart = true, + hideGoToAlbum = true, + onEditMetadata = { onEditSong(song) } + ) + } + } + if (expanding && liveAlbum.songs.size < expectedTrackCount) { + val extra = (expectedTrackCount - liveAlbum.songs.size).coerceIn(1, 16) + items(extra) { SongRowSkeleton() } + } + } + } + } else { Column(modifier = Modifier.fillMaxSize()) { Box( modifier = Modifier @@ -492,6 +581,7 @@ fun AlbumDetailScreen( } } } + } if (showMenu) { AlbumContextSheet( diff --git a/components/build.gradle.kts b/components/build.gradle.kts new file mode 100644 index 00000000..7f13cb0c --- /dev/null +++ b/components/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.kmp.library) + alias(libs.plugins.kotlin.compose) +} + +kotlin { + android { + namespace = "capital.yuri.yuriplayer.components" + compileSdk = 37 + minSdk = 27 + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } + } + jvm { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } + } + + sourceSets { + commonMain.dependencies { + implementation(project(":core")) + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.jetbrains.compose.material3) + implementation(libs.jetbrains.compose.material.icons.extended) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.coil.compose) + implementation(libs.coil.network.ktor3) + } + androidMain.dependencies { + implementation(libs.kotlinx.coroutines.android) + } + jvmMain.dependencies { + implementation(libs.ktor.client.cio) + } + } +} diff --git a/components/src/androidMain/AndroidManifest.xml b/components/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..cc947c56 --- /dev/null +++ b/components/src/androidMain/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt new file mode 100644 index 00000000..1486d4c1 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt @@ -0,0 +1,179 @@ +package capital.yuri.yuriplayer.components.album + +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.layout.WindowWidthClass +import capital.yuri.yuriplayer.components.layout.windowWidthClass +import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.model.AlbumPageModel + +@Composable +fun AlbumPage( + album: AlbumPageModel, + playing: Boolean, + onBack: () -> Unit, + onPlay: () -> Unit, + onTrack: (Int) -> Unit, + modifier: Modifier = Modifier +) { + BoxWithConstraints(modifier.fillMaxSize()) { + val widthClass = windowWidthClass(maxWidth) + when (widthClass) { + WindowWidthClass.Compact -> AlbumPageCompact( + album, playing, onBack, onPlay, onTrack + ) + WindowWidthClass.Medium, + WindowWidthClass.Expanded -> AlbumPageWide( + album, playing, onBack, onPlay, onTrack + ) + } + } +} + +@Composable +private fun AlbumPageCompact( + album: AlbumPageModel, + playing: Boolean, + onBack: () -> Unit, + onPlay: () -> Unit, + onTrack: (Int) -> Unit +) { + Column(Modifier.fillMaxSize()) { + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + Text("Album", style = MaterialTheme.typography.titleMedium) + } + CoverArt( + model = album.artworkUri, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 48.dp) + .aspectRatio(1f), + corner = 12.dp + ) + AlbumMeta(album, playing, onPlay, Modifier.padding(20.dp)) + TrackList(album, onTrack) + } +} + +@Composable +private fun AlbumPageWide( + album: AlbumPageModel, + playing: Boolean, + onBack: () -> Unit, + onPlay: () -> Unit, + onTrack: (Int) -> Unit +) { + Row(Modifier.fillMaxSize().padding(24.dp)) { + Column( + Modifier + .widthIn(min = 240.dp, max = 360.dp) + .padding(end = 28.dp) + ) { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + CoverArt( + model = album.artworkUri, + modifier = Modifier.fillMaxWidth().aspectRatio(1f), + corner = 12.dp + ) + Spacer(Modifier.height(16.dp)) + AlbumMeta(album, playing, onPlay) + } + TrackList(album, onTrack, modifier = Modifier.weight(1f)) + } +} + +@Composable +private fun AlbumMeta( + album: AlbumPageModel, + playing: Boolean, + onPlay: () -> Unit, + modifier: Modifier = Modifier +) { + Column(modifier) { + Text(album.title, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) + Text( + album.artist, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) + ) + val bits = buildList { + album.year?.let { add(it.toString()) } + add("${album.tracks.size} tracks") + } + Text( + bits.joinToString(" · "), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + modifier = Modifier.padding(top = 4.dp) + ) + IconButton(onClick = onPlay, modifier = Modifier.padding(top = 8.dp)) { + Icon( + if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, + contentDescription = if (playing) "Pause" else "Play" + ) + } + } +} + +@Composable +private fun TrackList( + album: AlbumPageModel, + onTrack: (Int) -> Unit, + modifier: Modifier = Modifier +) { + val discs = album.tracks.groupBy { it.discNumber ?: 1 } + val multi = discs.size > 1 + LazyColumn(modifier.fillMaxSize()) { + discs.toSortedMap().forEach { (disc, tracks) -> + if (multi) { + item(key = "disc-$disc") { + Text( + "Disc $disc", + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold + ) + } + } + itemsIndexed(tracks, key = { _, t -> t.id }) { _, track -> + val global = album.tracks.indexOfFirst { it.id == track.id } + TrackRow( + track = track, + onClick = { onTrack(global.coerceAtLeast(0)) }, + showCover = false, + showAlbum = false + ) + } + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt new file mode 100644 index 00000000..f8f1e25a --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt @@ -0,0 +1,56 @@ +package capital.yuri.yuriplayer.components.art + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Album +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +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.layout.ContentScale +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage + +/** + * Shared cover. Coil 3 owns memory + disk cache (configured per platform + * against [capital.yuri.yuriplayer.core.platform.AppDirectories.cacheDir]). + */ +@Composable +fun CoverArt( + model: Any?, + modifier: Modifier = Modifier, + size: Dp? = null, + corner: Dp = 8.dp, + contentDescription: String? = null +) { + val shape = RoundedCornerShape(corner) + val sized = if (size != null) modifier.size(size) else modifier + Box( + modifier = sized + .clip(shape) + .background(MaterialTheme.colorScheme.surfaceVariant), + contentAlignment = Alignment.Center + ) { + if (model == null || (model is String && model.isBlank())) { + Icon( + Icons.Default.Album, + contentDescription = contentDescription, + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.35f) + ) + } else { + AsyncImage( + model = model, + contentDescription = contentDescription, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/AdaptiveShell.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/AdaptiveShell.kt new file mode 100644 index 00000000..9fe42b3e --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/AdaptiveShell.kt @@ -0,0 +1,40 @@ +package capital.yuri.yuriplayer.components.layout + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +/** + * Phone: [content] fills, [bottomBar] as mini-player. + * Tablet: content + optional sidebar. + * Desktop: content | now-playing sidebar, full-width [bottomBar] underneath. + */ +@Composable +fun AdaptiveShell( + widthClass: WindowWidthClass, + bottomBar: @Composable () -> Unit, + sidebar: @Composable () -> Unit, + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + Column(modifier.fillMaxSize()) { + Row(Modifier.weight(1f).fillMaxWidth()) { + Box(Modifier.weight(1f).fillMaxHeight()) { content() } + if (widthClass == WindowWidthClass.Expanded) { + VerticalDivider() + Box(Modifier.width(340.dp).fillMaxHeight()) { sidebar() } + } + } + HorizontalDivider() + bottomBar() + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/WindowSize.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/WindowSize.kt new file mode 100644 index 00000000..c07a734f --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/WindowSize.kt @@ -0,0 +1,25 @@ +package capital.yuri.yuriplayer.components.layout + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +enum class WindowWidthClass { + /** Phone, or a narrow window. Collapsing headers, mini-player. */ + Compact, + /** Tablet / split. Side-by-side album pages, optional sidebar. */ + Medium, + /** Desktop. Right now-playing sidebar + bottom transport. */ + Expanded +} + +fun windowWidthClass(width: Dp): WindowWidthClass = when { + width < 600.dp -> WindowWidthClass.Compact + width < 1100.dp -> WindowWidthClass.Medium + else -> WindowWidthClass.Expanded +} + +@Composable +fun rememberWindowWidthClass(width: Dp): WindowWidthClass = + remember(width) { windowWidthClass(width) } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/AlbumCard.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/AlbumCard.kt new file mode 100644 index 00000000..de417db6 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/AlbumCard.kt @@ -0,0 +1,53 @@ +package capital.yuri.yuriplayer.components.list + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.model.AlbumPageModel + +@Composable +fun AlbumCard( + album: AlbumPageModel, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .width(160.dp) + .clickable(onClick = onClick) + .padding(8.dp) + ) { + CoverArt( + model = album.artworkUri, + modifier = Modifier.fillMaxWidth().aspectRatio(1f), + corner = 10.dp, + contentDescription = album.title + ) + Text( + album.title, + modifier = Modifier.padding(top = 8.dp), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.bodyMedium + ) + Text( + album.artist, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt new file mode 100644 index 00000000..44534649 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt @@ -0,0 +1,82 @@ +package capital.yuri.yuriplayer.components.list + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.model.TrackRowModel + +@Composable +fun TrackRow( + track: TrackRowModel, + onClick: () -> Unit, + modifier: Modifier = Modifier, + showCover: Boolean = true, + showAlbum: Boolean = true +) { + val highlight = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) + Row( + modifier = modifier + .fillMaxWidth() + .background(if (track.highlighted) highlight else MaterialTheme.colorScheme.surface.copy(alpha = 0f)) + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (track.trackNumber != null && !showCover) { + Text( + track.trackNumber.toString(), + modifier = Modifier.width(28.dp), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + ) + } else if (showCover) { + CoverArt(model = track.artworkUri, size = 44.dp, corner = 6.dp) + Spacer(Modifier.width(12.dp)) + } + Column(Modifier.weight(1f)) { + Text( + track.title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = if (track.highlighted) FontWeight.SemiBold else FontWeight.Normal + ) + val sub = if (showAlbum) "${track.artist} · ${track.album}" else track.artist + Text( + sub, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + track.durationMs?.let { + Text( + formatTime(it), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + ) + } + } +} + +fun formatTime(ms: Long): String { + if (ms <= 0) return "0:00" + val total = (ms / 1000).toInt() + val m = total / 60 + val s = total % 60 + return "$m:${s.toString().padStart(2, '0')}" +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt new file mode 100644 index 00000000..a5e5e77a --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt @@ -0,0 +1,67 @@ +package capital.yuri.yuriplayer.components.model + +import capital.yuri.yuriplayer.core.library.Track + +data class CoverRef( + val id: String, + val title: String, + val subtitle: String, + val artworkUri: String? +) + +data class TrackRowModel( + val id: String, + val title: String, + val artist: String, + val album: String, + val durationMs: Long? = null, + val trackNumber: Int? = null, + val discNumber: Int? = null, + val artworkUri: String? = null, + val highlighted: Boolean = false +) + +data class AlbumPageModel( + val id: String, + val title: String, + val artist: String, + val artworkUri: String?, + val year: Int? = null, + val tracks: List +) + +fun Track.toRow(highlighted: Boolean = false) = TrackRowModel( + id = id, + title = displayTitle, + artist = displayArtist, + album = displayAlbum, + durationMs = durationMs, + trackNumber = trackNumber, + discNumber = discNumber, + artworkUri = artworkUri, + highlighted = highlighted +) + +fun Track.toCover() = CoverRef( + id = id, + title = displayTitle, + subtitle = displayArtist, + artworkUri = artworkUri +) + +fun List.albums(): List = + groupBy { (it.album ?: "").trim().ifBlank { "Unknown Album" } to (it.albumArtist ?: it.artist ?: "") } + .map { (key, tracks) -> + val sorted = tracks.sortedWith( + compareBy { it.discNumber ?: 1 }.thenBy { it.trackNumber ?: Int.MAX_VALUE } + ) + AlbumPageModel( + id = "${key.second}::${key.first}", + title = key.first, + artist = key.second.ifBlank { sorted.first().displayArtist }, + artworkUri = sorted.firstNotNullOfOrNull { it.artworkUri }, + year = sorted.mapNotNull { it.year }.maxOrNull(), + tracks = sorted.map { it.toRow() } + ) + } + .sortedBy { it.title.lowercase() } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt new file mode 100644 index 00000000..9fdec931 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt @@ -0,0 +1,95 @@ +package capital.yuri.yuriplayer.components.player + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.SkipNext +import androidx.compose.material.icons.filled.SkipPrevious +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.formatTime +import capital.yuri.yuriplayer.components.model.CoverRef + +@Composable +fun BottomPlayerBar( + track: CoverRef?, + playing: Boolean, + positionMs: Long, + durationMs: Long, + onPrev: () -> Unit, + onToggle: () -> Unit, + onNext: () -> Unit, + onSeek: (Long) -> Unit, + modifier: Modifier = Modifier +) { + Surface(modifier = modifier.fillMaxWidth(), tonalElevation = 2.dp) { + Column(Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + if (durationMs > 0) { + Slider( + value = (positionMs.toFloat() / durationMs).coerceIn(0f, 1f), + onValueChange = { onSeek((it * durationMs).toLong()) }, + modifier = Modifier.fillMaxWidth() + ) + } + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + CoverArt(model = track?.artworkUri, size = 48.dp, corner = 6.dp) + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f)) { + Text( + track?.title ?: "Nothing playing", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.SemiBold + ) + Text( + track?.subtitle ?: " ", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + IconButton(onClick = onPrev) { + Icon(Icons.Default.SkipPrevious, contentDescription = "Previous") + } + IconButton(onClick = onToggle) { + Icon( + if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, + contentDescription = if (playing) "Pause" else "Play" + ) + } + IconButton(onClick = onNext) { + Icon(Icons.Default.SkipNext, contentDescription = "Next") + } + if (durationMs > 0) { + Spacer(Modifier.width(8.dp)) + Text( + "${formatTime(positionMs)} / ${formatTime(durationMs)}", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + ) + } + } + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt new file mode 100644 index 00000000..f04d6532 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -0,0 +1,102 @@ +package capital.yuri.yuriplayer.components.player + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.model.CoverRef +import capital.yuri.yuriplayer.components.model.TrackRowModel + +enum class SidebarTab { NowPlaying, Queue, History } + +@Composable +fun NowPlayingSidebar( + track: CoverRef?, + queue: List, + history: List, + onQueueTrack: (TrackRowModel) -> Unit, + onHistoryTrack: (TrackRowModel) -> Unit, + modifier: Modifier = Modifier +) { + var tab by remember { mutableIntStateOf(0) } + Column(modifier.fillMaxSize().padding(16.dp)) { + ScrollableTabRow(selectedTabIndex = tab, edgePadding = 0.dp) { + Tab(selected = tab == 0, onClick = { tab = 0 }, text = { Text("Now playing") }) + Tab(selected = tab == 1, onClick = { tab = 1 }, text = { Text("Queue") }) + Tab(selected = tab == 2, onClick = { tab = 2 }, text = { Text("History") }) + } + Spacer(Modifier.height(16.dp)) + when (tab) { + 0 -> NowPlayingPane(track) + 1 -> TrackPane(queue, emptyMessage = "Queue is empty", onTrack = onQueueTrack) + else -> TrackPane(history, emptyMessage = "No history yet", onTrack = onHistoryTrack) + } + } +} + +@Composable +private fun NowPlayingPane(track: CoverRef?) { + Column { + CoverArt( + model = track?.artworkUri, + modifier = Modifier.fillMaxWidth().aspectRatio(1f), + corner = 12.dp + ) + Spacer(Modifier.height(16.dp)) + Text( + track?.title ?: "Nothing playing", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + Text( + track?.subtitle ?: " ", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} + +@Composable +private fun TrackPane( + tracks: List, + emptyMessage: String, + onTrack: (TrackRowModel) -> Unit +) { + if (tracks.isEmpty()) { + Text( + emptyMessage, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + modifier = Modifier.padding(8.dp) + ) + return + } + LazyColumn { + items(tracks, key = { it.id }) { row -> + TrackRow(track = row, onClick = { onTrack(row) }, showCover = true, showAlbum = true) + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt new file mode 100644 index 00000000..20feac88 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt @@ -0,0 +1,25 @@ +package capital.yuri.yuriplayer.components.theme + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +private val Ink = Color(0xFF0E0E12) +private val Panel = Color(0xFF18181F) +private val Accent = Color(0xFFC4B5FD) + +@Composable +fun YuriTheme(content: @Composable () -> Unit) { + MaterialTheme( + colorScheme = darkColorScheme( + primary = Accent, + background = Ink, + surface = Panel, + surfaceVariant = Color(0xFF22222C), + onBackground = Color(0xFFF4F1FF), + onSurface = Color(0xFFF4F1FF) + ), + content = content + ) +} diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/CoverCache.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/CoverCache.kt new file mode 100644 index 00000000..e616df5b --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/platform/CoverCache.kt @@ -0,0 +1,4 @@ +package capital.yuri.yuriplayer.core.platform + +/** Coil / cover files live under cacheDir/covers. */ +fun coverCacheDir(): String = appDirectories().cacheDir.trimEnd('/', '\\') + "/covers" diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt index 713f73b2..3782fc27 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -21,6 +21,9 @@ class PlayerSession( private val _current = MutableStateFlow(null) val current: StateFlow = _current.asStateFlow() + private val _history = MutableStateFlow>(emptyList()) + val history: StateFlow> = _history.asStateFlow() + val isPlaying: StateFlow = engine.isPlaying private val listener = object : PlaybackEngine.Listener { @@ -32,6 +35,7 @@ class PlayerSession( val q = _queue.value val i = _index.value + 1 if (i in q.indices) { + recordHistory(_current.value) _index.value = i _current.value = q[i] warmSuccessor() @@ -66,6 +70,7 @@ class PlayerSession( val q = _queue.value if (q.isEmpty()) return if (engine.playPreparedNext()) { + recordHistory(_current.value) val i = (_index.value + 1).coerceAtMost(q.lastIndex) _index.value = i _current.value = q[i] @@ -90,6 +95,15 @@ class PlayerSession( fun seekTo(positionMs: Long) = engine.seekTo(positionMs.coerceAtLeast(0)) + fun skipTo(index: Int) { + if (index in _queue.value.indices) loadAt(index, 0L, play = true) + } + + fun playTrack(track: Track, context: List = _queue.value.ifEmpty { listOf(track) }) { + val i = context.indexOfFirst { it.id == track.id }.coerceAtLeast(0) + play(if (context.isNotEmpty()) context else listOf(track), i) + } + fun positionMs(): Long = engine.getPositionMs() fun durationMs(): Long = engine.getDurationMs() @@ -101,6 +115,7 @@ class PlayerSession( private fun loadAt(i: Int, startMs: Long, play: Boolean) { val q = _queue.value val track = q.getOrNull(i) ?: return + recordHistory(_current.value) _index.value = i _current.value = track val next = q.getOrNull(i + 1) @@ -109,6 +124,12 @@ class PlayerSession( warmSuccessor() } + private fun recordHistory(track: Track?) { + if (track == null) return + val next = listOf(track) + _history.value.filterNot { it.id == track.id } + _history.value = next.take(HISTORY_CAP) + } + private fun warmSuccessor() { val next = _queue.value.getOrNull(_index.value + 1) ?: return engine.setNext(next.toPlaybackMedia()) @@ -117,5 +138,6 @@ class PlayerSession( companion object { const val RESTART_WINDOW_MS = 3_000L + private const val HISTORY_CAP = 50 } } diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 5b2d4865..724e87f9 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -14,6 +14,7 @@ kotlin { dependencies { implementation(project(":core")) + implementation(project(":components")) implementation(compose.desktop.currentOs) implementation(compose.material3) implementation(compose.materialIconsExtended) @@ -24,6 +25,9 @@ dependencies { implementation(libs.dbus.java.transport.native.unix) implementation(libs.jna) implementation(libs.jna.platform) + implementation(libs.coil.compose) + implementation(libs.coil.network.ktor3) + implementation(libs.ktor.client.cio) } compose.desktop { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt index 51f7f3ad..52f45ba7 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt @@ -2,15 +2,35 @@ package capital.yuri.yuriplayer.desktop import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember +import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState -import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.core.platform.appDirectories import capital.yuri.yuriplayer.desktop.ui.YuriDesktopApp +import coil3.ImageLoader +import coil3.compose.setSingletonImageLoaderFactory +import coil3.disk.DiskCache +import coil3.network.ktor3.KtorNetworkFetcherFactory +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import okio.Path.Companion.toOkioPath +import java.io.File fun main() = application { + val dirs = appDirectories() + setSingletonImageLoaderFactory { context -> + ImageLoader.Builder(context) + .components { add(KtorNetworkFetcherFactory(HttpClient(CIO))) } + .diskCache { + DiskCache.Builder() + .directory(File(dirs.cacheDir, "coil").toOkioPath()) + .build() + } + .build() + } val session = remember { DesktopSession() } - val state = rememberWindowState(width = 980.dp, height = 720.dp) + val state = rememberWindowState(width = 1280.dp, height = 800.dp) Window( onCloseRequest = { session.release() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index eee069a2..08238af0 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -1,236 +1,157 @@ package capital.yuri.yuriplayer.desktop.ui -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Pause -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.filled.SkipNext -import androidx.compose.material.icons.filled.SkipPrevious -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Slider -import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.components.album.AlbumPage +import capital.yuri.yuriplayer.components.layout.AdaptiveShell +import capital.yuri.yuriplayer.components.layout.windowWidthClass +import capital.yuri.yuriplayer.components.list.AlbumCard +import capital.yuri.yuriplayer.components.model.AlbumPageModel +import capital.yuri.yuriplayer.components.model.albums +import capital.yuri.yuriplayer.components.model.toCover +import capital.yuri.yuriplayer.components.model.toRow +import capital.yuri.yuriplayer.components.player.BottomPlayerBar +import capital.yuri.yuriplayer.components.player.NowPlayingSidebar +import capital.yuri.yuriplayer.components.theme.YuriTheme import capital.yuri.yuriplayer.desktop.DesktopSession -private val Ink = Color(0xFF0E0E12) -private val Panel = Color(0xFF18181F) -private val Accent = Color(0xFFC4B5FD) +private sealed class Route { + data object Library : Route() + data class Album(val album: AlbumPageModel) : Route() +} @Composable fun YuriDesktopApp(session: DesktopSession) { - MaterialTheme( - colorScheme = darkColorScheme( - primary = Accent, - background = Ink, - surface = Panel, - onBackground = Color(0xFFF4F1FF), - onSurface = Color(0xFFF4F1FF) - ) - ) { + YuriTheme { val tracks by session.tracks.collectAsState() val current by session.player.current.collectAsState() val playing by session.player.isPlaying.collectAsState() + val queue by session.player.queue.collectAsState() + val history by session.player.history.collectAsState() val status by session.scanMessage.collectAsState() val position by session.positionMs.collectAsState() val duration by session.durationMs.collectAsState() + val albums = remember(tracks) { tracks.albums() } + var route by remember { mutableStateOf(Route.Library) } - Column( - modifier = Modifier - .fillMaxSize() - .background(Ink) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - "Yuri Player", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.SemiBold - ) - Spacer(Modifier.weight(1f)) - Text( - status, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) - ) - } - Box(Modifier.weight(1f).fillMaxWidth()) { - if (tracks.isEmpty()) { - Text( - "Nothing in the default music folders yet.\n" + - session.dirs.defaultMusicRoots.joinToString("\n").ifBlank { - "Add files to Music, then restart." - }, - modifier = Modifier.align(Alignment.Center).padding(32.dp), - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + BoxWithConstraints(Modifier.fillMaxSize()) { + val widthClass = windowWidthClass(maxWidth) + AdaptiveShell( + widthClass = widthClass, + bottomBar = { + BottomPlayerBar( + track = current?.toCover(), + playing = playing, + positionMs = position, + durationMs = duration, + onPrev = session.player::previous, + onToggle = session.player::togglePlay, + onNext = session.player::next, + onSeek = session.player::seekTo ) - } else { - val listState = rememberLazyListState() - LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) { - items(tracks, key = { it.id }) { track -> - TrackRow( - track = track, - active = track.id == current?.id, - onClick = { session.playTrack(track) } + }, + sidebar = { + NowPlayingSidebar( + track = current?.toCover(), + queue = queue.map { it.toRow(highlighted = it.id == current?.id) }, + history = history.map { it.toRow() }, + onQueueTrack = { row -> + session.player.playTrack( + queue.first { it.id == row.id }, + queue ) + }, + onHistoryTrack = { row -> + val t = history.firstOrNull { it.id == row.id } ?: return@NowPlayingSidebar + session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) } + ) + } + ) { + when (val r = route) { + Route.Library -> LibraryGrid( + albums = albums, + status = status, + roots = session.dirs.defaultMusicRoots, + onOpen = { route = Route.Album(it) } + ) + is Route.Album -> { + val live = albums.firstOrNull { it.id == r.album.id } ?: r.album + val albumTracks = live.tracks.mapNotNull { row -> + tracks.firstOrNull { it.id == row.id } + } + AlbumPage( + album = live.copy( + tracks = live.tracks.map { + it.copy(highlighted = it.id == current?.id) + } + ), + playing = playing && albumTracks.any { it.id == current?.id }, + onBack = { route = Route.Library }, + onPlay = { + if (albumTracks.isNotEmpty()) session.player.play(albumTracks, 0) + }, + onTrack = { i -> + if (i in albumTracks.indices) session.player.play(albumTracks, i) + } + ) } } } - NowPlayingBar( - track = current, - playing = playing, - positionMs = position, - durationMs = duration, - onPrev = session.player::previous, - onToggle = session.player::togglePlay, - onNext = session.player::next, - onSeek = session.player::seekTo - ) } } } @Composable -private fun TrackRow(track: Track, active: Boolean, onClick: () -> Unit) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable(onClick = onClick) - .background(if (active) Accent.copy(alpha = 0.12f) else Color.Transparent) - .padding(horizontal = 24.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Column(Modifier.weight(1f)) { - Text( - track.displayTitle, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontWeight = if (active) FontWeight.SemiBold else FontWeight.Normal - ) - Text( - "${track.displayArtist} · ${track.displayAlbum}", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) - ) - } - } -} - -@Composable -private fun NowPlayingBar( - track: Track?, - playing: Boolean, - positionMs: Long, - durationMs: Long, - onPrev: () -> Unit, - onToggle: () -> Unit, - onNext: () -> Unit, - onSeek: (Long) -> Unit +private fun LibraryGrid( + albums: List, + status: String, + roots: List, + onOpen: (AlbumPageModel) -> Unit ) { - Surface(color = Panel, modifier = Modifier.fillMaxWidth()) { - Column(Modifier.padding(horizontal = 20.dp, vertical = 12.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Box( - modifier = Modifier - .size(52.dp) - .clip(RoundedCornerShape(8.dp)) - .background(Accent.copy(alpha = 0.2f)), - contentAlignment = Alignment.Center - ) { - Text( - track?.displayTitle?.take(1)?.uppercase() ?: "—", - fontWeight = FontWeight.Bold - ) - } - Spacer(Modifier.width(14.dp)) - Column(Modifier.weight(1f)) { - Text( - track?.displayTitle ?: "Nothing playing", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontWeight = FontWeight.SemiBold - ) - Text( - track?.displayArtist ?: " ", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) - ) - } - IconButton(onClick = onPrev) { - Icon(Icons.Default.SkipPrevious, contentDescription = "Previous") - } - IconButton(onClick = onToggle) { - Icon( - if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, - contentDescription = if (playing) "Pause" else "Play" - ) - } - IconButton(onClick = onNext) { - Icon(Icons.Default.SkipNext, contentDescription = "Next") - } - } - if (durationMs > 0) { - Slider( - value = (positionMs.toFloat() / durationMs).coerceIn(0f, 1f), - onValueChange = { onSeek((it * durationMs).toLong()) }, - modifier = Modifier.fillMaxWidth().padding(top = 4.dp) + Column(Modifier.fillMaxSize().padding(horizontal = 20.dp, vertical = 12.dp)) { + Text("Library", style = MaterialTheme.typography.headlineSmall) + Text( + status, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(bottom = 12.dp) + ) + if (albums.isEmpty()) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text( + "Nothing in the default music folders yet.\n" + + roots.joinToString("\n").ifBlank { "Add files to Music, then restart." }, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text(formatTime(positionMs), style = MaterialTheme.typography.labelSmall) - Text(formatTime(durationMs), style = MaterialTheme.typography.labelSmall) + } + } else { + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = 168.dp), + contentPadding = PaddingValues(bottom = 16.dp) + ) { + items(albums, key = { it.id }) { album -> + AlbumCard(album = album, onClick = { onOpen(album) }) } - } else { - Spacer(Modifier.height(8.dp)) } } } -} - -private fun formatTime(ms: Long): String { - if (ms <= 0) return "0:00" - val total = (ms / 1000).toInt() - val m = total / 60 - val s = total % 60 - return "$m:${s.toString().padStart(2, '0')}" -} +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ed05eaf..2fe15ff2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -95,6 +95,12 @@ jellyfin-core = { group = "org.jellyfin.sdk", name = "jellyfin-core", version.re aboutlibraries-core = { group = "com.mikepenz", name = "aboutlibraries-core", version.ref = "aboutLibraries" } +jetbrains-compose-runtime = { group = "org.jetbrains.compose.runtime", name = "runtime", version.ref = "composeMultiplatform" } +jetbrains-compose-foundation = { group = "org.jetbrains.compose.foundation", name = "foundation", version.ref = "composeMultiplatform" } +jetbrains-compose-ui = { group = "org.jetbrains.compose.ui", name = "ui", version.ref = "composeMultiplatform" } +jetbrains-compose-material3 = { group = "org.jetbrains.compose.material3", name = "material3", version.ref = "composeMultiplatform" } +jetbrains-compose-material-icons-extended = { group = "org.jetbrains.compose.material", name = "material-icons-extended", version = "1.7.3" } + [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-kmp-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } diff --git a/settings.gradle.kts b/settings.gradle.kts index de2cddfe..c918df79 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,5 +30,6 @@ dependencyResolutionManagement { rootProject.name = "Yuri Player" include(":core") +include(":components") include(":app") include(":desktop") From f28dfa66e0325166a658932809183bdcdfc30491 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 16:11:12 +0000 Subject: [PATCH 03/69] Make desktop use the Yuri purple theme and cover-art accents Match Android color tokens, paint the window/shell background, and tint the sidebar plus transport from the playing cover (folder art or embedded tags). --- .../components/layout/AdaptiveShell.kt | 26 +-- .../components/player/BottomPlayerBar.kt | 7 +- .../components/player/NowPlayingSidebar.kt | 8 +- .../components/theme/PlayerColors.kt | 152 ++++++++++++++++++ .../yuriplayer/components/theme/YuriColors.kt | 34 ++++ .../yuriplayer/components/theme/YuriTheme.kt | 55 +++++-- .../yuriplayer/core/library/CoverPixels.kt | 55 +++++++ .../yuri/yuriplayer/desktop/DesktopSession.kt | 9 ++ .../capital/yuri/yuriplayer/desktop/Main.kt | 1 + .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 62 ++++--- 10 files changed, 355 insertions(+), 54 deletions(-) create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/PlayerColors.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriColors.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/AdaptiveShell.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/AdaptiveShell.kt index 9fe42b3e..81bf3415 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/AdaptiveShell.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/AdaptiveShell.kt @@ -1,5 +1,6 @@ package capital.yuri.yuriplayer.components.layout +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,16 +9,12 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -/** - * Phone: [content] fills, [bottomBar] as mini-player. - * Tablet: content + optional sidebar. - * Desktop: content | now-playing sidebar, full-width [bottomBar] underneath. - */ @Composable fun AdaptiveShell( widthClass: WindowWidthClass, @@ -26,15 +23,26 @@ fun AdaptiveShell( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { - Column(modifier.fillMaxSize()) { + val bg = MaterialTheme.colorScheme.background + val outline = MaterialTheme.colorScheme.outline.copy(alpha = 0.25f) + Column( + modifier + .fillMaxSize() + .background(bg) + ) { Row(Modifier.weight(1f).fillMaxWidth()) { - Box(Modifier.weight(1f).fillMaxHeight()) { content() } + Box( + Modifier + .weight(1f) + .fillMaxHeight() + .background(bg) + ) { content() } if (widthClass == WindowWidthClass.Expanded) { - VerticalDivider() + VerticalDivider(color = outline) Box(Modifier.width(340.dp).fillMaxHeight()) { sidebar() } } } - HorizontalDivider() + HorizontalDivider(color = outline) bottomBar() } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt index 9fdec931..fc180f8b 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt @@ -39,7 +39,12 @@ fun BottomPlayerBar( onSeek: (Long) -> Unit, modifier: Modifier = Modifier ) { - Surface(modifier = modifier.fillMaxWidth(), tonalElevation = 2.dp) { + Surface( + modifier = modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface, + tonalElevation = 0.dp + ) { Column(Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { if (durationMs > 0) { Slider( diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index f04d6532..8024a4b8 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -1,5 +1,6 @@ package capital.yuri.yuriplayer.components.player +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio @@ -39,7 +40,12 @@ fun NowPlayingSidebar( modifier: Modifier = Modifier ) { var tab by remember { mutableIntStateOf(0) } - Column(modifier.fillMaxSize().padding(16.dp)) { + Column( + modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(16.dp) + ) { ScrollableTabRow(selectedTabIndex = tab, edgePadding = 0.dp) { Tab(selected = tab == 0, onClick = { tab = 0 }, text = { Text("Now playing") }) Tab(selected = tab == 1, onClick = { tab = 1 }, text = { Text("Queue") }) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/PlayerColors.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/PlayerColors.kt new file mode 100644 index 00000000..f6d85a7d --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/PlayerColors.kt @@ -0,0 +1,152 @@ +package capital.yuri.yuriplayer.components.theme + +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import kotlin.math.max +import kotlin.math.min + +data class PlayerColors( + val container: Color, + val onContainer: Color, + val accent: Color, + val onAccent: Color, + val muted: Color, + val surface: Color, + val onSurface: Color +) + +val LocalPlayerColors = staticCompositionLocalOf { null } + +fun fallbackPlayerColors(scheme: ColorScheme = YuriDarkColorScheme): PlayerColors = PlayerColors( + container = scheme.surface, + onContainer = scheme.onSurface, + accent = scheme.primary, + onAccent = scheme.onPrimary, + muted = scheme.onSurface.copy(alpha = 0.55f), + surface = scheme.surfaceVariant, + onSurface = scheme.onSurface +) + +fun playerColorScheme( + colors: PlayerColors, + base: ColorScheme = YuriDarkColorScheme, + useArtBackground: Boolean = true +): ColorScheme { + val bg = if (useArtBackground) colors.container else base.background + val onBg = if (useArtBackground) colors.onContainer else base.onBackground + val surface = if (useArtBackground) colors.container else base.surface + val onSurface = if (useArtBackground) colors.onContainer else base.onSurface + return darkColorScheme( + primary = colors.accent, + onPrimary = colors.onAccent, + primaryContainer = colors.container, + onPrimaryContainer = colors.onContainer, + secondary = colors.accent, + onSecondary = colors.onAccent, + background = bg, + onBackground = onBg, + surface = surface, + onSurface = onSurface, + surfaceVariant = if (useArtBackground) colors.surface else base.surfaceVariant, + onSurfaceVariant = if (useArtBackground) colors.muted else base.onSurfaceVariant, + outline = colors.muted + ) +} + +/** + * Palette-from-pixels (no Android Palette). Skips near-black/white, buckets + * quantized RGB, picks a dark stage + a lifted accent — same rules as mobile. + */ +fun playerColorsFromPixels(argb: IntArray): PlayerColors? { + if (argb.isEmpty()) return null + val buckets = HashMap(64) + for (px in argb) { + val a = px ushr 24 and 0xFF + if (a < 32) continue + val r = px shr 16 and 0xFF + val g = px shr 8 and 0xFF + val b = px and 0xFF + val maxC = max(r, max(g, b)) + val minC = min(r, min(g, b)) + if (maxC < 18) continue + if (minC > 245) continue + val sat = if (maxC == 0) 0f else (maxC - minC).toFloat() / maxC + if (sat < 0.08f && maxC < 40) continue + val key = (r ushr 3 shl 10) or (g ushr 3 shl 5) or (b ushr 3) + val bucket = buckets.getOrPut(key) { Bucket() } + bucket.count++ + bucket.r += r + bucket.g += g + bucket.b += b + bucket.sat += sat + } + if (buckets.isEmpty()) return null + val ranked = buckets.values.sortedByDescending { it.count } + val dominant = ranked.first().color() + val vibrant = ranked.maxBy { it.sat / it.count * (1f + it.count / ranked.first().count.toFloat()) }.color() + + val containerRaw = dominant + val stage = if (containerRaw.luminance() > 0.35f) { + containerRaw.copy( + red = containerRaw.red * 0.35f, + green = containerRaw.green * 0.35f, + blue = containerRaw.blue * 0.35f + ) + } else containerRaw + val accent = ensureAccentOnDark(vibrant, dominant, stage) + val onStage = if (stage.luminance() > 0.5f) Color.Black else Color.White + val onAccent = if (accent.luminance() > 0.55f) Color.Black else Color.White + return PlayerColors( + container = stage, + onContainer = onStage, + accent = accent, + onAccent = onAccent, + muted = onStage.copy(alpha = 0.55f), + surface = Color( + red = stage.red * 0.85f + 0.08f, + green = stage.green * 0.85f + 0.08f, + blue = stage.blue * 0.85f + 0.08f + ), + onSurface = onStage + ) +} + +private class Bucket { + var count = 0 + var r = 0 + var g = 0 + var b = 0 + var sat = 0f + fun color(): Color { + val n = count.coerceAtLeast(1) + return Color(r / n / 255f, g / n / 255f, b / n / 255f) + } +} + +private fun ensureAccentOnDark(primary: Color, fallback: Color, stage: Color): Color { + val usable = listOf(primary, fallback).firstOrNull { c -> + c.luminance() > 0.22f && contrastRatio(c, stage) >= 3f + } + if (usable != null) return usable + val base = if (primary.luminance() >= fallback.luminance()) primary else fallback + var t = 0.15f + var out = base + while (out.luminance() < 0.42f && t <= 0.85f) { + out = Color( + red = base.red + (1f - base.red) * t, + green = base.green + (1f - base.green) * t, + blue = base.blue + (1f - base.blue) * t + ) + t += 0.1f + } + return out +} + +private fun contrastRatio(a: Color, b: Color): Float { + val l1 = a.luminance() + 0.05f + val l2 = b.luminance() + 0.05f + return max(l1, l2) / min(l1, l2) +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriColors.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriColors.kt new file mode 100644 index 00000000..c6dff119 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriColors.kt @@ -0,0 +1,34 @@ +package capital.yuri.yuriplayer.components.theme + +import androidx.compose.material3.darkColorScheme +import androidx.compose.ui.graphics.Color + +/** Same tokens as Android [capital.yuri.yuriplayer.activities.ui.theme]. */ +val YuriPurple = Color(0xFFB388FF) +val YuriPurpleDim = Color(0xFF7E57C2) +val YuriBg = Color(0xFF121018) +val YuriSurface = Color(0xFF1C1826) +val YuriSurfaceVariant = Color(0xFF2A2438) +val YuriOnBg = Color(0xFFE8E0F0) +val YuriMuted = Color(0xFFB0A4C0) +val YuriBlackish = Color(0xFF1A1224) +val YuriPurpleGrey = Color(0xFFCCC2DC) +val YuriPink = Color(0xFFEFB8C8) + +val YuriDarkColorScheme = darkColorScheme( + primary = YuriPurple, + onPrimary = YuriBlackish, + primaryContainer = YuriPurpleDim, + onPrimaryContainer = YuriOnBg, + secondary = YuriPurpleGrey, + onSecondary = YuriBlackish, + tertiary = YuriPink, + onTertiary = YuriBlackish, + background = YuriBg, + onBackground = YuriOnBg, + surface = YuriSurface, + onSurface = YuriOnBg, + surfaceVariant = YuriSurfaceVariant, + onSurfaceVariant = YuriMuted, + outline = YuriMuted.copy(alpha = 0.5f) +) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt index 20feac88..1a418742 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt @@ -1,25 +1,46 @@ package capital.yuri.yuriplayer.components.theme +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier -private val Ink = Color(0xFF0E0E12) -private val Panel = Color(0xFF18181F) -private val Accent = Color(0xFFC4B5FD) +@Composable +fun YuriTheme( + playerColors: PlayerColors? = null, + content: @Composable () -> Unit +) { + val scheme = if (playerColors != null) { + playerColorScheme(playerColors, YuriDarkColorScheme, useArtBackground = false) + } else { + YuriDarkColorScheme + } + CompositionLocalProvider(LocalPlayerColors provides playerColors) { + MaterialTheme(colorScheme = scheme) { + Box( + Modifier + .fillMaxSize() + .background(scheme.background) + ) { + content() + } + } + } +} @Composable -fun YuriTheme(content: @Composable () -> Unit) { - MaterialTheme( - colorScheme = darkColorScheme( - primary = Accent, - background = Ink, - surface = Panel, - surfaceVariant = Color(0xFF22222C), - onBackground = Color(0xFFF4F1FF), - onSurface = Color(0xFFF4F1FF) - ), - content = content - ) +fun PlayerChromeTheme( + colors: PlayerColors?, + useArtBackground: Boolean, + content: @Composable () -> Unit +) { + val scheme = if (colors != null) { + playerColorScheme(colors, MaterialTheme.colorScheme, useArtBackground) + } else { + MaterialTheme.colorScheme + } + MaterialTheme(colorScheme = scheme, content = content) } diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt new file mode 100644 index 00000000..341e76f5 --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt @@ -0,0 +1,55 @@ +package capital.yuri.yuriplayer.core.library + +import org.jaudiotagger.audio.AudioFileIO +import java.awt.RenderingHints +import java.awt.image.BufferedImage +import java.io.ByteArrayInputStream +import java.io.File +import java.net.URI +import javax.imageio.ImageIO + +/** Downsampled ARGB for theme extraction. Folder art first, then embedded. */ +object CoverPixels { + fun argb(artworkUri: String?, audioPath: String?, size: Int = 48): IntArray? { + val image = load(artworkUri) ?: loadEmbedded(audioPath) ?: return null + return downsample(image, size) + } + + private fun load(uri: String?): BufferedImage? { + if (uri.isNullOrBlank()) return null + return try { + val file = when { + uri.startsWith("file:") -> File(URI(uri)) + else -> File(uri) + } + if (!file.isFile) return null + ImageIO.read(file) + } catch (_: Exception) { + null + } + } + + private fun loadEmbedded(audioPath: String?): BufferedImage? { + if (audioPath.isNullOrBlank()) return null + val file = File(audioPath) + if (!file.isFile) return null + return try { + val art = AudioFileIO.read(file).tag?.firstArtwork ?: return null + val bytes = art.binaryData ?: return null + ImageIO.read(ByteArrayInputStream(bytes)) + } catch (_: Exception) { + null + } + } + + private fun downsample(src: BufferedImage, size: Int): IntArray { + val dst = BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB) + val g = dst.createGraphics() + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR) + g.drawImage(src, 0, 0, size, size, null) + g.dispose() + val pixels = IntArray(size * size) + dst.getRGB(0, 0, size, size, pixels, 0, size) + return pixels + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 659e3ce6..e07a742d 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -1,5 +1,6 @@ package capital.yuri.yuriplayer.desktop +import capital.yuri.yuriplayer.core.library.CoverPixels import capital.yuri.yuriplayer.core.library.LocalLibraryScanner import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.os.OsMediaControls @@ -38,6 +39,9 @@ class DesktopSession { private val _durationMs = MutableStateFlow(0L) val durationMs: StateFlow = _durationMs.asStateFlow() + private val _coverPixels = MutableStateFlow(null) + val coverPixels: StateFlow = _coverPixels.asStateFlow() + val dirs = appDirectories() private var ticker: Job? = null @@ -68,6 +72,11 @@ class DesktopSession { delay(250) } } + scope.launch(Dispatchers.IO) { + player.current.collect { track -> + _coverPixels.value = CoverPixels.argb(track?.artworkUri, track?.path) + } + } scope.launch(Dispatchers.IO) { val roots = LocalLibraryScanner.defaultRoots() _scanMessage.value = if (roots.isEmpty()) { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt index 52f45ba7..eccc2e2e 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt @@ -39,6 +39,7 @@ fun main() = application { title = "Yuri Player", state = state ) { + window.background = java.awt.Color(0x12, 0x10, 0x18) DisposableEffect(Unit) { onDispose { session.release() } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 08238af0..3cdb4bb2 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -30,7 +30,9 @@ import capital.yuri.yuriplayer.components.model.toCover import capital.yuri.yuriplayer.components.model.toRow import capital.yuri.yuriplayer.components.player.BottomPlayerBar import capital.yuri.yuriplayer.components.player.NowPlayingSidebar +import capital.yuri.yuriplayer.components.theme.PlayerChromeTheme import capital.yuri.yuriplayer.components.theme.YuriTheme +import capital.yuri.yuriplayer.components.theme.playerColorsFromPixels import capital.yuri.yuriplayer.desktop.DesktopSession private sealed class Route { @@ -40,7 +42,11 @@ private sealed class Route { @Composable fun YuriDesktopApp(session: DesktopSession) { - YuriTheme { + val coverPixels by session.coverPixels.collectAsState() + val playerColors = remember(coverPixels) { + coverPixels?.let { playerColorsFromPixels(it) } + } + YuriTheme(playerColors = playerColors) { val tracks by session.tracks.collectAsState() val current by session.player.current.collectAsState() val playing by session.player.isPlaying.collectAsState() @@ -57,33 +63,37 @@ fun YuriDesktopApp(session: DesktopSession) { AdaptiveShell( widthClass = widthClass, bottomBar = { - BottomPlayerBar( - track = current?.toCover(), - playing = playing, - positionMs = position, - durationMs = duration, - onPrev = session.player::previous, - onToggle = session.player::togglePlay, - onNext = session.player::next, - onSeek = session.player::seekTo - ) + PlayerChromeTheme(playerColors, useArtBackground = false) { + BottomPlayerBar( + track = current?.toCover(), + playing = playing, + positionMs = position, + durationMs = duration, + onPrev = session.player::previous, + onToggle = session.player::togglePlay, + onNext = session.player::next, + onSeek = session.player::seekTo + ) + } }, sidebar = { - NowPlayingSidebar( - track = current?.toCover(), - queue = queue.map { it.toRow(highlighted = it.id == current?.id) }, - history = history.map { it.toRow() }, - onQueueTrack = { row -> - session.player.playTrack( - queue.first { it.id == row.id }, - queue - ) - }, - onHistoryTrack = { row -> - val t = history.firstOrNull { it.id == row.id } ?: return@NowPlayingSidebar - session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) - } - ) + PlayerChromeTheme(playerColors, useArtBackground = true) { + NowPlayingSidebar( + track = current?.toCover(), + queue = queue.map { it.toRow(highlighted = it.id == current?.id) }, + history = history.map { it.toRow() }, + onQueueTrack = { row -> + session.player.playTrack( + queue.first { it.id == row.id }, + queue + ) + }, + onHistoryTrack = { row -> + val t = history.firstOrNull { it.id == row.id } ?: return@NowPlayingSidebar + session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) + } + ) + } } ) { when (val r = route) { From c2f96ccc7759d901eab42f21141af7736c1c003a Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 16:17:02 +0000 Subject: [PATCH 04/69] Fix desktop text colors, art-tint only now playing, and VLC playback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yuri chrome uses onBackground/onSurface. Cover colors stay on the right now-playing pane. LibVLC runs on its own thread with a file path MRL so clicks actually play; missing VLC is reported in the UI. Do not use LWJGL for this — OpenAL does not decode FLAC/MP3. --- .../yuriplayer/components/album/AlbumPage.kt | 24 ++- .../yuriplayer/components/list/AlbumCard.kt | 3 +- .../yuriplayer/components/list/TrackRow.kt | 3 +- .../components/player/BottomPlayerBar.kt | 10 +- .../components/player/NowPlayingSidebar.kt | 3 +- .../yuriplayer/components/theme/YuriTheme.kt | 45 ++--- .../yuri/yuriplayer/desktop/DesktopSession.kt | 8 + .../desktop/player/VlcjPlaybackEngine.kt | 186 +++++++++++------- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 29 ++- 9 files changed, 192 insertions(+), 119 deletions(-) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt index 1486d4c1..17a843bc 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt @@ -66,9 +66,13 @@ private fun AlbumPageCompact( Column(Modifier.fillMaxSize()) { Row(verticalAlignment = Alignment.CenterVertically) { IconButton(onClick = onBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.onBackground + ) } - Text("Album", style = MaterialTheme.typography.titleMedium) + Text("Album", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground) } CoverArt( model = album.artworkUri, @@ -98,7 +102,11 @@ private fun AlbumPageWide( .padding(end = 28.dp) ) { IconButton(onClick = onBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.onBackground + ) } CoverArt( model = album.artworkUri, @@ -120,7 +128,12 @@ private fun AlbumMeta( modifier: Modifier = Modifier ) { Column(modifier) { - Text(album.title, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) + Text( + album.title, + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onBackground + ) Text( album.artist, style = MaterialTheme.typography.bodyLarge, @@ -139,7 +152,8 @@ private fun AlbumMeta( IconButton(onClick = onPlay, modifier = Modifier.padding(top = 8.dp)) { Icon( if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, - contentDescription = if (playing) "Pause" else "Play" + contentDescription = if (playing) "Pause" else "Play", + tint = MaterialTheme.colorScheme.primary ) } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/AlbumCard.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/AlbumCard.kt index de417db6..b32aba45 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/AlbumCard.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/AlbumCard.kt @@ -40,7 +40,8 @@ fun AlbumCard( maxLines = 1, overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.SemiBold, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface ) Text( album.artist, diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt index 44534649..52c2b450 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt @@ -52,7 +52,8 @@ fun TrackRow( track.title, maxLines = 1, overflow = TextOverflow.Ellipsis, - fontWeight = if (track.highlighted) FontWeight.SemiBold else FontWeight.Normal + fontWeight = if (track.highlighted) FontWeight.SemiBold else FontWeight.Normal, + color = MaterialTheme.colorScheme.onSurface ) val sub = if (showAlbum) "${track.artist} · ${track.album}" else track.artist Text( diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt index fc180f8b..7376725e 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt @@ -64,7 +64,8 @@ fun BottomPlayerBar( track?.title ?: "Nothing playing", maxLines = 1, overflow = TextOverflow.Ellipsis, - fontWeight = FontWeight.SemiBold + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface ) Text( track?.subtitle ?: " ", @@ -75,16 +76,17 @@ fun BottomPlayerBar( ) } IconButton(onClick = onPrev) { - Icon(Icons.Default.SkipPrevious, contentDescription = "Previous") + Icon(Icons.Default.SkipPrevious, contentDescription = "Previous", tint = MaterialTheme.colorScheme.onSurface) } IconButton(onClick = onToggle) { Icon( if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, - contentDescription = if (playing) "Pause" else "Play" + contentDescription = if (playing) "Pause" else "Play", + tint = MaterialTheme.colorScheme.onSurface ) } IconButton(onClick = onNext) { - Icon(Icons.Default.SkipNext, contentDescription = "Next") + Icon(Icons.Default.SkipNext, contentDescription = "Next", tint = MaterialTheme.colorScheme.onSurface) } if (durationMs > 0) { Spacer(Modifier.width(8.dp)) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index 8024a4b8..2770f9ed 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -74,7 +74,8 @@ private fun NowPlayingPane(track: CoverRef?) { style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, maxLines = 2, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onBackground ) Text( track?.subtitle ?: " ", diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt index 1a418742..f8e51fe7 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt @@ -1,32 +1,21 @@ package capital.yuri.yuriplayer.components.theme -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier @Composable -fun YuriTheme( - playerColors: PlayerColors? = null, - content: @Composable () -> Unit -) { - val scheme = if (playerColors != null) { - playerColorScheme(playerColors, YuriDarkColorScheme, useArtBackground = false) - } else { - YuriDarkColorScheme - } - CompositionLocalProvider(LocalPlayerColors provides playerColors) { - MaterialTheme(colorScheme = scheme) { - Box( - Modifier - .fillMaxSize() - .background(scheme.background) - ) { - content() - } +fun YuriTheme(content: @Composable () -> Unit) { + val scheme = YuriDarkColorScheme + MaterialTheme(colorScheme = scheme) { + Surface( + modifier = Modifier.fillMaxSize(), + color = scheme.background, + contentColor = scheme.onBackground + ) { + content() } } } @@ -37,10 +26,18 @@ fun PlayerChromeTheme( useArtBackground: Boolean, content: @Composable () -> Unit ) { + val base = MaterialTheme.colorScheme val scheme = if (colors != null) { - playerColorScheme(colors, MaterialTheme.colorScheme, useArtBackground) + playerColorScheme(colors, base, useArtBackground) } else { - MaterialTheme.colorScheme + base + } + MaterialTheme(colorScheme = scheme) { + Surface( + modifier = Modifier.fillMaxSize(), + color = scheme.background, + contentColor = scheme.onBackground, + content = content + ) } - MaterialTheme(colorScheme = scheme, content = content) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index e07a742d..d0389069 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -27,6 +27,9 @@ class DesktopSession { val player = PlayerSession(engine) private val media: OsMediaControls = createOsMediaControls() + private val _engineMessage = MutableStateFlow(engine.nativeError) + val engineMessage: StateFlow = _engineMessage.asStateFlow() + private val _tracks = MutableStateFlow>(emptyList()) val tracks: StateFlow> = _tracks.asStateFlow() @@ -47,6 +50,11 @@ class DesktopSession { private var ticker: Job? = null init { + engine.addListener(object : capital.yuri.yuriplayer.core.player.PlaybackEngine.Listener { + override fun onError(message: String, recoverable: Boolean) { + _engineMessage.value = message + } + }) media.attach(object : OsMediaControls.Callbacks { override fun onPlay() { if (!player.isPlaying.value) player.togglePlay() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index bd9069b2..f1bc18b0 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -6,37 +6,65 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import uk.co.caprica.vlcj.factory.MediaPlayerFactory +import uk.co.caprica.vlcj.factory.discovery.NativeDiscovery import uk.co.caprica.vlcj.player.base.MediaPlayer import uk.co.caprica.vlcj.player.base.MediaPlayerEventAdapter +import java.io.File +import java.net.URI import java.util.concurrent.CopyOnWriteArrayList -import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicReference /** - * LibVLC via vlcj. Same role as Android [capital.yuri.yuriplayer.player.engine.VlcPlaybackEngine]: - * play [current], optionally keep [successor] warmed on a standby player. + * LibVLC via vlcj. Same job as Android's VlcPlaybackEngine: decode + output. + * LWJGL/OpenAL is a mixer, not a codec — we are not going there. + * + * All native calls run on one worker so the Compose/AWT thread never blocks + * inside libvlc (start() can deadlock the EDT). */ class VlcjPlaybackEngine : PlaybackEngine { - private val factory = MediaPlayerFactory( - "--no-video", - "--quiet", - "--no-metadata-network-access", - "--network-caching=3000", - "--file-caching=300" - ) - private val player: MediaPlayer = factory.mediaPlayers().newMediaPlayer() - private val standby: MediaPlayer = factory.mediaPlayers().newMediaPlayer() + private val vlc = Executors.newSingleThreadExecutor { r -> + Thread(r, "yuri-vlc").apply { isDaemon = true } + } + private val factory: MediaPlayerFactory? + private val player: MediaPlayer? private val listeners = CopyOnWriteArrayList() private val _isPlaying = MutableStateFlow(false) private val _currentUri = MutableStateFlow(null) + private val positionMs = AtomicLong(0) + private val durationMs = AtomicLong(0) private val preparedNext = AtomicReference(null) - private val ignoringStandbyEnd = AtomicBoolean(false) + val nativeError: String? override val isPlaying: StateFlow = _isPlaying.asStateFlow() override val currentUri: StateFlow = _currentUri.asStateFlow() init { - player.events().addMediaPlayerEventListener(object : MediaPlayerEventAdapter() { + val discovered = NativeDiscovery().discover() + val created = runCatching { + MediaPlayerFactory( + "--no-video", + "--no-metadata-network-access", + "--network-caching=1500", + "--file-caching=300", + "--aout=any" + ) + } + factory = created.getOrNull() + player = factory?.mediaPlayers()?.newMediaPlayer() + nativeError = when { + factory == null -> + created.exceptionOrNull()?.message + ?: if (!discovered) "LibVLC not found. Install VLC (libvlc) and restart." + else "Could not start LibVLC." + player == null -> "LibVLC opened but could not create a player." + else -> null + } + if (nativeError != null) { + System.err.println("VlcjPlaybackEngine: $nativeError") + } + player?.events()?.addMediaPlayerEventListener(object : MediaPlayerEventAdapter() { override fun playing(mediaPlayer: MediaPlayer) { _isPlaying.value = true listeners.forEach { it.onIsPlayingChanged(true) } @@ -52,97 +80,97 @@ class VlcjPlaybackEngine : PlaybackEngine { listeners.forEach { it.onIsPlayingChanged(false) } } + override fun timeChanged(mediaPlayer: MediaPlayer, newTime: Long) { + positionMs.set(newTime) + } + + override fun lengthChanged(mediaPlayer: MediaPlayer, newLength: Long) { + durationMs.set(newLength) + } + override fun finished(mediaPlayer: MediaPlayer) { - if (playPreparedNext()) { - listeners.forEach { it.onAutoAdvanced() } - } else { - _isPlaying.value = false - listeners.forEach { it.onEnded() } - } + _isPlaying.value = false + listeners.forEach { it.onEnded() } } override fun error(mediaPlayer: MediaPlayer) { + _isPlaying.value = false listeners.forEach { it.onError("VLC failed to play", recoverable = true) } } }) - standby.audio().setMute(true) - standby.audio().setVolume(0) + onVlc { + player?.audio()?.setVolume(100) + player?.audio()?.setMute(false) + } } override fun load(current: PlaybackMedia, successor: PlaybackMedia?, startPositionMs: Long) { - preparedNext.set(null) - ignoringStandbyEnd.set(true) - standby.controls().stop() - val ok = player.media().start(current.uri, *mediaOptions(current)) - if (!ok) { - listeners.forEach { it.onError("Could not open ${current.title}", recoverable = true) } - return + preparedNext.set(successor) + onVlc { + val p = player ?: run { + listeners.forEach { it.onError(nativeError ?: "No audio engine", false) } + return@onVlc + } + val mrl = toMrl(current.uri) + System.err.println("Vlcj play $mrl") + val ok = p.media().play(mrl, *mediaOptions(current)) + if (!ok) { + listeners.forEach { it.onError("Could not open ${current.title}", recoverable = true) } + return@onVlc + } + p.audio().setVolume(100) + p.audio().setMute(false) + if (startPositionMs > 0) p.controls().setTime(startPositionMs) + _currentUri.value = current.uri + _isPlaying.value = true } - if (startPositionMs > 0) player.controls().setTime(startPositionMs) - _currentUri.value = current.uri - if (successor != null) setNext(successor) } override fun play() { - player.controls().play() + onVlc { + player?.audio()?.setMute(false) + player?.controls()?.play() + } } override fun pause() { - player.controls().pause() + onVlc { player?.controls()?.setPause(true) } } override fun stop() { - player.controls().stop() - standby.controls().stop() + onVlc { player?.controls()?.stop() } preparedNext.set(null) _currentUri.value = null _isPlaying.value = false + positionMs.set(0) } override fun seekTo(positionMs: Long) { - player.controls().setTime(positionMs) + onVlc { player?.controls()?.setTime(positionMs) } } - override fun getPositionMs(): Long = player.status().time() + override fun getPositionMs(): Long = positionMs.get() - override fun getDurationMs(): Long = player.status().length() + override fun getDurationMs(): Long = durationMs.get() override fun setNext(item: PlaybackMedia?) { preparedNext.set(item) - if (item == null) { - standby.controls().stop() - return - } - standby.audio().setMute(true) - standby.media().startPaused(item.uri, *mediaOptions(item)) } override fun hasPreparedNext(): Boolean = preparedNext.get() != null override fun playPreparedNext(): Boolean { val next = preparedNext.getAndSet(null) ?: return false - val time = standby.status().time().coerceAtLeast(0L) - // Swap: start standby (already demuxed) on the audible player by - // re-using the same MRL — LibVLC Java can't steal a native player - // mid-buffer, so we jump the main player to the warmed URI. - player.media().start(next.uri, *mediaOptions(next)) - if (time in 1..5_000) player.controls().setTime(time) - _currentUri.value = next.uri - standby.controls().stop() + load(next, null, 0L) return true } - override fun warmupNext() { - val next = preparedNext.get() ?: return - if (!standby.status().isPlayable) { - standby.media().startPaused(next.uri, *mediaOptions(next)) - } - } - override fun release() { - runCatching { player.release() } - runCatching { standby.release() } - runCatching { factory.release() } + onVlc { + runCatching { player?.release() } + runCatching { factory?.release() } + } + vlc.shutdown() listeners.clear() } @@ -154,11 +182,33 @@ class VlcjPlaybackEngine : PlaybackEngine { listeners -= listener } - private fun mediaOptions(media: PlaybackMedia): Array { - val opts = ArrayList(media.headers.size + 1) - media.headers.forEach { (k, v) -> - opts += ":http-header=$k: $v" + private fun onVlc(block: () -> Unit) { + if (vlc.isShutdown) return + vlc.execute { + try { + block() + } catch (t: Throwable) { + System.err.println("Vlcj: ${t.message}") + t.printStackTrace() + listeners.forEach { it.onError(t.message ?: "VLC error", true) } + } } - return opts.toTypedArray() + } + + private fun toMrl(uri: String): String { + if (uri.startsWith("http://") || uri.startsWith("https://")) return uri + return try { + when { + uri.startsWith("file:") -> File(URI(uri)).absolutePath + else -> File(uri).absolutePath + } + } catch (_: Exception) { + uri + } + } + + private fun mediaOptions(media: PlaybackMedia): Array { + if (media.headers.isEmpty()) return emptyArray() + return media.headers.map { (k, v) -> ":http-header=$k: $v" }.toTypedArray() } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 3cdb4bb2..73ab774e 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -46,7 +46,7 @@ fun YuriDesktopApp(session: DesktopSession) { val playerColors = remember(coverPixels) { coverPixels?.let { playerColorsFromPixels(it) } } - YuriTheme(playerColors = playerColors) { + YuriTheme { val tracks by session.tracks.collectAsState() val current by session.player.current.collectAsState() val playing by session.player.isPlaying.collectAsState() @@ -55,6 +55,7 @@ fun YuriDesktopApp(session: DesktopSession) { val status by session.scanMessage.collectAsState() val position by session.positionMs.collectAsState() val duration by session.durationMs.collectAsState() + val engineMessage by session.engineMessage.collectAsState() val albums = remember(tracks) { tracks.albums() } var route by remember { mutableStateOf(Route.Library) } @@ -63,18 +64,16 @@ fun YuriDesktopApp(session: DesktopSession) { AdaptiveShell( widthClass = widthClass, bottomBar = { - PlayerChromeTheme(playerColors, useArtBackground = false) { - BottomPlayerBar( - track = current?.toCover(), - playing = playing, - positionMs = position, - durationMs = duration, - onPrev = session.player::previous, - onToggle = session.player::togglePlay, - onNext = session.player::next, - onSeek = session.player::seekTo - ) - } + BottomPlayerBar( + track = current?.toCover(), + playing = playing, + positionMs = position, + durationMs = duration, + onPrev = session.player::previous, + onToggle = session.player::togglePlay, + onNext = session.player::next, + onSeek = session.player::seekTo + ) }, sidebar = { PlayerChromeTheme(playerColors, useArtBackground = true) { @@ -99,7 +98,7 @@ fun YuriDesktopApp(session: DesktopSession) { when (val r = route) { Route.Library -> LibraryGrid( albums = albums, - status = status, + status = engineMessage ?: status, roots = session.dirs.defaultMusicRoots, onOpen = { route = Route.Album(it) } ) @@ -138,7 +137,7 @@ private fun LibraryGrid( onOpen: (AlbumPageModel) -> Unit ) { Column(Modifier.fillMaxSize().padding(horizontal = 20.dp, vertical = 12.dp)) { - Text("Library", style = MaterialTheme.typography.headlineSmall) + Text("Library", style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground) Text( status, style = MaterialTheme.typography.bodySmall, From a1ebc39a7c3c391ea195178bc30286a605f8e795 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 16:25:24 +0000 Subject: [PATCH 05/69] Bundle LibVLC with the desktop app Download official VideoLAN natives at build (Windows/macOS NuGet, Debian libvlc on Linux) and point JNA at them before the engine starts. System VLC is still used when no bundle is present. --- README.md | 5 +- desktop/build.gradle.kts | 212 ++++++++++++++++++ desktop/libvlc/README.md | 20 ++ .../capital/yuri/yuriplayer/desktop/Main.kt | 54 ++--- .../desktop/player/LibVlcBootstrap.kt | 46 ++++ 5 files changed, 310 insertions(+), 27 deletions(-) create mode 100644 desktop/libvlc/README.md create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt diff --git a/README.md b/README.md index c3769261..bb952619 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,9 @@ A multi-platform Jetpack Compose music player for Android and desktop ## Desktop -Needs a system LibVLC (`vlc` / `libvlc-dev` on Linux, VideoLAN on Windows, -`brew install libvlc` on macOS). +Needs bundled LibVLC (downloaded at build: `:desktop:downloadLibVlc`) so Windows +and macOS do not require a VLC install. Linux prefers distro LibVLC and falls +back to a bundled Debian extract. See [desktop/libvlc/README.md](desktop/libvlc/README.md). ```bash ./gradlew :desktop:run diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 724e87f9..95220ac2 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -1,3 +1,8 @@ +import java.io.File +import java.net.URI +import java.nio.file.Files +import java.util.zip.ZipInputStream + plugins { alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.compose) @@ -30,6 +35,65 @@ dependencies { implementation(libs.ktor.client.cio) } +val libvlcRoot = layout.buildDirectory.dir("libvlc") +val libvlcResources = layout.buildDirectory.dir("libvlc-resources") +val downloadAllLibVlc = (findProperty("yuri.libvlc.all") as String?) == "true" +val hostLibVlc = hostLibVlcPlatform() + +val downloadLibVlc by tasks.registering { + group = "vlc" + description = "Download official LibVLC natives for the host OS (set yuri.libvlc.all=true for every OS)." + val destRoot = libvlcRoot + val all = downloadAllLibVlc + val host = hostLibVlc + outputs.dir(destRoot) + notCompatibleWithConfigurationCache("LibVLC download uses script helpers") + doLast { + val wanted = if (all) { + listOf("windows-x64", "macos-x64", "linux-x64") + } else { + listOf(host) + } + wanted.forEach { platform -> + val dest = destRoot.get().asFile.resolve(platform) + if (hasLibvlc(dest)) { + println("LibVLC $platform already present") + return@forEach + } + dest.mkdirs() + println("Downloading LibVLC for $platform…") + when (platform) { + "windows-x64" -> unpackNuget( + "https://api.nuget.org/v3-flatcontainer/videolan.libvlc.windows/3.0.23.1/videolan.libvlc.windows.3.0.23.1.nupkg", + dest, + "libvlc.dll" + ) + "macos-x64" -> unpackNuget( + "https://api.nuget.org/v3-flatcontainer/videolan.libvlc.mac/3.1.3.1/videolan.libvlc.mac.3.1.3.1.nupkg", + dest, + "libvlc.dylib" + ) + "linux-x64" -> unpackDebianLibvlc(dest) + else -> println("No bundled LibVLC recipe for $platform — system VLC will be used") + } + } + } +} + +val copyLibVlcResources by tasks.registering { + dependsOn(downloadLibVlc) + outputs.dir(libvlcResources) + notCompatibleWithConfigurationCache("LibVLC copy") + doLast { + val root = libvlcRoot.get().asFile + val out = libvlcResources.get().asFile + out.mkdirs() + root.listFiles()?.filter { it.isDirectory }?.forEach { src -> + src.copyRecursively(out.resolve(src.name), overwrite = true) + } + } +} + compose.desktop { application { mainClass = "capital.yuri.yuriplayer.desktop.MainKt" @@ -39,6 +103,154 @@ compose.desktop { description = "Yuri Player" copyright = "AGPL-3.0" vendor = "Yuri" + appResourcesRootDir.set(libvlcResources) + } + } +} + +afterEvaluate { + tasks.matching { it.name.contains("prepareAppResources", ignoreCase = true) }.configureEach { + dependsOn(copyLibVlcResources) + } + tasks.matching { it.name == "run" }.configureEach { + dependsOn(downloadLibVlc) + if (this is JavaExec) { + val dir = libvlcRoot.get().asFile.resolve(hostLibVlc) + systemProperty("yuri.libvlc.dir", dir.absolutePath) + } + } +} + +fun hostLibVlcPlatform(): String { + val os = System.getProperty("os.name").lowercase() + val arch = System.getProperty("os.arch").lowercase() + val family = when { + os.contains("win") -> "windows" + os.contains("mac") || os.contains("darwin") -> "macos" + else -> "linux" + } + val cpu = when { + arch.contains("aarch64") || arch.contains("arm64") -> "arm64" + else -> "x64" + } + return "$family-$cpu" +} + +fun hasLibvlc(dir: File): Boolean { + if (!dir.isDirectory) return false + return dir.walkTopDown().any { f -> + val n = f.name + n.startsWith("libvlc") && (n.endsWith(".dll") || n.endsWith(".so") || n.endsWith(".dylib") || n.contains(".so.")) + } +} + +fun unpackNuget(url: String, dest: File, marker: String) { + val tmp = dest.resolveSibling("${dest.name}.nupkg") + download(url, tmp) + val unzip = dest.resolveSibling("${dest.name}-unpack") + unzip.deleteRecursively() + unzip.mkdirs() + unzipFile(tmp, unzip) + val found = unzip.walkTopDown().firstOrNull { it.name.equals(marker, ignoreCase = true) } + ?: error("NuGet package did not contain $marker") + found.parentFile.copyRecursively(dest, overwrite = true) + tmp.delete() + unzip.deleteRecursively() +} + +fun unzipFile(zip: File, dest: File) { + ZipInputStream(zip.inputStream().buffered()).use { zis -> + var entry = zis.nextEntry + while (entry != null) { + val out = dest.resolve(entry.name) + if (entry.isDirectory) { + out.mkdirs() + } else { + out.parentFile.mkdirs() + out.outputStream().use { zis.copyTo(it) } + } + entry = zis.nextEntry + } + } +} + +fun unpackDebianLibvlc(dest: File) { + val base = "https://deb.debian.org/debian/pool/main/v/vlc" + val debs = listOf( + "libvlc5_3.0.23-3+b4_amd64.deb", + "libvlccore9_3.0.23-3+b4_amd64.deb", + "vlc-plugin-base_3.0.23-3+b4_amd64.deb", + "vlc-data_3.0.23-3_all.deb" + ) + val scratch = dest.resolveSibling("${dest.name}-debs") + scratch.deleteRecursively() + scratch.mkdirs() + debs.forEach { name -> + val file = scratch.resolve(name) + download("$base/$name", file) + extractDeb(file, scratch.resolve("tree")) + } + val tree = scratch.resolve("tree") + dest.mkdirs() + val libDir = tree.resolve("usr/lib/x86_64-linux-gnu") + if (libDir.isDirectory) { + libDir.listFiles()?.filter { it.isFile && it.name.startsWith("libvlc") }?.forEach { + it.copyTo(dest.resolve(it.name), overwrite = true) + } + val so = dest.resolve("libvlc.so.5") + if (so.exists() && !dest.resolve("libvlc.so").exists()) { + dest.resolve("libvlc.so").let { link -> + runCatching { Files.createSymbolicLink(link.toPath(), so.toPath().fileName) } + .onFailure { so.copyTo(dest.resolve("libvlc.so"), overwrite = true) } + } + } + val core = dest.resolve("libvlccore.so.9") + if (core.exists() && !dest.resolve("libvlccore.so").exists()) { + runCatching { + Files.createSymbolicLink( + dest.resolve("libvlccore.so").toPath(), + core.toPath().fileName + ) + }.onFailure { core.copyTo(dest.resolve("libvlccore.so"), overwrite = true) } + } + val plugins = libDir.resolve("vlc/plugins") + if (plugins.isDirectory) { + plugins.copyRecursively(dest.resolve("plugins"), overwrite = true) } } + scratch.deleteRecursively() + check(hasLibvlc(dest)) { "Debian LibVLC extract produced no libvlc" } } + +fun extractDeb(deb: File, into: File) { + into.mkdirs() + val ar = ProcessBuilder("ar", "t", deb.absolutePath).start() + val entries = ar.inputStream.bufferedReader().readLines() + check(ar.waitFor() == 0) { "ar failed on ${deb.name}" } + val data = entries.firstOrNull { it.startsWith("data.tar") } ?: error("no data.tar in ${deb.name}") + val flag = when { + data.endsWith(".xz") -> "-xJ" + data.endsWith(".gz") -> "-xz" + data.endsWith(".zst") -> "-x --zstd" + data.endsWith(".bz2") -> "-xj" + else -> "-x" + } + val extract = ProcessBuilder( + "bash", + "-lc", + "ar p ${deb.absolutePath.shell()} $data | tar $flag -C ${into.absolutePath.shell()}" + ) + .redirectErrorStream(true) + .start() + val out = extract.inputStream.bufferedReader().readText() + check(extract.waitFor() == 0) { "extract ${deb.name} failed: $out" } +} + +fun download(url: String, dest: File) { + dest.parentFile.mkdirs() + dest.outputStream().use { out -> + URI(url).toURL().openStream().use { it.copyTo(out) } + } +} + +fun String.shell(): String = "'" + replace("'", "'\\''") + "'" diff --git a/desktop/libvlc/README.md b/desktop/libvlc/README.md new file mode 100644 index 00000000..8caa66c5 --- /dev/null +++ b/desktop/libvlc/README.md @@ -0,0 +1,20 @@ +# Bundled LibVLC + +Yuri Player ships LibVLC so desktop machines do not need VLC installed. + +| Platform | Source | +| --- | --- | +| Windows x64 | Official [VideoLAN.LibVLC.Windows](https://www.nuget.org/packages/VideoLAN.LibVLC.Windows) | +| macOS | Official [VideoLAN.LibVLC.Mac](https://www.nuget.org/packages/VideoLAN.LibVLC.Mac) | +| Linux x64 | Debian `libvlc5` + `libvlccore9` + `vlc-plugin-base` (glibc). Distro VLC is still used if present. | + +Natives are **downloaded at build time** (`:desktop:downloadLibVlc`), not committed. + +```bash +./gradlew :desktop:downloadLibVlc +./gradlew :desktop:run +``` + +Override the lookup with `YURI_LIBVLC=/path/to/libvlc` or `-Dyuri.libvlc.dir=...`. + +Bundling LibVLC means the desktop distribution is covered by VLC's GPL, which is compatible with this repo's AGPL-3.0. diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt index eccc2e2e..6a304e05 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt @@ -7,6 +7,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import capital.yuri.yuriplayer.core.platform.appDirectories +import capital.yuri.yuriplayer.desktop.player.LibVlcBootstrap import capital.yuri.yuriplayer.desktop.ui.YuriDesktopApp import coil3.ImageLoader import coil3.compose.setSingletonImageLoaderFactory @@ -17,32 +18,35 @@ import io.ktor.client.engine.cio.CIO import okio.Path.Companion.toOkioPath import java.io.File -fun main() = application { - val dirs = appDirectories() - setSingletonImageLoaderFactory { context -> - ImageLoader.Builder(context) - .components { add(KtorNetworkFetcherFactory(HttpClient(CIO))) } - .diskCache { - DiskCache.Builder() - .directory(File(dirs.cacheDir, "coil").toOkioPath()) - .build() +fun main() { + LibVlcBootstrap.install() + application { + val dirs = appDirectories() + setSingletonImageLoaderFactory { context -> + ImageLoader.Builder(context) + .components { add(KtorNetworkFetcherFactory(HttpClient(CIO))) } + .diskCache { + DiskCache.Builder() + .directory(File(dirs.cacheDir, "coil").toOkioPath()) + .build() + } + .build() + } + val session = remember { DesktopSession() } + val state = rememberWindowState(width = 1280.dp, height = 800.dp) + Window( + onCloseRequest = { + session.release() + exitApplication() + }, + title = "Yuri Player", + state = state + ) { + window.background = java.awt.Color(0x12, 0x10, 0x18) + DisposableEffect(Unit) { + onDispose { session.release() } } - .build() - } - val session = remember { DesktopSession() } - val state = rememberWindowState(width = 1280.dp, height = 800.dp) - Window( - onCloseRequest = { - session.release() - exitApplication() - }, - title = "Yuri Player", - state = state - ) { - window.background = java.awt.Color(0x12, 0x10, 0x18) - DisposableEffect(Unit) { - onDispose { session.release() } + YuriDesktopApp(session) } - YuriDesktopApp(session) } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt new file mode 100644 index 00000000..dd3d5fcc --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt @@ -0,0 +1,46 @@ +package capital.yuri.yuriplayer.desktop.player + +import java.io.File + +/** + * Points JNA / LibVLC at a bundled copy before [uk.co.caprica.vlcj.factory.MediaPlayerFactory] + * loads. Order: `-Dyuri.libvlc.dir`, `YURI_LIBVLC`, Compose app-resources, then system VLC. + * + * Windows/macOS natives come from VideoLAN's official LibVLC NuGet packages. + * Linux uses extracted Debian libvlc + plugins (glibc) as a fallback; distro + * packages are still preferred when present. + */ +object LibVlcBootstrap { + fun install(): File? { + val dir = resolveDir() ?: return null + val path = dir.canonicalPath + val existing = System.getProperty("jna.library.path") + System.setProperty( + "jna.library.path", + if (existing.isNullOrBlank()) path else "$path${File.pathSeparator}$existing" + ) + val plugins = File(dir, "plugins") + if (plugins.isDirectory) { + System.setProperty("VLC_PLUGIN_PATH", plugins.canonicalPath) + } + System.err.println("LibVLC bundle: $path") + return dir + } + + private fun resolveDir(): File? { + val candidates = listOfNotNull( + System.getProperty("yuri.libvlc.dir"), + System.getenv("YURI_LIBVLC"), + System.getProperty("compose.application.resources.dir") + ) + return candidates + .map(::File) + .firstOrNull { hasLibvlc(it) } + } + + private fun hasLibvlc(dir: File): Boolean { + if (!dir.isDirectory) return false + val names = dir.list()?.toList().orEmpty() + return names.any { it.startsWith("libvlc") && (it.endsWith(".dll") || it.endsWith(".so") || it.endsWith(".dylib") || it.contains(".so.")) } + } +} From 611d91b398506e4b035d189328c3088c82b2bac3 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 16:30:01 +0000 Subject: [PATCH 06/69] Package desktop as deb/rpm/dmg/pkg/msi/exe Linux installers depend on distro vlc instead of vendoring LibVLC. Windows and macOS installers still bundle official VideoLAN natives. --- README.md | 11 ++- desktop/build.gradle.kts | 157 +++++++++++++++++---------------------- desktop/libvlc/README.md | 29 +++++--- 3 files changed, 92 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index bb952619..5355dff7 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,17 @@ A multi-platform Jetpack Compose music player for Android and desktop ## Desktop -Needs bundled LibVLC (downloaded at build: `:desktop:downloadLibVlc`) so Windows -and macOS do not require a VLC install. Linux prefers distro LibVLC and falls -back to a bundled Debian extract. See [desktop/libvlc/README.md](desktop/libvlc/README.md). +## Desktop + +Windows and macOS installers **bundle LibVLC**. Linux `.deb` / `.rpm` **depend on +distro `vlc`** (system plugins, PipeWire/Pulse, updates). Details: +[desktop/libvlc/README.md](desktop/libvlc/README.md). ```bash ./gradlew :desktop:run +./gradlew :desktop:packageDeb # Linux, Requires/Depends: vlc +./gradlew :desktop:packageRpm +./gradlew :desktop:packageDistributionForCurrentOS ``` Default music folders (scanned on start): diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 95220ac2..89f9ddba 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -1,7 +1,8 @@ import java.io.File import java.net.URI -import java.nio.file.Files import java.util.zip.ZipInputStream +import org.jetbrains.compose.desktop.application.dsl.TargetFormat +import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask plugins { alias(libs.plugins.kotlin.jvm) @@ -39,10 +40,12 @@ val libvlcRoot = layout.buildDirectory.dir("libvlc") val libvlcResources = layout.buildDirectory.dir("libvlc-resources") val downloadAllLibVlc = (findProperty("yuri.libvlc.all") as String?) == "true" val hostLibVlc = hostLibVlcPlatform() +val bundleLibVlcOnHost = !hostLibVlc.startsWith("linux") +/** Official VideoLAN natives for Windows/macOS installers. Linux uses distro VLC. */ val downloadLibVlc by tasks.registering { group = "vlc" - description = "Download official LibVLC natives for the host OS (set yuri.libvlc.all=true for every OS)." + description = "Download bundled LibVLC for Windows/macOS (Linux packages depend on system vlc)." val destRoot = libvlcRoot val all = downloadAllLibVlc val host = hostLibVlc @@ -50,10 +53,17 @@ val downloadLibVlc by tasks.registering { notCompatibleWithConfigurationCache("LibVLC download uses script helpers") doLast { val wanted = if (all) { - listOf("windows-x64", "macos-x64", "linux-x64") + listOf("windows-x64", "macos-x64", "macos-arm64") + } else if (host.startsWith("linux")) { + emptyList() } else { listOf(host) } + if (wanted.isEmpty()) { + println("Linux: not bundling LibVLC (installer Depends/Requires vlc)") + destRoot.get().asFile.mkdirs() + return@doLast + } wanted.forEach { platform -> val dest = destRoot.get().asFile.resolve(platform) if (hasLibvlc(dest)) { @@ -62,19 +72,18 @@ val downloadLibVlc by tasks.registering { } dest.mkdirs() println("Downloading LibVLC for $platform…") - when (platform) { - "windows-x64" -> unpackNuget( + when { + platform.startsWith("windows") -> unpackNuget( "https://api.nuget.org/v3-flatcontainer/videolan.libvlc.windows/3.0.23.1/videolan.libvlc.windows.3.0.23.1.nupkg", dest, "libvlc.dll" ) - "macos-x64" -> unpackNuget( + platform.startsWith("macos") -> unpackNuget( "https://api.nuget.org/v3-flatcontainer/videolan.libvlc.mac/3.1.3.1/videolan.libvlc.mac.3.1.3.1.nupkg", dest, "libvlc.dylib" ) - "linux-x64" -> unpackDebianLibvlc(dest) - else -> println("No bundled LibVLC recipe for $platform — system VLC will be used") + else -> println("Skip $platform") } } } @@ -88,9 +97,11 @@ val copyLibVlcResources by tasks.registering { val root = libvlcRoot.get().asFile val out = libvlcResources.get().asFile out.mkdirs() - root.listFiles()?.filter { it.isDirectory }?.forEach { src -> - src.copyRecursively(out.resolve(src.name), overwrite = true) - } + root.listFiles() + ?.filter { it.isDirectory && !it.name.startsWith("linux") } + ?.forEach { src -> + src.copyRecursively(out.resolve(src.name), overwrite = true) + } } } @@ -98,12 +109,37 @@ compose.desktop { application { mainClass = "capital.yuri.yuriplayer.desktop.MainKt" nativeDistributions { + targetFormats( + TargetFormat.Deb, + TargetFormat.Rpm, + TargetFormat.Dmg, + TargetFormat.Pkg, + TargetFormat.Msi, + TargetFormat.Exe + ) packageName = "Yuri Player" packageVersion = "0.1.0" description = "Yuri Player" copyright = "AGPL-3.0" vendor = "Yuri" appResourcesRootDir.set(libvlcResources) + linux { + packageName = "yuri-player" + debMaintainer = "yuri@yuri.capital" + menuGroup = "AudioVideo" + appCategory = "AudioVideo" + appRelease = "1" + rpmLicenseType = "AGPLv3" + } + windows { + menuGroup = "Yuri" + dirChooser = true + } + macOS { + bundleID = "capital.yuri.yuriplayer" + dockName = "Yuri Player" + packageVersion = "1.0.0" + } } } } @@ -113,10 +149,23 @@ afterEvaluate { dependsOn(copyLibVlcResources) } tasks.matching { it.name == "run" }.configureEach { - dependsOn(downloadLibVlc) - if (this is JavaExec) { - val dir = libvlcRoot.get().asFile.resolve(hostLibVlc) - systemProperty("yuri.libvlc.dir", dir.absolutePath) + if (bundleLibVlcOnHost) { + dependsOn(downloadLibVlc) + if (this is JavaExec) { + systemProperty("yuri.libvlc.dir", libvlcRoot.get().asFile.resolve(hostLibVlc).absolutePath) + } + } + } + tasks.withType().configureEach { + if (targetFormat == TargetFormat.Deb || targetFormat == TargetFormat.Rpm) { + freeArgs.addAll(listOf("--linux-package-deps", "vlc")) + } + if (targetFormat == TargetFormat.Msi || + targetFormat == TargetFormat.Exe || + targetFormat == TargetFormat.Dmg || + targetFormat == TargetFormat.Pkg + ) { + dependsOn(copyLibVlcResources) } } } @@ -140,7 +189,9 @@ fun hasLibvlc(dir: File): Boolean { if (!dir.isDirectory) return false return dir.walkTopDown().any { f -> val n = f.name - n.startsWith("libvlc") && (n.endsWith(".dll") || n.endsWith(".so") || n.endsWith(".dylib") || n.contains(".so.")) + n.startsWith("libvlc") && ( + n.endsWith(".dll") || n.endsWith(".so") || n.endsWith(".dylib") || n.contains(".so.") + ) } } @@ -174,83 +225,9 @@ fun unzipFile(zip: File, dest: File) { } } -fun unpackDebianLibvlc(dest: File) { - val base = "https://deb.debian.org/debian/pool/main/v/vlc" - val debs = listOf( - "libvlc5_3.0.23-3+b4_amd64.deb", - "libvlccore9_3.0.23-3+b4_amd64.deb", - "vlc-plugin-base_3.0.23-3+b4_amd64.deb", - "vlc-data_3.0.23-3_all.deb" - ) - val scratch = dest.resolveSibling("${dest.name}-debs") - scratch.deleteRecursively() - scratch.mkdirs() - debs.forEach { name -> - val file = scratch.resolve(name) - download("$base/$name", file) - extractDeb(file, scratch.resolve("tree")) - } - val tree = scratch.resolve("tree") - dest.mkdirs() - val libDir = tree.resolve("usr/lib/x86_64-linux-gnu") - if (libDir.isDirectory) { - libDir.listFiles()?.filter { it.isFile && it.name.startsWith("libvlc") }?.forEach { - it.copyTo(dest.resolve(it.name), overwrite = true) - } - val so = dest.resolve("libvlc.so.5") - if (so.exists() && !dest.resolve("libvlc.so").exists()) { - dest.resolve("libvlc.so").let { link -> - runCatching { Files.createSymbolicLink(link.toPath(), so.toPath().fileName) } - .onFailure { so.copyTo(dest.resolve("libvlc.so"), overwrite = true) } - } - } - val core = dest.resolve("libvlccore.so.9") - if (core.exists() && !dest.resolve("libvlccore.so").exists()) { - runCatching { - Files.createSymbolicLink( - dest.resolve("libvlccore.so").toPath(), - core.toPath().fileName - ) - }.onFailure { core.copyTo(dest.resolve("libvlccore.so"), overwrite = true) } - } - val plugins = libDir.resolve("vlc/plugins") - if (plugins.isDirectory) { - plugins.copyRecursively(dest.resolve("plugins"), overwrite = true) - } - } - scratch.deleteRecursively() - check(hasLibvlc(dest)) { "Debian LibVLC extract produced no libvlc" } -} - -fun extractDeb(deb: File, into: File) { - into.mkdirs() - val ar = ProcessBuilder("ar", "t", deb.absolutePath).start() - val entries = ar.inputStream.bufferedReader().readLines() - check(ar.waitFor() == 0) { "ar failed on ${deb.name}" } - val data = entries.firstOrNull { it.startsWith("data.tar") } ?: error("no data.tar in ${deb.name}") - val flag = when { - data.endsWith(".xz") -> "-xJ" - data.endsWith(".gz") -> "-xz" - data.endsWith(".zst") -> "-x --zstd" - data.endsWith(".bz2") -> "-xj" - else -> "-x" - } - val extract = ProcessBuilder( - "bash", - "-lc", - "ar p ${deb.absolutePath.shell()} $data | tar $flag -C ${into.absolutePath.shell()}" - ) - .redirectErrorStream(true) - .start() - val out = extract.inputStream.bufferedReader().readText() - check(extract.waitFor() == 0) { "extract ${deb.name} failed: $out" } -} - fun download(url: String, dest: File) { dest.parentFile.mkdirs() dest.outputStream().use { out -> URI(url).toURL().openStream().use { it.copyTo(out) } } } - -fun String.shell(): String = "'" + replace("'", "'\\''") + "'" diff --git a/desktop/libvlc/README.md b/desktop/libvlc/README.md index 8caa66c5..1268c8e9 100644 --- a/desktop/libvlc/README.md +++ b/desktop/libvlc/README.md @@ -1,20 +1,25 @@ -# Bundled LibVLC +# LibVLC on desktop -Yuri Player ships LibVLC so desktop machines do not need VLC installed. - -| Platform | Source | +| Ship as | LibVLC | | --- | --- | -| Windows x64 | Official [VideoLAN.LibVLC.Windows](https://www.nuget.org/packages/VideoLAN.LibVLC.Windows) | -| macOS | Official [VideoLAN.LibVLC.Mac](https://www.nuget.org/packages/VideoLAN.LibVLC.Mac) | -| Linux x64 | Debian `libvlc5` + `libvlccore9` + `vlc-plugin-base` (glibc). Distro VLC is still used if present. | +| Windows `.msi` / `.exe` | Bundled official [VideoLAN.LibVLC.Windows](https://www.nuget.org/packages/VideoLAN.LibVLC.Windows) in app resources | +| macOS `.dmg` / `.pkg` | Bundled official [VideoLAN.LibVLC.Mac](https://www.nuget.org/packages/VideoLAN.LibVLC.Mac) in app resources | +| Linux `.deb` / `.rpm` | **Depends on distro `vlc`** (libvlc + plugins from the OS). Not vendored. | -Natives are **downloaded at build time** (`:desktop:downloadLibVlc`), not committed. +Natives for Windows/macOS are downloaded at package time (`:desktop:downloadLibVlc`), not committed. ```bash -./gradlew :desktop:downloadLibVlc -./gradlew :desktop:run +# Linux packages (this host) — requires system VLC at runtime +./gradlew :desktop:packageDeb +./gradlew :desktop:packageRpm + +# Windows/macOS natives into the installer (on those OSes, or with -Pyuri.libvlc.all=true) +./gradlew :desktop:packageMsi +./gradlew :desktop:packageDmg ``` -Override the lookup with `YURI_LIBVLC=/path/to/libvlc` or `-Dyuri.libvlc.dir=...`. +`:desktop:run` on Linux uses NativeDiscovery against `/usr/lib`. Install `vlc` or `libvlc-dev` + `vlc-plugin-base`. + +Override lookup with `YURI_LIBVLC=/path/to/libvlc` or `-Dyuri.libvlc.dir=...`. -Bundling LibVLC means the desktop distribution is covered by VLC's GPL, which is compatible with this repo's AGPL-3.0. +Windows/macOS installers that bundle LibVLC are covered by VLC's GPL, which is compatible with this repo's AGPL-3.0. From 09e7ab1be5f4f65106347be1e7de451b789adc1f Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 16:56:38 +0000 Subject: [PATCH 07/69] Share a radial album-art backdrop on mobile and desktop Album pages both paint through AlbumArtBackdrop so the cover wash is the same helper: a soft radial bloom instead of a hard vertical band. --- .../activities/ui/AlbumDetailScreen.kt | 45 ++-------- .../yuriplayer/components/album/AlbumPage.kt | 37 +++++--- .../components/theme/AlbumArtBackdrop.kt | 85 +++++++++++++++++++ .../core/library/CoverPixels.android.kt | 35 ++++++++ .../yuriplayer/core/library/CoverPixels.kt | 8 ++ .../yuriplayer/core/library/CoverPixels.kt | 3 + 6 files changed, 164 insertions(+), 49 deletions(-) create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/AlbumArtBackdrop.kt create mode 100644 core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.android.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt index dd811478..725026b6 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt @@ -52,10 +52,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.nestedscroll.NestedScrollConnection @@ -76,6 +73,7 @@ import capital.yuri.yuriplayer.data.MetadataEnrichmentService import capital.yuri.yuriplayer.data.MyStuffPinStore import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.data.StuffPinKind +import capital.yuri.yuriplayer.components.theme.AlbumArtBackdrop import capital.yuri.yuriplayer.data.albumKey import capital.yuri.yuriplayer.data.albumTrackOrder import capital.yuri.yuriplayer.data.AlbumLog @@ -94,7 +92,6 @@ import kotlin.math.sqrt private val ExpandedHeaderBody = 420.dp private val CollapsedBarHeight = 56.dp -private val GradientFadeLength = 220.dp @Composable private fun CircularPlayButton( @@ -326,49 +323,18 @@ fun AlbumDetailScreen( else onPlayAlbum(liveAlbum.songs, 0) } - val fadePx = with(density) { GradientFadeLength.toPx() } - ThemedStatusBar(color = albumBg, enabled = true) MaterialTheme(colorScheme = scheme) { - BoxWithConstraints( + AlbumArtBackdrop( + wash = albumBg, + accent = themeColors.accent, modifier = Modifier .fillMaxSize() .nestedScroll(nestedScroll) .background(defaultBg) - .drawBehind { - val headerEnd = headerBodyH.toPx() - val solidEnd = headerEnd + with(density) { 48.dp.toPx() } - val fadeEnd = solidEnd + fadePx - - drawRect( - color = albumBg, - topLeft = Offset.Zero, - size = Size(size.width, solidEnd.coerceAtMost(size.height)) - ) - - if (fadeEnd > solidEnd) { - drawRect( - brush = Brush.verticalGradient( - colors = listOf( - albumBg, - albumBg.copy(alpha = 0.85f), - albumBg.copy(alpha = 0.45f), - albumBg.copy(alpha = 0.15f), - Color.Transparent - ), - startY = solidEnd, - endY = fadeEnd - ), - topLeft = Offset(0f, solidEnd), - size = Size( - size.width, - (fadeEnd - solidEnd).coerceAtMost(size.height - solidEnd) - ) - ) - } - } ) { + BoxWithConstraints(Modifier.fillMaxSize()) { val compact = maxWidth < 600.dp if (!compact) { Row( @@ -609,6 +575,7 @@ fun AlbumDetailScreen( ) } } + } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt index 17a843bc..dad006bb 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt @@ -1,5 +1,6 @@ package capital.yuri.yuriplayer.components.album +import androidx.compose.foundation.background import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -22,8 +23,10 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt @@ -31,6 +34,8 @@ import capital.yuri.yuriplayer.components.layout.WindowWidthClass import capital.yuri.yuriplayer.components.layout.windowWidthClass import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.AlbumPageModel +import capital.yuri.yuriplayer.components.theme.AlbumArtBackdrop +import capital.yuri.yuriplayer.components.theme.rememberCoverColors @Composable fun AlbumPage( @@ -41,16 +46,28 @@ fun AlbumPage( onTrack: (Int) -> Unit, modifier: Modifier = Modifier ) { - BoxWithConstraints(modifier.fillMaxSize()) { - val widthClass = windowWidthClass(maxWidth) - when (widthClass) { - WindowWidthClass.Compact -> AlbumPageCompact( - album, playing, onBack, onPlay, onTrack - ) - WindowWidthClass.Medium, - WindowWidthClass.Expanded -> AlbumPageWide( - album, playing, onBack, onPlay, onTrack - ) + val coverColors by rememberCoverColors( + artworkUri = album.artworkUri, + audioPath = album.tracks.firstOrNull()?.id + ) + AlbumArtBackdrop( + wash = coverColors?.container ?: Color.Transparent, + accent = coverColors?.accent ?: Color.Transparent, + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ) { + BoxWithConstraints(Modifier.fillMaxSize()) { + val widthClass = windowWidthClass(maxWidth) + when (widthClass) { + WindowWidthClass.Compact -> AlbumPageCompact( + album, playing, onBack, onPlay, onTrack + ) + WindowWidthClass.Medium, + WindowWidthClass.Expanded -> AlbumPageWide( + album, playing, onBack, onPlay, onTrack + ) + } } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/AlbumArtBackdrop.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/AlbumArtBackdrop.kt new file mode 100644 index 00000000..1a0af79c --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/AlbumArtBackdrop.kt @@ -0,0 +1,85 @@ +package capital.yuri.yuriplayer.components.theme + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.produceState +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.core.library.sampleCoverArgb +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Shared album-page atmosphere: page chrome stays [pageBackground], art color + * blooms radially from the cover (top-center on phone, left column on wide). + * Mobile and desktop both call this so the wash is identical. + */ +@Composable +fun AlbumArtBackdrop( + wash: Color, + accent: Color = wash, + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit +) { + Box( + modifier = modifier.drawBehind { + drawAlbumArtScrim(wash = wash, accent = accent) + }, + content = content + ) +} + +fun DrawScope.drawAlbumArtScrim(wash: Color, accent: Color) { + if (wash.alpha <= 0.01f) return + val compact = size.width < 600.dp.toPx() + val center = if (compact) { + Offset(size.width * 0.5f, size.height * 0.18f) + } else { + Offset(size.width * 0.16f, size.height * 0.28f) + } + val radius = size.maxDimension * 1.35f + drawRect(wash.copy(alpha = 0.07f)) + drawRect( + brush = Brush.radialGradient( + colorStops = arrayOf( + 0.00f to wash.copy(alpha = 0.50f), + 0.16f to wash.copy(alpha = 0.34f), + 0.34f to wash.copy(alpha = 0.18f), + 0.52f to wash.copy(alpha = 0.09f), + 0.72f to wash.copy(alpha = 0.035f), + 1.00f to Color.Transparent + ), + center = center, + radius = radius + ) + ) + drawRect( + brush = Brush.radialGradient( + colorStops = arrayOf( + 0.00f to accent.copy(alpha = 0.14f), + 0.28f to accent.copy(alpha = 0.06f), + 0.62f to accent.copy(alpha = 0.02f), + 1.00f to Color.Transparent + ), + center = center, + radius = radius * 0.55f + ) + ) +} + +@Composable +fun rememberCoverColors( + artworkUri: String?, + audioPath: String? = null +): State = produceState(initialValue = null, artworkUri, audioPath) { + value = withContext(Dispatchers.Default) { + sampleCoverArgb(artworkUri, audioPath)?.let { playerColorsFromPixels(it) } + } +} diff --git a/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.android.kt b/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.android.kt new file mode 100644 index 00000000..225878b6 --- /dev/null +++ b/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.android.kt @@ -0,0 +1,35 @@ +package capital.yuri.yuriplayer.core.library + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import java.io.File +import java.net.URI + +actual fun sampleCoverArgb(artworkUri: String?, audioPath: String?, size: Int): IntArray? { + val path = when { + artworkUri.isNullOrBlank() -> audioPath + artworkUri.startsWith("file:") -> runCatching { File(URI(artworkUri)).path }.getOrNull() + else -> artworkUri + } ?: return null + val file = File(path) + if (!file.isFile) return null + val opts = BitmapFactory.Options().apply { + inJustDecodeBounds = true + } + BitmapFactory.decodeFile(file.absolutePath, opts) + val longest = maxOf(opts.outWidth, opts.outHeight).coerceAtLeast(1) + val sample = (longest / size).coerceAtLeast(1) + val decoded = BitmapFactory.decodeFile( + file.absolutePath, + BitmapFactory.Options().apply { + inSampleSize = sample + inPreferredConfig = Bitmap.Config.ARGB_8888 + } + ) ?: return null + val scaled = Bitmap.createScaledBitmap(decoded, size, size, true) + if (scaled != decoded) decoded.recycle() + val pixels = IntArray(size * size) + scaled.getPixels(pixels, 0, size, 0, 0, size, size) + if (scaled != decoded) scaled.recycle() + return pixels +} diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt new file mode 100644 index 00000000..9ea2f32a --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt @@ -0,0 +1,8 @@ +package capital.yuri.yuriplayer.core.library + +/** Downsampled ARGB for theme extraction (folder art, then embedded). */ +expect fun sampleCoverArgb( + artworkUri: String?, + audioPath: String? = null, + size: Int = 48 +): IntArray? diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt index 341e76f5..0926078c 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt @@ -8,6 +8,9 @@ import java.io.File import java.net.URI import javax.imageio.ImageIO +actual fun sampleCoverArgb(artworkUri: String?, audioPath: String?, size: Int): IntArray? = + CoverPixels.argb(artworkUri, audioPath, size) + /** Downsampled ARGB for theme extraction. Folder art first, then embedded. */ object CoverPixels { fun argb(artworkUri: String?, audioPath: String?, size: Int = 48): IntArray? { From 8f793b253837955d6347a5acb6450e9786944493 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 17:13:04 +0000 Subject: [PATCH 08/69] Spotify-shaped desktop shell, Expressive chrome, and KMP Room driver Left bento nav, center stage, right now-playing plus shared QueuePanel, bottom bar play button tinted from the current cover. Desktop settings is a large Discord-style modal. Android 12+ can follow system colors. Room uses BundledSQLiteDriver so the catalog is on the KMP sqlite path. --- app/build.gradle.kts | 6 + .../activities/ui/SettingsScreen.kt | 22 ++- .../yuriplayer/activities/ui/theme/Theme.kt | 24 ++- .../yuri/yuriplayer/data/LibrarySettings.kt | 9 + .../yuri/yuriplayer/data/db/YuriDatabase.kt | 6 +- .../yuri/yuriplayer/data/db/YuriMigrations.kt | 182 +++++++++--------- build.gradle.kts | 1 + .../components/layout/SpotifyShell.kt | 168 ++++++++++++++++ .../components/player/BottomPlayerBar.kt | 33 +++- .../components/player/NowPlayingSidebar.kt | 72 ++----- .../components/player/QueuePanel.kt | 146 ++++++++++++++ .../components/settings/SettingsShell.kt | 124 ++++++++++++ .../yuriplayer/components/theme/YuriShapes.kt | 14 ++ .../yuriplayer/components/theme/YuriTheme.kt | 25 ++- .../yuriplayer/desktop/ui/DesktopSettings.kt | 103 ++++++++++ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 178 ++++++++++------- gradle/libs.versions.toml | 3 + 17 files changed, 876 insertions(+), 240 deletions(-) create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/SpotifyShell.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsShell.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriShapes.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c0f81d3e..9b73c467 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,6 +11,7 @@ plugins { alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) + alias(libs.plugins.androidx.room) alias(libs.plugins.aboutlibraries) alias(libs.plugins.aboutlibraries.android) } @@ -160,6 +161,10 @@ android { } } +room { + schemaDirectory(layout.projectDirectory.dir("schemas").asFile.absolutePath) +} + aboutLibraries { library { duplicationMode.set(com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE) @@ -267,6 +272,7 @@ dependencies { implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.ktx) + implementation(libs.androidx.sqlite.bundled) ksp(libs.androidx.room.compiler) implementation(libs.jaudiotagger) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt index 63935ca2..ec90c8e2 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt @@ -353,6 +353,10 @@ private fun PlaybackEngineSettingsScreen(onBack: () -> Unit) { .verticalScroll(rememberScrollState()) ) { SettingsTopBar(title = "Advanced playback", onBack = onBack) + TextNote( + "This list is Android-only. Desktop always uses LibVLC. " + + "The choice applies the next time a song starts." + ) SettingsSectionTitle("Decoder") TextNote( @@ -490,6 +494,7 @@ private fun AppearanceSettingsScreen(onBack: () -> Unit) { val settings: LibrarySettings = koinInject() var coverVariant by remember { mutableStateOf(settings.getCoverColorVariant()) } var bannerVariant by remember { mutableStateOf(settings.getBannerColorVariant()) } + var systemColors by remember { mutableStateOf(settings.useSystemColors()) } Column( modifier = Modifier @@ -500,10 +505,23 @@ private fun AppearanceSettingsScreen(onBack: () -> Unit) { SettingsTopBar(title = "Appearance", onBack = onBack) TextNote( - text = "Colors come from your artwork. Album covers and artist headers " + - "can use different palettes." + text = "On Android 12 and newer, the app chrome can follow your system " + + "palette. Playing artwork still tints now playing." ) + SettingsSectionTitle("System") + SettingsGroup { + SettingsSwitchRow( + title = "Use system colors", + subtitle = "Material You on devices that support it. Desktop stays Yuri purple.", + checked = systemColors, + onCheckedChange = { + settings.setUseSystemColors(it) + systemColors = it + } + ) + } + SettingsSectionTitle("Cover") SettingsGroup { ArtColorVariant.entries.forEach { variant -> diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/theme/Theme.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/theme/Theme.kt index 86797d96..b359e83d 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/theme/Theme.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/theme/Theme.kt @@ -1,19 +1,26 @@ package capital.yuri.yuriplayer.activities.ui.theme import android.app.Activity +import android.os.Build import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat +import capital.yuri.yuriplayer.components.theme.YuriShapes +import capital.yuri.yuriplayer.data.LibrarySettings +import org.koin.compose.koinInject private val ColorBlackish = Color(0xFF1A1224) -/** Always-dark purple theme. Accent customization lands in Settings later. */ private val YuriDarkPurple = darkColorScheme( primary = YuriPurple, onPrimary = ColorBlackish, @@ -36,10 +43,18 @@ private val YuriDarkPurple = darkColorScheme( fun YuriPlayerTheme( content: @Composable () -> Unit ) { - val scheme = YuriDarkPurple + val settings: LibrarySettings = koinInject() + val colorRev by settings.colorPrefsRevision.collectAsState() + val context = LocalContext.current + val scheme = if (colorRev >= 0 && + settings.useSystemColors() && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + ) { + dynamicDarkColorScheme(context) + } else { + YuriDarkPurple + } - // Keep system status-bar *icons* readable against the app chrome. - // Light theme later: same rule (luminance of surface) flips icons automatically. val view = LocalView.current if (!view.isInEditMode) { SideEffect { @@ -54,6 +69,7 @@ fun YuriPlayerTheme( MaterialTheme( colorScheme = scheme, + shapes = YuriShapes, typography = Typography, content = content ) diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt b/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt index 11bff554..c4a48f1e 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt @@ -39,6 +39,14 @@ class LibrarySettings(context: Context) { /** Ticks when cover/banner color variants change so themed pages re-resolve once. */ val colorPrefsRevision: StateFlow = _colorPrefsRevision.asStateFlow() + fun useSystemColors(): Boolean = prefs.getBoolean(KEY_SYSTEM_COLORS, true) + + fun setUseSystemColors(enabled: Boolean) { + if (enabled == useSystemColors()) return + prefs.edit().putBoolean(KEY_SYSTEM_COLORS, enabled).apply() + bumpColorPrefs() + } + // ── local scan mode ─────────────────────────────────────────────────── fun getScanMode(): LibraryScanMode { @@ -284,6 +292,7 @@ class LibrarySettings(context: Context) { private const val KEY_STREAM_QUALITY = "stream_quality" private const val KEY_COVER_COLOR_VARIANT = "cover_color_variant" private const val KEY_BANNER_COLOR_VARIANT = "banner_color_variant" + private const val KEY_SYSTEM_COLORS = "system_colors" val DEFAULT_ROOTS = listOf( "Music", diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriDatabase.kt b/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriDatabase.kt index da2f7045..0c8026f3 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriDatabase.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriDatabase.kt @@ -4,6 +4,8 @@ import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import kotlinx.coroutines.Dispatchers @Database( entities = [ @@ -46,8 +48,8 @@ abstract class YuriDatabase : RoomDatabase() { YuriDatabase::class.java, "yuriplayer.db" ) - // Never wipe user data on schema change. When bumping [version], - // add a Migration in YuriMigrations and register it here. + .setDriver(BundledSQLiteDriver()) + .setQueryCoroutineContext(Dispatchers.IO) .addMigrations(*YuriMigrations.ALL) .build() } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriMigrations.kt b/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriMigrations.kt index 1b803d11..d4c7e10f 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriMigrations.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriMigrations.kt @@ -1,115 +1,119 @@ package capital.yuri.yuriplayer.data.db import androidx.room.migration.Migration +import androidx.sqlite.SQLiteConnection import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.execSQL /** * Additive Room migrations. Register every new Migration here and bump * [YuriDatabase] version — never use fallbackToDestructiveMigration. + * + * Both SupportSQLite and SQLiteConnection paths are implemented so we can + * run on the bundled KMP driver. */ object YuriMigrations { val MIGRATION_6_7 = object : Migration(6, 7) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( - """ - CREATE TABLE IF NOT EXISTS playlist_covers ( - id TEXT NOT NULL PRIMARY KEY, - playlistId TEXT NOT NULL, - uri TEXT NOT NULL, - isSecret INTEGER NOT NULL DEFAULT 0, - sortOrder INTEGER NOT NULL DEFAULT 0, - createdAtMs INTEGER NOT NULL - ) - """.trimIndent() - ) - db.execSQL( - "CREATE INDEX IF NOT EXISTS index_playlist_covers_playlistId ON playlist_covers(playlistId)" - ) - db.execSQL( - "CREATE INDEX IF NOT EXISTS index_playlist_covers_playlistId_sortOrder ON playlist_covers(playlistId, sortOrder)" - ) - db.execSQL( - "ALTER TABLE playlists ADD COLUMN activeCoverId TEXT" - ) - // Seed cover rows from legacy customImageUri so existing art is kept. - db.execSQL( - """ - INSERT INTO playlist_covers (id, playlistId, uri, isSecret, sortOrder, createdAtMs) - SELECT - id || '-legacy', - id, - customImageUri, - 0, - 0, - COALESCE(updatedAtMs, createdAtMs, 0) - FROM playlists - WHERE customImageUri IS NOT NULL AND length(customImageUri) > 0 - """.trimIndent() - ) - db.execSQL( - """ - UPDATE playlists - SET activeCoverId = id || '-legacy' - WHERE customImageUri IS NOT NULL AND length(customImageUri) > 0 - """.trimIndent() - ) - } + override fun migrate(db: SupportSQLiteDatabase) = sql6to7 { db.execSQL(it) } + override fun migrate(connection: SQLiteConnection) = sql6to7 { connection.execSQL(it) } } val MIGRATION_7_8 = object : Migration(7, 8) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE catalog_artists ADD COLUMN mbid TEXT") - db.execSQL( - """ - CREATE TABLE IF NOT EXISTS catalog_credits ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - subjectType TEXT NOT NULL, - subjectKey TEXT NOT NULL, - artistKey TEXT NOT NULL, - displayName TEXT NOT NULL, - role TEXT NOT NULL, - position INTEGER NOT NULL DEFAULT 0 - ) - """.trimIndent() - ) - db.execSQL( - "CREATE INDEX IF NOT EXISTS index_catalog_credits_subjectType_subjectKey ON catalog_credits(subjectType, subjectKey)" - ) - db.execSQL( - "CREATE INDEX IF NOT EXISTS index_catalog_credits_artistKey ON catalog_credits(artistKey)" - ) - } + override fun migrate(db: SupportSQLiteDatabase) = sql7to8 { db.execSQL(it) } + override fun migrate(connection: SQLiteConnection) = sql7to8 { connection.execSQL(it) } } val MIGRATION_8_9 = object : Migration(8, 9) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( - "CREATE INDEX IF NOT EXISTS " + - "index_catalog_tracks_sourceType_sourceInstanceId_externalId " + - "ON catalog_tracks(sourceType, sourceInstanceId, externalId)" - ) - } + override fun migrate(db: SupportSQLiteDatabase) = sql8to9 { db.execSQL(it) } + override fun migrate(connection: SQLiteConnection) = sql8to9 { connection.execSQL(it) } } val MIGRATION_9_10 = object : Migration(9, 10) { - override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL( - """ - CREATE TABLE IF NOT EXISTS artist_aliases ( - aliasKey TEXT NOT NULL PRIMARY KEY, - canonicalKey TEXT NOT NULL, - aliasName TEXT NOT NULL, - source TEXT NOT NULL, - createdAtMs INTEGER NOT NULL - ) - """.trimIndent() + override fun migrate(db: SupportSQLiteDatabase) = sql9to10 { db.execSQL(it) } + override fun migrate(connection: SQLiteConnection) = sql9to10 { connection.execSQL(it) } + } + + val ALL: Array = arrayOf(MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10) + + private fun sql6to7(exec: (String) -> Unit) { + exec( + """ + CREATE TABLE IF NOT EXISTS playlist_covers ( + id TEXT NOT NULL PRIMARY KEY, + playlistId TEXT NOT NULL, + uri TEXT NOT NULL, + isSecret INTEGER NOT NULL DEFAULT 0, + sortOrder INTEGER NOT NULL DEFAULT 0, + createdAtMs INTEGER NOT NULL ) - db.execSQL( - "CREATE INDEX IF NOT EXISTS index_artist_aliases_canonicalKey ON artist_aliases(canonicalKey)" + """.trimIndent() + ) + exec("CREATE INDEX IF NOT EXISTS index_playlist_covers_playlistId ON playlist_covers(playlistId)") + exec("CREATE INDEX IF NOT EXISTS index_playlist_covers_playlistId_sortOrder ON playlist_covers(playlistId, sortOrder)") + exec("ALTER TABLE playlists ADD COLUMN activeCoverId TEXT") + exec( + """ + INSERT INTO playlist_covers (id, playlistId, uri, isSecret, sortOrder, createdAtMs) + SELECT + id || '-legacy', + id, + customImageUri, + 0, + 0, + COALESCE(updatedAtMs, createdAtMs, 0) + FROM playlists + WHERE customImageUri IS NOT NULL AND length(customImageUri) > 0 + """.trimIndent() + ) + exec( + """ + UPDATE playlists + SET activeCoverId = id || '-legacy' + WHERE customImageUri IS NOT NULL AND length(customImageUri) > 0 + """.trimIndent() + ) + } + + private fun sql7to8(exec: (String) -> Unit) { + exec("ALTER TABLE catalog_artists ADD COLUMN mbid TEXT") + exec( + """ + CREATE TABLE IF NOT EXISTS catalog_credits ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + subjectType TEXT NOT NULL, + subjectKey TEXT NOT NULL, + artistKey TEXT NOT NULL, + displayName TEXT NOT NULL, + role TEXT NOT NULL, + position INTEGER NOT NULL DEFAULT 0 ) - } + """.trimIndent() + ) + exec("CREATE INDEX IF NOT EXISTS index_catalog_credits_subjectType_subjectKey ON catalog_credits(subjectType, subjectKey)") + exec("CREATE INDEX IF NOT EXISTS index_catalog_credits_artistKey ON catalog_credits(artistKey)") } - val ALL: Array = arrayOf(MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10) + private fun sql8to9(exec: (String) -> Unit) { + exec( + "CREATE INDEX IF NOT EXISTS " + + "index_catalog_tracks_sourceType_sourceInstanceId_externalId " + + "ON catalog_tracks(sourceType, sourceInstanceId, externalId)" + ) + } + + private fun sql9to10(exec: (String) -> Unit) { + exec( + """ + CREATE TABLE IF NOT EXISTS artist_aliases ( + aliasKey TEXT NOT NULL PRIMARY KEY, + canonicalKey TEXT NOT NULL, + aliasName TEXT NOT NULL, + source TEXT NOT NULL, + createdAtMs INTEGER NOT NULL + ) + """.trimIndent() + ) + exec("CREATE INDEX IF NOT EXISTS index_artist_aliases_canonicalKey ON artist_aliases(canonicalKey)") + } } diff --git a/build.gradle.kts b/build.gradle.kts index cd50956e..b136521d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,4 +8,5 @@ plugins { alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.compose.multiplatform) apply false alias(libs.plugins.ksp) apply false + alias(libs.plugins.androidx.room) apply false } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/SpotifyShell.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/SpotifyShell.kt new file mode 100644 index 00000000..b87376bc --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/SpotifyShell.kt @@ -0,0 +1,168 @@ +package capital.yuri.yuriplayer.components.layout + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.LibraryMusic +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +enum class DesktopNav { Home, Search, Library } + +/** + * Spotify-shaped chrome: left bento nav, center stage, right now-playing/queue, + * continuous bottom bar. Material 3 Expressive corners, not Spotify's squircle. + */ +@Composable +fun SpotifyShell( + nav: DesktopNav, + onNav: (DesktopNav) -> Unit, + onSettings: () -> Unit, + bottomBar: @Composable () -> Unit, + sidebar: @Composable () -> Unit, + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + val bg = MaterialTheme.colorScheme.background + Column(modifier.fillMaxSize().background(bg)) { + Row( + Modifier + .weight(1f) + .fillMaxWidth() + .padding(start = 8.dp, end = 8.dp, top = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + LeftNav( + nav = nav, + onNav = onNav, + onSettings = onSettings, + modifier = Modifier + .width(248.dp) + .fillMaxHeight() + ) + Surface( + modifier = Modifier.weight(1f).fillMaxHeight(), + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.surface, + tonalElevation = 0.dp + ) { + Box(Modifier.fillMaxSize()) { content() } + } + Surface( + modifier = Modifier.width(360.dp).fillMaxHeight(), + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.surface, + tonalElevation = 0.dp + ) { + sidebar() + } + } + Box(Modifier.fillMaxWidth().padding(8.dp)) { bottomBar() } + } +} + +@Composable +private fun LeftNav( + nav: DesktopNav, + onNav: (DesktopNav) -> Unit, + onSettings: () -> Unit, + modifier: Modifier = Modifier +) { + Column(modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) { + Surface( + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.surface, + modifier = Modifier.fillMaxWidth() + ) { + Column(Modifier.padding(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + NavRow(Icons.Default.Home, "Home", nav == DesktopNav.Home) { onNav(DesktopNav.Home) } + NavRow(Icons.Default.Search, "Search", nav == DesktopNav.Search) { onNav(DesktopNav.Search) } + } + } + Surface( + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.surface, + modifier = Modifier.weight(1f).fillMaxWidth() + ) { + Column(Modifier.padding(12.dp).fillMaxSize()) { + NavRow( + Icons.Outlined.LibraryMusic, + "Your library", + nav == DesktopNav.Library + ) { onNav(DesktopNav.Library) } + Spacer(Modifier.height(8.dp)) + Text( + "Local folders show up here.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + modifier = Modifier.padding(horizontal = 8.dp) + ) + Spacer(Modifier.weight(1f)) + NavRow(Icons.Default.Settings, "Settings", selected = false, onClick = onSettings) + } + } + } +} + +@Composable +private fun NavRow( + icon: ImageVector, + label: String, + selected: Boolean, + onClick: () -> Unit +) { + val shape = if (selected) CircleShape else RoundedCornerShape(28.dp) + Row( + Modifier + .fillMaxWidth() + .clip(shape) + .clickable(onClick = onClick) + .background( + if (selected) MaterialTheme.colorScheme.primary.copy(alpha = 0.16f) + else MaterialTheme.colorScheme.surface + ) + .padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + icon, + contentDescription = label, + modifier = Modifier.size(24.dp), + tint = if (selected) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.onSurface + ) + Spacer(Modifier.width(12.dp)) + Text( + label, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, + color = if (selected) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.onSurface + ) + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt index 7376725e..d861f663 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt @@ -5,21 +5,27 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.SkipNext import androidx.compose.material.icons.filled.SkipPrevious +import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -37,27 +43,34 @@ fun BottomPlayerBar( onToggle: () -> Unit, onNext: () -> Unit, onSeek: (Long) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + accent: Color = MaterialTheme.colorScheme.primary ) { Surface( modifier = modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface, + shape = MaterialTheme.shapes.large, tonalElevation = 0.dp ) { - Column(Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + Column(Modifier.padding(horizontal = 16.dp, vertical = 10.dp)) { if (durationMs > 0) { Slider( value = (positionMs.toFloat() / durationMs).coerceIn(0f, 1f), onValueChange = { onSeek((it * durationMs).toLong()) }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + colors = SliderDefaults.colors( + thumbColor = accent, + activeTrackColor = accent, + inactiveTrackColor = accent.copy(alpha = 0.24f) + ) ) } Row( Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { - CoverArt(model = track?.artworkUri, size = 48.dp, corner = 6.dp) + CoverArt(model = track?.artworkUri, size = 56.dp, corner = 12.dp) Spacer(Modifier.width(12.dp)) Column(Modifier.weight(1f)) { Text( @@ -78,11 +91,19 @@ fun BottomPlayerBar( IconButton(onClick = onPrev) { Icon(Icons.Default.SkipPrevious, contentDescription = "Previous", tint = MaterialTheme.colorScheme.onSurface) } - IconButton(onClick = onToggle) { + FilledIconButton( + onClick = onToggle, + modifier = Modifier.size(56.dp), + shape = CircleShape, + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = accent, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { Icon( if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, contentDescription = if (playing) "Pause" else "Play", - tint = MaterialTheme.colorScheme.onSurface + modifier = Modifier.size(28.dp) ) } IconButton(onClick = onNext) { diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index 2770f9ed..8bbf762f 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -8,28 +8,17 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ScrollableTabRow -import androidx.compose.material3.Tab import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt -import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.CoverRef import capital.yuri.yuriplayer.components.model.TrackRowModel -enum class SidebarTab { NowPlaying, Queue, History } - @Composable fun NowPlayingSidebar( track: CoverRef?, @@ -37,38 +26,24 @@ fun NowPlayingSidebar( history: List, onQueueTrack: (TrackRowModel) -> Unit, onHistoryTrack: (TrackRowModel) -> Unit, + onClearQueue: () -> Unit = {}, modifier: Modifier = Modifier ) { - var tab by remember { mutableIntStateOf(0) } Column( modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) - .padding(16.dp) + .padding(12.dp) ) { - ScrollableTabRow(selectedTabIndex = tab, edgePadding = 0.dp) { - Tab(selected = tab == 0, onClick = { tab = 0 }, text = { Text("Now playing") }) - Tab(selected = tab == 1, onClick = { tab = 1 }, text = { Text("Queue") }) - Tab(selected = tab == 2, onClick = { tab = 2 }, text = { Text("History") }) - } - Spacer(Modifier.height(16.dp)) - when (tab) { - 0 -> NowPlayingPane(track) - 1 -> TrackPane(queue, emptyMessage = "Queue is empty", onTrack = onQueueTrack) - else -> TrackPane(history, emptyMessage = "No history yet", onTrack = onHistoryTrack) - } - } -} - -@Composable -private fun NowPlayingPane(track: CoverRef?) { - Column { CoverArt( model = track?.artworkUri, - modifier = Modifier.fillMaxWidth().aspectRatio(1f), - corner = 12.dp + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1f) + .padding(4.dp), + corner = 20.dp ) - Spacer(Modifier.height(16.dp)) + Spacer(Modifier.height(12.dp)) Text( track?.title ?: "Nothing playing", style = MaterialTheme.typography.titleLarge, @@ -84,26 +59,17 @@ private fun NowPlayingPane(track: CoverRef?) { maxLines = 1, overflow = TextOverflow.Ellipsis ) - } -} - -@Composable -private fun TrackPane( - tracks: List, - emptyMessage: String, - onTrack: (TrackRowModel) -> Unit -) { - if (tracks.isEmpty()) { - Text( - emptyMessage, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), - modifier = Modifier.padding(8.dp) + Spacer(Modifier.height(12.dp)) + QueuePanel( + nowPlaying = track, + upcoming = queue.filter { !it.highlighted }, + history = history, + onPlay = { row -> + if (history.any { it.id == row.id } && queue.none { it.id == row.id }) onHistoryTrack(row) + else onQueueTrack(row) + }, + onClearQueue = onClearQueue, + modifier = Modifier.weight(1f) ) - return - } - LazyColumn { - items(tracks, key = { it.id }) { row -> - TrackRow(track = row, onClick = { onTrack(row) }, showCover = true, showAlbum = true) - } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt new file mode 100644 index 00000000..ecec896c --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt @@ -0,0 +1,146 @@ +package capital.yuri.yuriplayer.components.player + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.FilterChip +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.model.CoverRef +import capital.yuri.yuriplayer.components.model.TrackRowModel + +private enum class QueueTab { Queue, History } + +/** + * Shared queue chrome used by the mobile overlay and the desktop right rail. + * Upcoming list + recently played — same tabs as Android [capital.yuri.yuriplayer.activities.ui.QueuePanel]. + */ +@Composable +fun QueuePanel( + nowPlaying: CoverRef?, + upcoming: List, + history: List, + onPlay: (TrackRowModel) -> Unit, + onClearQueue: () -> Unit = {}, + onClearHistory: () -> Unit = {}, + modifier: Modifier = Modifier +) { + var tab by remember { mutableStateOf(QueueTab.Queue) } + Column(modifier.fillMaxSize()) { + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + FilterChip( + selected = tab == QueueTab.Queue, + onClick = { tab = QueueTab.Queue }, + label = { Text("Queue") } + ) + FilterChip( + selected = tab == QueueTab.History, + onClick = { tab = QueueTab.History }, + label = { Text("Recently played") } + ) + Spacer(Modifier.weight(1f)) + if (tab == QueueTab.Queue && upcoming.isNotEmpty()) { + TextButton(onClick = onClearQueue) { Text("Clear") } + } + if (tab == QueueTab.History && history.isNotEmpty()) { + TextButton(onClick = onClearHistory) { Text("Clear") } + } + } + when (tab) { + QueueTab.Queue -> { + nowPlaying?.let { NowPlayingQueueCard(it) } + if (upcoming.isEmpty()) { + Text( + "Nothing up next.", + modifier = Modifier.padding(16.dp), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + ) + } else { + LazyColumn(Modifier.weight(1f)) { + items(upcoming, key = { it.id }) { row -> + TrackRow( + track = row, + onClick = { onPlay(row) }, + showCover = true, + showAlbum = false + ) + } + } + } + } + QueueTab.History -> { + if (history.isEmpty()) { + Text( + "Play something and it’ll land here.", + modifier = Modifier.padding(16.dp), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + ) + } else { + LazyColumn(Modifier.weight(1f)) { + items(history, key = { it.id }) { row -> + TrackRow( + track = row, + onClick = { onPlay(row) }, + showCover = true, + showAlbum = true + ) + } + } + } + } + } + } +} + +@Composable +private fun NowPlayingQueueCard(track: CoverRef) { + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp), + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.45f) + ) { + Column(Modifier.padding(12.dp)) { + Text( + "Now playing", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary + ) + Text( + track.title, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + track.subtitle, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsShell.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsShell.kt new file mode 100644 index 00000000..d245e6b0 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsShell.kt @@ -0,0 +1,124 @@ +package capital.yuri.yuriplayer.components.settings + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.ColorLens +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.LibraryMusic +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +enum class SettingsCategory(val label: String, val icon: ImageVector) { + Appearance("Appearance", Icons.Default.ColorLens), + Playback("Playback", Icons.Default.PlayArrow), + Library("Library", Icons.Default.LibraryMusic), + About("About", Icons.Default.Info) +} + +/** + * Discord-shaped settings: left category rail, large right pane. + * Material 3 surfaces and extra-round selected pills. + */ +@Composable +fun SettingsShell( + selected: SettingsCategory, + onSelect: (SettingsCategory) -> Unit, + onClose: () -> Unit, + modifier: Modifier = Modifier, + categories: List = SettingsCategory.entries, + content: @Composable () -> Unit +) { + Surface( + modifier = modifier, + shape = MaterialTheme.shapes.extraLarge, + color = MaterialTheme.colorScheme.surface, + tonalElevation = 2.dp + ) { + Row(Modifier.fillMaxSize()) { + Column( + Modifier + .width(240.dp) + .fillMaxHeight() + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f)) + .padding(16.dp) + ) { + Text( + "Settings", + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 12.dp) + ) + categories.forEach { cat -> + val on = cat == selected + Row( + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(50)) + .clickable { onSelect(cat) } + .background( + if (on) MaterialTheme.colorScheme.primary.copy(alpha = 0.16f) + else MaterialTheme.colorScheme.surface.copy(alpha = 0f) + ) + .padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + cat.icon, + contentDescription = cat.label, + tint = if (on) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.onSurface + ) + Spacer(Modifier.width(12.dp)) + Text( + cat.label, + fontWeight = if (on) FontWeight.SemiBold else FontWeight.Medium, + color = if (on) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.onSurface + ) + } + } + } + Column(Modifier.weight(1f).fillMaxHeight()) { + Row( + Modifier.fillMaxWidth().padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + selected.label, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f).padding(start = 8.dp) + ) + IconButton(onClick = onClose) { + Icon(Icons.Default.Close, contentDescription = "Close") + } + } + Column(Modifier.fillMaxSize().padding(horizontal = 24.dp, vertical = 8.dp)) { + content() + } + } + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriShapes.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriShapes.kt new file mode 100644 index 00000000..95f9ccc0 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriShapes.kt @@ -0,0 +1,14 @@ +package capital.yuri.yuriplayer.components.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Shapes +import androidx.compose.ui.unit.dp + +/** M3 Expressive-leaning scale: bigger rounds, bento cards. */ +val YuriShapes = Shapes( + extraSmall = RoundedCornerShape(8.dp), + small = RoundedCornerShape(12.dp), + medium = RoundedCornerShape(20.dp), + large = RoundedCornerShape(28.dp), + extraLarge = RoundedCornerShape(36.dp) +) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt index f8e51fe7..3e8dd168 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt @@ -1,19 +1,25 @@ package capital.yuri.yuriplayer.components.theme import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @Composable -fun YuriTheme(content: @Composable () -> Unit) { - val scheme = YuriDarkColorScheme - MaterialTheme(colorScheme = scheme) { +fun YuriTheme( + colorScheme: ColorScheme = YuriDarkColorScheme, + content: @Composable () -> Unit +) { + MaterialTheme( + colorScheme = colorScheme, + shapes = YuriShapes + ) { Surface( modifier = Modifier.fillMaxSize(), - color = scheme.background, - contentColor = scheme.onBackground + color = colorScheme.background, + contentColor = colorScheme.onBackground ) { content() } @@ -32,12 +38,5 @@ fun PlayerChromeTheme( } else { base } - MaterialTheme(colorScheme = scheme) { - Surface( - modifier = Modifier.fillMaxSize(), - color = scheme.background, - contentColor = scheme.onBackground, - content = content - ) - } + MaterialTheme(colorScheme = scheme, shapes = YuriShapes, content = content) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt new file mode 100644 index 00000000..b0c6dd0f --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt @@ -0,0 +1,103 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import capital.yuri.yuriplayer.components.settings.SettingsCategory +import capital.yuri.yuriplayer.components.settings.SettingsShell + +@Composable +fun DesktopSettingsDialog(onDismiss: () -> Unit) { + var category by remember { mutableStateOf(SettingsCategory.Appearance) } + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + SettingsShell( + selected = category, + onSelect = { category = it }, + onClose = onDismiss, + modifier = Modifier + .widthIn(min = 720.dp, max = 1080.dp) + .heightIn(min = 480.dp, max = 720.dp) + .fillMaxWidth(0.86f) + .height(640.dp), + categories = listOf( + SettingsCategory.Appearance, + SettingsCategory.Playback, + SettingsCategory.Library, + SettingsCategory.About + ) + ) { + when (category) { + SettingsCategory.Appearance -> AppearancePane() + SettingsCategory.Playback -> PlaybackPane() + SettingsCategory.Library -> LibraryPane() + SettingsCategory.About -> AboutPane() + } + } + } +} + +@Composable +private fun AppearancePane() { + Column { + Text( + "Yuri’s dark stage plus colors from whatever is playing. " + + "System Material You is an Android thing.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) + ) + Spacer(Modifier.height(16.dp)) + Text("Cover art tints the now-playing rail and the play button.", color = MaterialTheme.colorScheme.onSurface) + } +} + +@Composable +private fun PlaybackPane() { + Column { + Text("Audio engine", style = MaterialTheme.typography.titleMedium) + Spacer(Modifier.height(8.dp)) + Text( + "Desktop uses LibVLC (vlcj). That’s the same family as the default " + + "Android engine. Media3 and FFmpeg are phone-only options.", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f) + ) + Spacer(Modifier.height(12.dp)) + Text("LibVLC — bundled on Windows/macOS, system package on Linux.", color = MaterialTheme.colorScheme.onSurface) + } +} + +@Composable +private fun LibraryPane() { + Column { + Text( + "Scans the OS music folder on launch (XDG Music, ~/Music, or %USERPROFILE%\\Music).", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f) + ) + } +} + +@Composable +private fun AboutPane() { + Column(Modifier.fillMaxSize().padding(top = 8.dp)) { + Text("Yuri Player", style = MaterialTheme.typography.headlineSmall) + Text("Desktop · Compose Multiplatform", color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)) + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 73ab774e..27aa122c 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -1,15 +1,16 @@ package capital.yuri.yuriplayer.desktop.ui import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -21,8 +22,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.album.AlbumPage -import capital.yuri.yuriplayer.components.layout.AdaptiveShell -import capital.yuri.yuriplayer.components.layout.windowWidthClass +import capital.yuri.yuriplayer.components.layout.DesktopNav +import capital.yuri.yuriplayer.components.layout.SpotifyShell import capital.yuri.yuriplayer.components.list.AlbumCard import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.model.albums @@ -58,74 +59,90 @@ fun YuriDesktopApp(session: DesktopSession) { val engineMessage by session.engineMessage.collectAsState() val albums = remember(tracks) { tracks.albums() } var route by remember { mutableStateOf(Route.Library) } + var nav by remember { mutableStateOf(DesktopNav.Home) } + var showSettings by remember { mutableStateOf(false) } + var query by remember { mutableStateOf("") } - BoxWithConstraints(Modifier.fillMaxSize()) { - val widthClass = windowWidthClass(maxWidth) - AdaptiveShell( - widthClass = widthClass, - bottomBar = { - BottomPlayerBar( + SpotifyShell( + nav = nav, + onNav = { + nav = it + route = Route.Library + }, + onSettings = { showSettings = true }, + bottomBar = { + BottomPlayerBar( + track = current?.toCover(), + playing = playing, + positionMs = position, + durationMs = duration, + onPrev = session.player::previous, + onToggle = session.player::togglePlay, + onNext = session.player::next, + onSeek = session.player::seekTo, + accent = playerColors?.accent ?: MaterialTheme.colorScheme.primary + ) + }, + sidebar = { + PlayerChromeTheme(playerColors, useArtBackground = true) { + NowPlayingSidebar( track = current?.toCover(), - playing = playing, - positionMs = position, - durationMs = duration, - onPrev = session.player::previous, - onToggle = session.player::togglePlay, - onNext = session.player::next, - onSeek = session.player::seekTo + queue = queue.map { it.toRow(highlighted = it.id == current?.id) }, + history = history.map { it.toRow() }, + onQueueTrack = { row -> + session.player.playTrack( + queue.first { it.id == row.id }, + queue + ) + }, + onHistoryTrack = { row -> + val t = history.firstOrNull { it.id == row.id } ?: return@NowPlayingSidebar + session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) + } ) - }, - sidebar = { - PlayerChromeTheme(playerColors, useArtBackground = true) { - NowPlayingSidebar( - track = current?.toCover(), - queue = queue.map { it.toRow(highlighted = it.id == current?.id) }, - history = history.map { it.toRow() }, - onQueueTrack = { row -> - session.player.playTrack( - queue.first { it.id == row.id }, - queue - ) - }, - onHistoryTrack = { row -> - val t = history.firstOrNull { it.id == row.id } ?: return@NowPlayingSidebar - session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) - } - ) - } } - ) { - when (val r = route) { - Route.Library -> LibraryGrid( - albums = albums, - status = engineMessage ?: status, - roots = session.dirs.defaultMusicRoots, - onOpen = { route = Route.Album(it) } - ) - is Route.Album -> { - val live = albums.firstOrNull { it.id == r.album.id } ?: r.album - val albumTracks = live.tracks.mapNotNull { row -> - tracks.firstOrNull { it.id == row.id } - } - AlbumPage( - album = live.copy( - tracks = live.tracks.map { - it.copy(highlighted = it.id == current?.id) - } - ), - playing = playing && albumTracks.any { it.id == current?.id }, - onBack = { route = Route.Library }, - onPlay = { - if (albumTracks.isNotEmpty()) session.player.play(albumTracks, 0) - }, - onTrack = { i -> - if (i in albumTracks.indices) session.player.play(albumTracks, i) - } - ) + } + ) { + when (val r = route) { + Route.Library -> LibraryGrid( + albums = albums.filter { + nav != DesktopNav.Search || query.isBlank() || + it.title.contains(query, true) || + it.artist.contains(query, true) + }, + status = engineMessage ?: status, + roots = session.dirs.defaultMusicRoots, + showSearch = nav == DesktopNav.Search, + query = query, + onQuery = { query = it }, + onOpen = { route = Route.Album(it) } + ) + is Route.Album -> { + val live = albums.firstOrNull { it.id == r.album.id } ?: r.album + val albumTracks = live.tracks.mapNotNull { row -> + tracks.firstOrNull { it.id == row.id } } + AlbumPage( + album = live.copy( + tracks = live.tracks.map { + it.copy(highlighted = it.id == current?.id) + } + ), + playing = playing && albumTracks.any { it.id == current?.id }, + onBack = { route = Route.Library }, + onPlay = { + if (albumTracks.isNotEmpty()) session.player.play(albumTracks, 0) + }, + onTrack = { i -> + if (i in albumTracks.indices) session.player.play(albumTracks, i) + } + ) } } } + if (showSettings) { + DesktopSettingsDialog(onDismiss = { showSettings = false }) + } } } @@ -134,20 +151,39 @@ private fun LibraryGrid( albums: List, status: String, roots: List, + showSearch: Boolean, + query: String, + onQuery: (String) -> Unit, onOpen: (AlbumPageModel) -> Unit ) { - Column(Modifier.fillMaxSize().padding(horizontal = 20.dp, vertical = 12.dp)) { - Text("Library", style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground) + Column(Modifier.fillMaxSize().padding(horizontal = 20.dp, vertical = 16.dp)) { Text( - status, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), - modifier = Modifier.padding(bottom = 12.dp) + if (showSearch) "Search" else "Library", + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onBackground ) + if (showSearch) { + OutlinedTextField( + value = query, + onValueChange = onQuery, + modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), + placeholder = { Text("Albums and artists") }, + singleLine = true, + shape = MaterialTheme.shapes.large + ) + } else { + Text( + status, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(bottom = 12.dp, top = 4.dp) + ) + } if (albums.isEmpty()) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text( - "Nothing in the default music folders yet.\n" + + if (showSearch) "Nothing matches." + else "Nothing in the default music folders yet.\n" + roots.joinToString("\n").ifBlank { "Add files to Music, then restart." }, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) @@ -163,4 +199,4 @@ private fun LibraryGrid( } } } -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2fe15ff2..ded124e2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ koin = "4.2.2" coroutines = "1.11.0" palette = "1.0.0" room = "2.8.4" +sqlite = "2.6.2" ksp = "2.3.11" serialization = "1.11.0" jaudiotagger = "2.2.7" @@ -73,6 +74,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } +androidx-sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled", version.ref = "sqlite" } jaudiotagger = { group = "com.github.goxr3plus", name = "jaudiotagger", version.ref = "jaudiotagger" } @@ -110,5 +112,6 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +androidx-room = { id = "androidx.room", version.ref = "room" } aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibraries" } aboutlibraries-android = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutLibraries" } From 871cc3884a2e94e171a1337d80205e8b00f5892a Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:02:31 +0000 Subject: [PATCH 09/69] Wire Android to :components and finish the desktop shell :app now depends on :components so AlbumArtBackdrop and theme types compile. Desktop gets the bento layout, resizable sidebars, extra local folders, and Jellyfin/Subsonic sources. Phone nav is Home / My Stuff / Search with a floating art-tinted mini player. --- app/build.gradle.kts | 1 + .../capital/yuri/yuriplayer/DeviceUiTest.kt | 12 +- .../yuriplayer/activities/MainActivity.kt | 124 ++++-- .../activities/ui/BrowseAllLibraryScreen.kt | 58 +++ .../activities/ui/HomeFeedScreen.kt | 275 +++++++++++++ .../yuriplayer/activities/ui/MyStuffScreen.kt | 166 ++------ .../activities/ui/NowPlayingPreview.kt | 41 +- .../activities/ui/SettingsScreen.kt | 71 +++- .../yuriplayer/activities/ui/theme/Theme.kt | 40 +- .../capital/yuri/yuriplayer/data/HomeRowId.kt | 9 + .../yuri/yuriplayer/data/LibrarySettings.kt | 37 ++ .../capital/yuri/yuriplayer/ui/TestTags.kt | 2 + .../components/layout/DesktopBentoLayout.kt | 344 ++++++++++++++++ .../components/layout/SpotifyShell.kt | 168 -------- .../components/player/BottomPlayerBar.kt | 188 +++++++-- .../components/player/NowPlayingSidebar.kt | 65 ++- .../components/theme/AccentPicker.kt | 76 ++++ .../components/theme/ThemeCatalog.kt | 115 ++++++ .../yuriplayer/components/theme/YuriTheme.kt | 11 +- core/build.gradle.kts | 3 + .../yuri/yuriplayer/core/http/UrlDsl.kt | 35 ++ .../yuriplayer/core/player/PlaybackEngine.kt | 3 + .../yuriplayer/core/player/PlayerSession.kt | 52 ++- .../yuri/yuriplayer/core/player/RepeatMode.kt | 9 + .../yuriplayer/core/source/RemoteAccount.kt | 29 ++ .../yuriplayer/core/library/CoverPixels.kt | 16 +- .../core/library/LocalLibraryScanner.kt | 83 +++- .../yuriplayer/core/source/JellyfinCatalog.kt | 175 ++++++++ .../core/source/LibrarySourceStore.kt | 85 ++++ .../yuriplayer/core/source/SubsonicCatalog.kt | 219 ++++++++++ .../yuriplayer/desktop/DesktopCollection.kt | 83 ++++ .../yuriplayer/desktop/DesktopLayoutStore.kt | 61 +++ .../yuri/yuriplayer/desktop/DesktopSession.kt | 130 +++++- .../yuriplayer/desktop/DesktopThemeStore.kt | 50 +++ .../capital/yuri/yuriplayer/desktop/Main.kt | 8 + .../desktop/player/VlcjPlaybackEngine.kt | 4 + .../yuriplayer/desktop/ui/DesktopSettings.kt | 201 ++++++++- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 388 +++++++++++++++--- gradle/libs.versions.toml | 1 + 39 files changed, 2872 insertions(+), 566 deletions(-) create mode 100644 app/src/main/java/capital/yuri/yuriplayer/activities/ui/BrowseAllLibraryScreen.kt create mode 100644 app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt create mode 100644 app/src/main/java/capital/yuri/yuriplayer/data/HomeRowId.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt delete mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/SpotifyShell.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/AccentPicker.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/ThemeCatalog.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/http/UrlDsl.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/RepeatMode.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/source/RemoteAccount.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/LibrarySourceStore.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopLayoutStore.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopThemeStore.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9b73c467..aa4804ce 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -238,6 +238,7 @@ tasks.register("buildFfmpeg") { dependencies { implementation(project(":core")) + implementation(project(":components")) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.activity.compose) implementation(libs.androidx.appcompat) diff --git a/app/src/androidTest/java/capital/yuri/yuriplayer/DeviceUiTest.kt b/app/src/androidTest/java/capital/yuri/yuriplayer/DeviceUiTest.kt index acc3f4a8..c20b895e 100644 --- a/app/src/androidTest/java/capital/yuri/yuriplayer/DeviceUiTest.kt +++ b/app/src/androidTest/java/capital/yuri/yuriplayer/DeviceUiTest.kt @@ -37,19 +37,19 @@ class DeviceUiTest { @Test fun tabsAndCatalogOpen() { - rule.waitForTag(TestTags.TAB_MY_STUFF) + rule.waitForTag(TestTags.TAB_HOME) + rule.onNodeWithTag(TestTags.TAB_HOME).assertIsDisplayed() rule.onNodeWithTag(TestTags.TAB_MY_STUFF).assertIsDisplayed() - rule.onNodeWithTag(TestTags.TAB_EXPLORE).assertIsDisplayed() + rule.onNodeWithTag(TestTags.TAB_SEARCH).assertIsDisplayed() rule.onNodeWithTag(TestTags.SETTINGS).assertIsDisplayed() - rule.onNodeWithTag(TestTags.MYSTUFF_CATALOG).performClick() + rule.onNodeWithTag(TestTags.TAB_MY_STUFF).performClick() rule.waitForTag(TestTags.CATALOG_TITLE) rule.onNodeWithTag(TestTags.CATALOG_TITLE).assertTextContains("Catalog") - rule.onNodeWithTag(TestTags.TAB_EXPLORE).performClick() - rule.onNodeWithTag(TestTags.TAB_MY_STUFF).performClick() + rule.onNodeWithTag(TestTags.TAB_SEARCH).performClick() + rule.onNodeWithTag(TestTags.TAB_HOME).performClick() rule.onNodeWithTag(TestTags.MYSTUFF_PINS).assertIsDisplayed() - rule.onNodeWithTag(TestTags.MYSTUFF_PLAYLISTS).assertIsDisplayed() } @Test diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt index 0e41f37e..20d48596 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt @@ -24,23 +24,32 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Search import androidx.compose.material3.AlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -57,7 +66,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp @@ -73,6 +81,7 @@ import capital.yuri.yuriplayer.activities.ui.EditAlbumMetadataScreen import capital.yuri.yuriplayer.activities.ui.EditSongMetadataScreen import capital.yuri.yuriplayer.activities.ui.ExploreScanMenu import capital.yuri.yuriplayer.activities.ui.ExploreScreen +import capital.yuri.yuriplayer.activities.ui.HomeFeedScreen import capital.yuri.yuriplayer.activities.ui.LocalAlbumNav import capital.yuri.yuriplayer.activities.ui.LocalArtistNav import capital.yuri.yuriplayer.activities.ui.LocalPlaylistNav @@ -298,9 +307,10 @@ class MainActivity : ComponentActivity() { } } -private enum class TopTab(val label: String, val icon: ImageVector) { - MyStuff("My Stuff", Icons.Default.Favorite), - Explore("Explore", Icons.Default.Search) +private enum class TopTab(val label: String) { + Home("Home"), + MyStuff("My Stuff"), + Search("Search") } private sealed class DetailRoute { @@ -390,7 +400,7 @@ fun YuriApp( ) } - var topTab by remember { mutableStateOf(TopTab.MyStuff) } + var topTab by remember { mutableStateOf(TopTab.Home) } var playerExpanded by remember { mutableStateOf(false) } var detailStack by remember { mutableStateOf>(emptyList()) } var npPlaylistSong by remember { mutableStateOf(null) } @@ -658,35 +668,9 @@ fun YuriApp( topBar = { if (detail == null) { TopAppBar( - title = { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = androidx.compose.ui.Alignment.CenterVertically - ) { - TopTab.entries.forEach { tab -> - val selected = topTab == tab - val tag = when (tab) { - TopTab.MyStuff -> TestTags.TAB_MY_STUFF - TopTab.Explore -> TestTags.TAB_EXPLORE - } - IconButton( - onClick = { topTab = tab }, - modifier = Modifier.testTag(tag) - ) { - Icon( - imageVector = tab.icon, - contentDescription = tab.label, - tint = if (selected) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.45f) - ) - } - } - } - }, + title = { Text(topTab.label) }, actions = { - if (topTab == TopTab.Explore) { - // TravelExplore dropdown: scan / pause / stop per source. - // Never auto-starts on tab enter — only explicit menu actions. + if (topTab == TopTab.Search) { ExploreScanMenu() } IconButton( @@ -700,14 +684,60 @@ fun YuriApp( } }, bottomBar = { - MiniPlayerBar( - song = currentSong, - playing = playing, - positionMs = positionMs, - durationMs = durationMs, - onToggle = { player.togglePlayPause() }, - onExpand = { playerExpanded = true } - ) + Column( + Modifier + .fillMaxWidth() + .then(if (detail != null) Modifier.navigationBarsPadding() else Modifier) + ) { + MiniPlayerBar( + song = currentSong, + playing = playing, + positionMs = positionMs, + durationMs = durationMs, + onToggle = { player.togglePlayPause() }, + onExpand = { playerExpanded = true } + ) + if (detail == null) { + NavigationBar { + NavigationBarItem( + selected = topTab == TopTab.Home, + onClick = { topTab = TopTab.Home }, + modifier = Modifier.testTag(TestTags.TAB_HOME), + icon = { + Icon( + if (topTab == TopTab.Home) Icons.Filled.Home else Icons.Outlined.Home, + contentDescription = "Home" + ) + }, + label = { Text("Home") } + ) + NavigationBarItem( + selected = topTab == TopTab.MyStuff, + onClick = { topTab = TopTab.MyStuff }, + modifier = Modifier.testTag(TestTags.TAB_MY_STUFF), + icon = { + Icon( + if (topTab == TopTab.MyStuff) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = "My Stuff" + ) + }, + label = { Text("My Stuff") } + ) + NavigationBarItem( + selected = topTab == TopTab.Search, + onClick = { topTab = TopTab.Search }, + modifier = Modifier.testTag(TestTags.TAB_SEARCH), + icon = { + Icon( + if (topTab == TopTab.Search) Icons.Filled.Search else Icons.Outlined.Search, + contentDescription = "Search" + ) + }, + label = { Text("Search") } + ) + } + } + } } ) { innerPadding -> val contentPadding = if (edgeToEdgeDetail) { @@ -906,6 +936,16 @@ fun YuriApp( } is DetailRoute.Settings -> SettingsScreen(onBack = { popDetail() }) null -> when (topTab) { + TopTab.Home -> HomeFeedScreen( + library = library, + onPlay = { songs, index -> player.playSource(songs, index) }, + onOpenAlbum = { openAlbumResolved(it) }, + onOpenArtist = { openArtistResolved(it) }, + onOpenPlaylist = { pl: Playlist -> + pushDetail(DetailRoute.Playlist(pl.id)) + }, + onOpenSongAlbum = { openAlbumForSong(it) } + ) TopTab.MyStuff -> MyStuffScreen( library = library, nowPlaying = currentSong, @@ -919,7 +959,7 @@ fun YuriApp( }, onOpenSongAlbum = { openAlbumForSong(it) } ) - TopTab.Explore -> ExploreScreen( + TopTab.Search -> ExploreScreen( nowPlaying = currentSong, isPlaybackActive = playing, onPlay = { songs, index -> player.playSource(songs, index) }, diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/BrowseAllLibraryScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/BrowseAllLibraryScreen.kt new file mode 100644 index 00000000..058e9256 --- /dev/null +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/BrowseAllLibraryScreen.kt @@ -0,0 +1,58 @@ +package capital.yuri.yuriplayer.activities.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import capital.yuri.yuriplayer.data.AlbumItem +import capital.yuri.yuriplayer.data.ArtistItem +import capital.yuri.yuriplayer.data.LibraryIndex +import capital.yuri.yuriplayer.data.Playlist +import capital.yuri.yuriplayer.data.PlaylistRepository +import capital.yuri.yuriplayer.data.Song +import org.koin.compose.koinInject + +@Composable +fun BrowseAllLibraryScreen( + library: LibraryIndex, + nowPlaying: Song?, + onBack: () -> Unit, + onPlay: (List, Int) -> Unit, + onAddToQueue: (Song) -> Unit, + onOpenAlbum: (AlbumItem) -> Unit, + onOpenArtist: (ArtistItem) -> Unit, + onOpenPlaylist: (Playlist) -> Unit, + onOpenSongAlbum: (Song) -> Unit +) { + BackHandler(onBack = onBack) + val playlistsRepo: PlaylistRepository = koinInject() + val songs by library.songs.collectAsState() + val playlists by playlistsRepo.observePlaylistsResolved().collectAsState(initial = emptyList()) + val albums = remember(songs) { library.albums(taggedOnly = false) } + val artists = remember(songs) { library.artists(taggedOnly = false) } + + Column(Modifier.fillMaxSize().statusBarsPadding()) { + SettingsTopBar(title = "All", onBack = onBack) + MediaBrowser( + lookup = mediaBrowserLookup( + songs = songs, + albums = albums, + artists = artists, + playlists = playlists + ), + nowPlaying = nowPlaying, + onPlay = onPlay, + onAddToQueue = onAddToQueue, + onOpenAlbum = onOpenAlbum, + onOpenArtist = onOpenArtist, + onOpenPlaylist = onOpenPlaylist, + onSongClick = onOpenSongAlbum + ) + } +} diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt new file mode 100644 index 00000000..1b18aa49 --- /dev/null +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt @@ -0,0 +1,275 @@ +package capital.yuri.yuriplayer.activities.ui + +import android.widget.Toast +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.data.AlbumItem +import capital.yuri.yuriplayer.data.ArtistItem +import capital.yuri.yuriplayer.data.CatalogRepository +import capital.yuri.yuriplayer.data.HomeRowId +import capital.yuri.yuriplayer.data.LibraryIndex +import capital.yuri.yuriplayer.data.LibrarySettings +import capital.yuri.yuriplayer.data.MyStuffPinStore +import capital.yuri.yuriplayer.data.Playlist +import capital.yuri.yuriplayer.data.PlaylistRepository +import capital.yuri.yuriplayer.data.Song +import capital.yuri.yuriplayer.data.StuffPinKind +import capital.yuri.yuriplayer.player.PlaybackHistoryStore +import capital.yuri.yuriplayer.player.PlayerController +import capital.yuri.yuriplayer.ui.TestTags +import kotlinx.coroutines.launch +import org.koin.compose.koinInject + +@Composable +fun HomeFeedScreen( + library: LibraryIndex, + onPlay: (List, Int) -> Unit, + onOpenAlbum: (AlbumItem) -> Unit, + onOpenArtist: (ArtistItem) -> Unit, + onOpenPlaylist: (Playlist) -> Unit, + onOpenSongAlbum: (Song) -> Unit +) { + val pinStore: MyStuffPinStore = koinInject() + val playlistsRepo: PlaylistRepository = koinInject() + val catalog: CatalogRepository = koinInject() + val player: PlayerController = koinInject() + val history: PlaybackHistoryStore = koinInject() + val settings: LibrarySettings = koinInject() + val pins by pinStore.pins.collectAsState() + val entries by pinStore.entries.collectAsState() + val playlists by playlistsRepo.observePlaylistsResolved().collectAsState(initial = emptyList()) + val allSongs by library.songs.collectAsState() + val historyEntries by history.entries.collectAsState() + val colorRev by settings.colorPrefsRevision.collectAsState() + val rows = remember(colorRev) { settings.enabledHomeRows() } + val scope = rememberCoroutineScope() + val context = LocalContext.current + var showAddPin by remember { mutableStateOf(false) } + + LaunchedEffect(playlistsRepo) { + playlistsRepo.observePlaylistsResolved().collect { list -> + pinStore.pruneMissingPlaylists(list.map { it.id }.toSet()) + } + } + + val stuffAlbums = remember(entries, allSongs) { + library.albums(taggedOnly = false).filter { album -> + entries.any { it.kind == StuffPinKind.ALBUM && it.title.equals(album.name, true) } || + album.songs.any { song -> entries.any { it.kind == StuffPinKind.SONG && it.id == song.songKey } } + }.ifEmpty { library.albums(taggedOnly = false) } + } + val stuffArtists = remember(entries) { + library.artists(taggedOnly = false).filter { artist -> + entries.any { it.kind == StuffPinKind.ARTIST && it.title.equals(artist.name, true) } + }.ifEmpty { library.artists(taggedOnly = false) } + } + + LazyColumn(Modifier.fillMaxSize().testTag(TestTags.MYSTUFF_PINS)) { + item { + Box(Modifier.heightIn(max = 380.dp)) { + MyStuffPinsHost( + pins = pins, + library = library, + playlists = playlists, + allSongs = allSongs, + onOpenPin = { pin -> + scope.launch { + openPin( + pin, library, playlists, allSongs, catalog, + onOpenAlbum, onOpenArtist, onOpenPlaylist, onOpenSongAlbum, context + ) + } + }, + onUnpin = { pinStore.unpin(it) }, + onAddPinSlot = { showAddPin = true }, + onPlayAll = { + scope.launch { + val songs = resolveCollectionSongs(entries, library, playlistsRepo) + if (songs.isEmpty()) { + Toast.makeText(context, "Nothing in My Stuff yet", Toast.LENGTH_SHORT).show() + } else { + player.startPlaylistRadio(songs, "My Stuff") + } + } + } + ) + } + } + if (HomeRowId.RECENTS in rows && historyEntries.isNotEmpty()) { + item { + HomeRow(title = "Recents") { + historyEntries.take(16).forEach { entry -> + CoverTile( + title = entry.song.displayTitle, + subtitle = entry.song.displayArtist, + onClick = { onOpenSongAlbum(entry.song) }, + art = { AlbumArt(song = entry.song, size = 112.dp, corner = 8.dp) } + ) + } + } + } + } + if (HomeRowId.RANDOM_ALBUMS in rows && stuffAlbums.isNotEmpty()) { + item { + HomeRow(title = "Random albums") { + stuffAlbums.shuffled().take(10).forEach { album -> + CoverTile( + title = album.name, + subtitle = album.artist, + onClick = { onOpenAlbum(album) }, + art = { AlbumArt(song = album.songs.firstOrNull(), size = 112.dp, corner = 8.dp) } + ) + } + } + } + } + if (HomeRowId.RANDOM_ARTISTS in rows && stuffArtists.isNotEmpty()) { + item { + HomeRow(title = "Random artists") { + stuffArtists.shuffled().take(10).forEach { artist -> + CoverTile( + title = artist.name, + subtitle = "${artist.albumCount} albums", + onClick = { onOpenArtist(artist) }, + art = { ArtistArt(artistName = artist.name, size = 112.dp, circular = true) } + ) + } + } + } + } + if (HomeRowId.RANDOM_PLAYLISTS in rows && playlists.isNotEmpty()) { + item { + HomeRow(title = "Random playlists") { + playlists.shuffled().take(10).forEach { pl -> + CoverTile( + title = pl.name, + subtitle = "Playlist", + onClick = { onOpenPlaylist(pl) }, + art = { AlbumArt(song = null, size = 112.dp, corner = 8.dp) } + ) + } + } + } + } + if (HomeRowId.RANDOM_SONGS in rows && allSongs.isNotEmpty()) { + item { + HomeRow(title = "Random songs") { + allSongs.shuffled().take(12).forEach { song -> + CoverTile( + title = song.displayTitle, + subtitle = song.displayArtist, + onClick = { onPlay(listOf(song) + allSongs.shuffled().take(20), 0) }, + art = { AlbumArt(song = song, size = 112.dp, corner = 8.dp) } + ) + } + } + } + } + item { Spacer(Modifier.height(24.dp)) } + } + + if (showAddPin) { + AddPinFromCollectionSheet( + entries = entries, + playlists = playlists, + alreadyPinned = pins.map { it.key }.toSet(), + library = library, + allSongs = allSongs, + onDismiss = { showAddPin = false }, + onPick = { pin -> + pinStore.pin(pin) + showAddPin = false + } + ) + } +} + +@Composable +private fun HomeRow( + title: String, + content: @Composable Row.() -> Unit +) { + Column(Modifier.fillMaxWidth().padding(top = 8.dp)) { + Text( + title, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + Row( + Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()) + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + content = content + ) + } +} + +@Composable +private fun CoverTile( + title: String, + subtitle: String, + onClick: () -> Unit, + art: @Composable () -> Unit +) { + Column( + Modifier + .width(124.dp) + .clip(RoundedCornerShape(12.dp)) + .clickable(onClick = onClick) + ) { + art() + Text( + title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(top = 6.dp) + ) + Text( + subtitle, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } +} diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffScreen.kt index 2d5121fb..5ba304f8 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffScreen.kt @@ -80,8 +80,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.koin.compose.koinInject -private enum class MyStuffTab { Pins, Collection, Playlists } - @OptIn(ExperimentalMaterial3Api::class) @Composable fun MyStuffScreen( @@ -97,18 +95,10 @@ fun MyStuffScreen( ) { val pinStore: MyStuffPinStore = koinInject() val playlistsRepo: PlaylistRepository = koinInject() - val catalog: CatalogRepository = koinInject() - val player: PlayerController = koinInject() - val pins by pinStore.pins.collectAsState() val entries by pinStore.entries.collectAsState() val playlists by playlistsRepo.observePlaylistsResolved().collectAsState(initial = emptyList()) - val allSongs by library.songs.collectAsState() - val scope = rememberCoroutineScope() - val context = LocalContext.current - var tab by remember { mutableStateOf(MyStuffTab.Pins) } - var showAddPin by remember { mutableStateOf(false) } - var showCreatePlaylist by remember { mutableStateOf(false) } + var showBrowseAll by remember { mutableStateOf(false) } LaunchedEffect(playlistsRepo) { playlistsRepo.observePlaylistsResolved().collect { list -> @@ -116,121 +106,47 @@ fun MyStuffScreen( } } - Column(modifier = Modifier.fillMaxSize()) { - Box(modifier = Modifier.weight(1f)) { - when (tab) { - MyStuffTab.Pins -> MyStuffPinsHost( - pins = pins, - library = library, - playlists = playlists, - allSongs = allSongs, - onOpenPin = { pin -> - scope.launch { - openPin( - pin, library, playlists, allSongs, catalog, - onOpenAlbum, onOpenArtist, onOpenPlaylist, onOpenSongAlbum, context - ) - } - }, - onUnpin = { pinStore.unpin(it) }, - onAddPinSlot = { showAddPin = true }, - onPlayAll = { - scope.launch { - val songs = resolveCollectionSongs(entries, library, playlistsRepo) - if (songs.isEmpty()) { - Toast.makeText(context, "Nothing in My Stuff yet", Toast.LENGTH_SHORT).show() - } else { - player.startPlaylistRadio(songs, "My Stuff") - Toast.makeText(context, "Radio · My Stuff", Toast.LENGTH_SHORT).show() - } - } - } - ) - MyStuffTab.Collection -> MyStuffCatalogTab( - entries = entries.filter { it.kind != StuffPinKind.PLAYLIST }, - library = library, - playlists = playlists, - nowPlaying = nowPlaying, - isPlaybackActive = isPlaybackActive, - onOpenAlbum = onOpenAlbum, - onOpenArtist = onOpenArtist, - onOpenPlaylist = onOpenPlaylist, - onOpenSongAlbum = onOpenSongAlbum, - onPlay = onPlay, - onAddToQueue = onAddToQueue - ) - MyStuffTab.Playlists -> MyStuffPlaylistsList( - playlists = playlists, - onOpen = onOpenPlaylist, - onCreate = { showCreatePlaylist = true } - ) - } - } - - NavigationBar(windowInsets = WindowInsets(0, 0, 0, 0)) { - NavigationBarItem( - selected = tab == MyStuffTab.Pins, - onClick = { tab = MyStuffTab.Pins }, - modifier = Modifier.testTag(TestTags.MYSTUFF_PINS), - icon = { - Icon( - if (tab == MyStuffTab.Pins) Icons.Filled.PushPin else Icons.Outlined.PushPin, - contentDescription = null - ) - }, - label = { Text("Pins") } - ) - NavigationBarItem( - selected = tab == MyStuffTab.Collection, - onClick = { tab = MyStuffTab.Collection }, - modifier = Modifier.testTag(TestTags.MYSTUFF_CATALOG), - icon = { - Icon( - if (tab == MyStuffTab.Collection) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, - contentDescription = null - ) - }, - label = { Text("Catalog") } - ) - NavigationBarItem( - selected = tab == MyStuffTab.Playlists, - onClick = { tab = MyStuffTab.Playlists }, - modifier = Modifier.testTag(TestTags.MYSTUFF_PLAYLISTS), - icon = { - Icon( - if (tab == MyStuffTab.Playlists) Icons.Filled.QueueMusic else Icons.Outlined.QueueMusic, - contentDescription = null - ) - }, - label = { Text("Playlists") } - ) - } - } - - if (showAddPin) { - AddPinFromCollectionSheet( - entries = entries, - playlists = playlists, - alreadyPinned = pins.map { it.key }.toSet(), + if (showBrowseAll) { + BrowseAllLibraryScreen( library = library, - allSongs = allSongs, - onDismiss = { showAddPin = false }, - onPick = { pin -> - pinStore.pin(pin) - showAddPin = false - Toast.makeText(context, "Pinned ${pin.title}", Toast.LENGTH_SHORT).show() - } + nowPlaying = nowPlaying, + onBack = { showBrowseAll = false }, + onPlay = onPlay, + onAddToQueue = onAddToQueue, + onOpenAlbum = onOpenAlbum, + onOpenArtist = onOpenArtist, + onOpenPlaylist = onOpenPlaylist, + onOpenSongAlbum = onOpenSongAlbum ) + return } - if (showCreatePlaylist) { - CreatePlaylistSheet( - onDismiss = { showCreatePlaylist = false }, - onCreated = { - showCreatePlaylist = false - tab = MyStuffTab.Playlists - } - ) + Column(modifier = Modifier.fillMaxSize().testTag(TestTags.MYSTUFF_CATALOG)) { + Box(modifier = Modifier.weight(1f)) { + MyStuffCatalogTab( + entries = entries.filter { it.kind != StuffPinKind.PLAYLIST }, + library = library, + playlists = playlists, + nowPlaying = nowPlaying, + isPlaybackActive = isPlaybackActive, + onOpenAlbum = onOpenAlbum, + onOpenArtist = onOpenArtist, + onOpenPlaylist = onOpenPlaylist, + onOpenSongAlbum = onOpenSongAlbum, + onPlay = onPlay, + onAddToQueue = onAddToQueue + ) + } + androidx.compose.material3.Button( + onClick = { showBrowseAll = true }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp) + .height(52.dp), + shape = RoundedCornerShape(28.dp) + ) { + Text("All") + } } } @@ -438,7 +354,7 @@ private fun BrowseEntryRow( @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun AddPinFromCollectionSheet( +internal fun AddPinFromCollectionSheet( entries: List, playlists: List, alreadyPinned: Set, @@ -542,7 +458,7 @@ private fun AddPinFromCollectionSheet( } } -private suspend fun openPin( +internal suspend fun openPin( pin: StuffPin, library: LibraryIndex, playlists: List, @@ -583,7 +499,7 @@ private suspend fun openPin( } } -private suspend fun resolveCollectionSongs( +internal suspend fun resolveCollectionSongs( entries: List, library: LibraryIndex, playlistsRepo: PlaylistRepository diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/NowPlayingPreview.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/NowPlayingPreview.kt index 9d09f88d..e484efda 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/NowPlayingPreview.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/NowPlayingPreview.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -37,11 +36,9 @@ import capital.yuri.yuriplayer.ui.TestTags import org.koin.compose.koinInject /** - * Compact now-playing strip (bottom of library / detail pages). - * - * Uses the **app default background** — not album art colors — so it stays - * neutral across pages. Accents (progress + play) still come from current art. - * Full Now Playing screen keeps the art-derived background. + * Compact now-playing chip. Background follows the current cover; it sits + * in the scaffold bottom bar so lists pad above it, visually floating over + * the tab bar. */ @Composable fun NowPlayingPreview( @@ -53,9 +50,8 @@ fun NowPlayingPreview( onOpen: () -> Unit, modifier: Modifier = Modifier, enableSwipeUp: Boolean = false, - edgeToEdgeBottom: Boolean = false, - tonalElevation: androidx.compose.ui.unit.Dp = 0.dp, - shadowElevation: androidx.compose.ui.unit.Dp = 4.dp + tonalElevation: androidx.compose.ui.unit.Dp = 2.dp, + shadowElevation: androidx.compose.ui.unit.Dp = 8.dp ) { val themeStore: PlayerThemeStore = koinInject() val ambient = MaterialTheme.colorScheme @@ -69,15 +65,19 @@ fun NowPlayingPreview( peekPrev?.songKey == song.songKey -> peekPrev else -> null } - val accent = matched?.colors?.accent ?: ambient.primary - val onAccent = matched?.colors?.onAccent ?: ambient.onPrimary - val trackInactive = ambient.onBackground.copy(alpha = 0.2f) + val colors = matched?.colors + val container = colors?.container ?: ambient.surfaceVariant + val onContainer = colors?.onContainer ?: ambient.onSurface + val accent = colors?.accent ?: ambient.primary + val onAccent = colors?.onAccent ?: ambient.onPrimary + val trackInactive = onContainer.copy(alpha = 0.22f) Surface( tonalElevation = tonalElevation, shadowElevation = shadowElevation, - color = ambient.background, - contentColor = ambient.onBackground, + shape = RoundedCornerShape(16.dp), + color = container, + contentColor = onContainer, modifier = modifier .fillMaxWidth() .testTag(TestTags.MINI_PLAYER) @@ -91,9 +91,7 @@ fun NowPlayingPreview( } else Modifier ) ) { - Column( - modifier = if (edgeToEdgeBottom) Modifier.navigationBarsPadding() else Modifier - ) { + Column { PlaybackProgress( positionMs = positionMs, durationMs = durationMs, @@ -124,13 +122,13 @@ fun NowPlayingPreview( MarqueeText( text = song?.displayTitle ?: "Not playing", style = MaterialTheme.typography.bodyMedium, - color = ambient.onBackground, + color = onContainer, modifier = Modifier.testTag(TestTags.MINI_TITLE) ) MarqueeText( text = song?.displayArtist ?: "", style = MaterialTheme.typography.bodySmall, - color = ambient.onBackground.copy(alpha = 0.6f), + color = onContainer.copy(alpha = 0.65f), modifier = Modifier.testTag(TestTags.MINI_ARTIST) ) } @@ -163,7 +161,8 @@ fun MiniPlayerBar( positionMs: Long, durationMs: Long, onToggle: () -> Unit, - onExpand: () -> Unit + onExpand: () -> Unit, + modifier: Modifier = Modifier ) { NowPlayingPreview( song = song, @@ -173,6 +172,6 @@ fun MiniPlayerBar( onToggle = onToggle, onOpen = onExpand, enableSwipeUp = true, - edgeToEdgeBottom = true + modifier = modifier.padding(start = 8.dp, end = 8.dp, top = 4.dp, bottom = 8.dp) ) } diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt index ec90c8e2..bcef31d8 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt @@ -41,6 +41,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.BuildConfig +import capital.yuri.yuriplayer.components.theme.AccentPicker +import capital.yuri.yuriplayer.components.theme.ThemeModePicker +import capital.yuri.yuriplayer.data.HomeRowId import capital.yuri.yuriplayer.data.LibraryIndex import capital.yuri.yuriplayer.data.LibraryScanMode import capital.yuri.yuriplayer.data.LibrarySettings @@ -62,6 +65,7 @@ private sealed class SettingsPage { data object StreamingQuality : SettingsPage() data object PlaybackHistory : SettingsPage() data object Appearance : SettingsPage() + data object HomeLayout : SettingsPage() data object Sync : SettingsPage() data class Organize( val rootKey: String, @@ -103,6 +107,7 @@ fun SettingsScreen(onBack: () -> Unit) { onOpenStreamingQuality = { push(SettingsPage.StreamingQuality) }, onOpenPlaybackHistory = { push(SettingsPage.PlaybackHistory) }, onOpenAppearance = { push(SettingsPage.Appearance) }, + onOpenHomeLayout = { push(SettingsPage.HomeLayout) }, onOpenSync = { push(SettingsPage.Sync) }, onOpenLicenses = { push(SettingsPage.OpenSourceLicenses) }, onOpenVersion = { push(SettingsPage.VersionInfo) } @@ -123,6 +128,7 @@ fun SettingsScreen(onBack: () -> Unit) { SettingsPage.StreamingQuality -> StreamingQualitySettingsScreen(onBack = { pop() }) SettingsPage.PlaybackHistory -> PlaybackHistorySettingsScreen(onBack = { pop() }) SettingsPage.Appearance -> AppearanceSettingsScreen(onBack = { pop() }) + SettingsPage.HomeLayout -> HomeLayoutSettingsScreen(onBack = { pop() }) SettingsPage.Sync -> SyncSettingsScreen(onBack = { pop() }) is SettingsPage.Organize -> OrganizeLayoutScreen( rootKey = p.rootKey, @@ -148,6 +154,7 @@ private fun SettingsHubScreen( onOpenStreamingQuality: () -> Unit, onOpenPlaybackHistory: () -> Unit, onOpenAppearance: () -> Unit, + onOpenHomeLayout: () -> Unit, onOpenSync: () -> Unit, onOpenLicenses: () -> Unit, onOpenVersion: () -> Unit @@ -294,10 +301,16 @@ private fun SettingsHubScreen( SettingsGroup { SettingsNavRow( title = "Appearance", - subtitle = "Cover · ${coverVariant.displayName} · Header · ${bannerVariant.displayName}", + subtitle = "Theme, accent, cover colors", icon = Icons.Default.ColorLens, onClick = onOpenAppearance ) + SettingsNavRow( + title = "Home rows", + subtitle = "Recents, random albums, and more", + icon = Icons.Default.LibraryMusic, + onClick = onOpenHomeLayout + ) SettingsNavRow( title = "Library browser", subtitle = "Tabs, sort defaults", @@ -495,6 +508,8 @@ private fun AppearanceSettingsScreen(onBack: () -> Unit) { var coverVariant by remember { mutableStateOf(settings.getCoverColorVariant()) } var bannerVariant by remember { mutableStateOf(settings.getBannerColorVariant()) } var systemColors by remember { mutableStateOf(settings.useSystemColors()) } + var themeMode by remember { mutableStateOf(settings.getThemeMode()) } + var accentId by remember { mutableStateOf(settings.getAccentId()) } Column( modifier = Modifier @@ -505,15 +520,33 @@ private fun AppearanceSettingsScreen(onBack: () -> Unit) { SettingsTopBar(title = "Appearance", onBack = onBack) TextNote( - text = "On Android 12 and newer, the app chrome can follow your system " + - "palette. Playing artwork still tints now playing." + text = "Light, dark, or follow the system. Pick an accent for the Material 3 " + + "primary. On Android 12+, system colors use your wallpaper palette instead." ) - SettingsSectionTitle("System") + SettingsSectionTitle("Theme") SettingsGroup { + androidx.compose.foundation.layout.Box(Modifier.padding(16.dp)) { + ThemeModePicker( + mode = themeMode, + onMode = { + settings.setThemeMode(it) + themeMode = it + } + ) + } + androidx.compose.foundation.layout.Box(Modifier.padding(16.dp)) { + AccentPicker( + selectedId = accentId, + onSelect = { + settings.setAccentId(it) + accentId = it + } + ) + } SettingsSwitchRow( title = "Use system colors", - subtitle = "Material You on devices that support it. Desktop stays Yuri purple.", + subtitle = "Material You on Android 12 and newer. Accent still applies when this is off.", checked = systemColors, onCheckedChange = { settings.setUseSystemColors(it) @@ -551,7 +584,35 @@ private fun AppearanceSettingsScreen(onBack: () -> Unit) { ) } } + Spacer(modifier = Modifier.height(48.dp)) + } +} +@Composable +private fun HomeLayoutSettingsScreen(onBack: () -> Unit) { + val settings: LibrarySettings = koinInject() + var enabled by remember { mutableStateOf(settings.enabledHomeRows()) } + Column( + modifier = Modifier + .fillMaxSize() + .statusBarsPadding() + .verticalScroll(rememberScrollState()) + ) { + SettingsTopBar(title = "Home rows", onBack = onBack) + TextNote("Pins stay at the top. Turn rows on or off below.") + SettingsGroup { + HomeRowId.entries.forEach { row -> + SettingsSwitchRow( + title = row.label, + subtitle = row.subtitle, + checked = row in enabled, + onCheckedChange = { on -> + settings.setHomeRowEnabled(row, on) + enabled = settings.enabledHomeRows() + } + ) + } + } Spacer(modifier = Modifier.height(48.dp)) } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/theme/Theme.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/theme/Theme.kt index b359e83d..8a9c6a81 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/theme/Theme.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/theme/Theme.kt @@ -2,43 +2,26 @@ package capital.yuri.yuriplayer.activities.ui.theme import android.app.Activity import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat +import capital.yuri.yuriplayer.components.theme.ThemeChoice import capital.yuri.yuriplayer.components.theme.YuriShapes +import capital.yuri.yuriplayer.components.theme.colorScheme +import capital.yuri.yuriplayer.components.theme.isDark import capital.yuri.yuriplayer.data.LibrarySettings import org.koin.compose.koinInject -private val ColorBlackish = Color(0xFF1A1224) - -private val YuriDarkPurple = darkColorScheme( - primary = YuriPurple, - onPrimary = ColorBlackish, - primaryContainer = YuriPurpleDim, - onPrimaryContainer = YuriOnBg, - secondary = PurpleGrey80, - onSecondary = ColorBlackish, - tertiary = Pink80, - onTertiary = ColorBlackish, - background = YuriBg, - onBackground = YuriOnBg, - surface = YuriSurface, - onSurface = YuriOnBg, - surfaceVariant = YuriSurfaceVariant, - onSurfaceVariant = YuriMuted, - outline = YuriMuted.copy(alpha = 0.5f) -) - @Composable fun YuriPlayerTheme( content: @Composable () -> Unit @@ -46,14 +29,21 @@ fun YuriPlayerTheme( val settings: LibrarySettings = koinInject() val colorRev by settings.colorPrefsRevision.collectAsState() val context = LocalContext.current - val scheme = if (colorRev >= 0 && + val systemDark = isSystemInDarkTheme() + val choice = ThemeChoice( + mode = settings.getThemeMode(), + accentId = settings.getAccentId() + ) + val dark = choice.isDark(systemDark) + val dynamic = if (colorRev >= 0 && settings.useSystemColors() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ) { - dynamicDarkColorScheme(context) + if (dark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } else { - YuriDarkPurple + null } + val scheme = choice.colorScheme(systemDark, dynamic) val view = LocalView.current if (!view.isInEditMode) { diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/HomeRowId.kt b/app/src/main/java/capital/yuri/yuriplayer/data/HomeRowId.kt new file mode 100644 index 00000000..7b92d399 --- /dev/null +++ b/app/src/main/java/capital/yuri/yuriplayer/data/HomeRowId.kt @@ -0,0 +1,9 @@ +package capital.yuri.yuriplayer.data + +enum class HomeRowId(val id: String, val label: String, val subtitle: String) { + RECENTS("recents", "Recents", "Recently played, newest first"), + RANDOM_ALBUMS("random_albums", "Random albums", "A handful from My Stuff"), + RANDOM_SONGS("random_songs", "Random songs", "A handful from My Stuff"), + RANDOM_PLAYLISTS("random_playlists", "Random playlists", "A handful from My Stuff"), + RANDOM_ARTISTS("random_artists", "Random artists", "A handful from My Stuff") +} diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt b/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt index c4a48f1e..e9bc381a 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt @@ -2,6 +2,8 @@ package capital.yuri.yuriplayer.data import android.content.Context import android.os.Environment +import capital.yuri.yuriplayer.components.theme.AccentCatalog +import capital.yuri.yuriplayer.components.theme.ThemeMode import capital.yuri.yuriplayer.data.theme.ArtColorSurface import capital.yuri.yuriplayer.data.theme.ArtColorVariant import capital.yuri.yuriplayer.player.engine.PlaybackEngineId @@ -249,6 +251,38 @@ class LibrarySettings(context: Context) { ArtColorSurface.BANNER -> getBannerColorVariant() } + fun getThemeMode(): ThemeMode = ThemeMode.fromId(prefs.getString(KEY_THEME_MODE, ThemeMode.DARK.id)) + + fun setThemeMode(mode: ThemeMode) { + if (mode == getThemeMode()) return + prefs.edit().putString(KEY_THEME_MODE, mode.id).apply() + bumpColorPrefs() + } + + fun getAccentId(): String = prefs.getString(KEY_ACCENT, AccentCatalog.yuri.id) ?: AccentCatalog.yuri.id + + fun setAccentId(id: String) { + val next = AccentCatalog.byId(id).id + if (next == getAccentId()) return + prefs.edit().putString(KEY_ACCENT, next).apply() + bumpColorPrefs() + } + + fun enabledHomeRows(): Set { + val raw = prefs.getString(KEY_HOME_ROWS, null) + if (raw.isNullOrBlank()) return HomeRowId.entries.toSet() + val ids = raw.split(',').map { it.trim() }.toSet() + val parsed = HomeRowId.entries.filter { it.id in ids }.toSet() + return parsed.ifEmpty { HomeRowId.entries.toSet() } + } + + fun setHomeRowEnabled(id: HomeRowId, enabled: Boolean) { + val next = enabledHomeRows().toMutableSet() + if (enabled) next.add(id) else next.remove(id) + prefs.edit().putString(KEY_HOME_ROWS, next.joinToString(",") { it.id }).apply() + bumpColorPrefs() + } + private fun bumpColorPrefs() { _colorPrefsRevision.value = System.currentTimeMillis() } @@ -293,6 +327,9 @@ class LibrarySettings(context: Context) { private const val KEY_COVER_COLOR_VARIANT = "cover_color_variant" private const val KEY_BANNER_COLOR_VARIANT = "banner_color_variant" private const val KEY_SYSTEM_COLORS = "system_colors" + private const val KEY_THEME_MODE = "theme_mode" + private const val KEY_ACCENT = "accent_id" + private const val KEY_HOME_ROWS = "home_rows" val DEFAULT_ROOTS = listOf( "Music", diff --git a/app/src/main/java/capital/yuri/yuriplayer/ui/TestTags.kt b/app/src/main/java/capital/yuri/yuriplayer/ui/TestTags.kt index 3363005e..9308cc9d 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/ui/TestTags.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/ui/TestTags.kt @@ -5,8 +5,10 @@ package capital.yuri.yuriplayer.ui * Keep these stable — instrumented tests and Maestro flows both use them. */ object TestTags { + const val TAB_HOME = "tab_home" const val TAB_MY_STUFF = "tab_my_stuff" const val TAB_EXPLORE = "tab_explore" + const val TAB_SEARCH = "tab_search" const val SETTINGS = "settings" const val MYSTUFF_PINS = "mystuff_pins" diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt new file mode 100644 index 00000000..ad57b404 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt @@ -0,0 +1,344 @@ +package capital.yuri.yuriplayer.components.layout + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +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.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowForward +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.PushPin +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.LibraryMusic +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.art.CoverArt + +enum class DesktopNav { Home, Search } + +enum class LibraryFilter { Recents, Playlists, Albums, Artists } + +data class LibraryRailItem( + val id: String, + val title: String, + val subtitle: String, + val artworkUri: String?, + val circular: Boolean = false, + val pinned: Boolean = false +) + +@Composable +fun DesktopBentoLayout( + nav: DesktopNav, + searchQuery: String, + onSearchQuery: (String) -> Unit, + onHome: () -> Unit, + onBack: () -> Unit, + onForward: () -> Unit, + canBack: Boolean, + canForward: Boolean, + onSettings: () -> Unit, + libraryFilter: LibraryFilter, + onLibraryFilter: (LibraryFilter) -> Unit, + libraryItems: List, + selectedLibraryId: String?, + onLibraryItem: (LibraryRailItem) -> Unit, + bottomBar: @Composable () -> Unit, + sidebar: @Composable () -> Unit, + modifier: Modifier = Modifier, + showSidebar: Boolean = true, + leftFraction: Float = 0.22f, + rightFraction: Float = 0.26f, + onLeftFraction: (Float, persist: Boolean) -> Unit = { _, _ -> }, + onRightFraction: (Float, persist: Boolean) -> Unit = { _, _ -> }, + content: @Composable () -> Unit +) { + val bg = MaterialTheme.colorScheme.background + Column(modifier.fillMaxSize().background(bg)) { + TopChrome( + nav = nav, + searchQuery = searchQuery, + onSearchQuery = onSearchQuery, + onHome = onHome, + onBack = onBack, + onForward = onForward, + canBack = canBack, + canForward = canForward, + onSettings = onSettings + ) + BoxWithConstraints( + Modifier + .weight(1f) + .fillMaxWidth() + .padding(horizontal = 8.dp) + ) { + val totalPx = constraints.maxWidth.toFloat().coerceAtLeast(1f) + val gap = 8.dp + val handle = 6.dp + var leftF = leftFraction.coerceIn(0.12f, 0.4f) + var rightF = if (showSidebar) rightFraction.coerceIn(0.14f, 0.45f) else 0f + if (showSidebar && leftF + rightF > 0.72f) { + val scale = 0.72f / (leftF + rightF) + leftF *= scale + rightF *= scale + } + val leftW = maxWidth * leftF + val rightW = maxWidth * rightF + Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) { + LibraryRail( + filter = libraryFilter, + onFilter = onLibraryFilter, + items = libraryItems, + selectedId = selectedLibraryId, + onItem = onLibraryItem, + modifier = Modifier.width(leftW.coerceAtLeast(180.dp)).fillMaxHeight() + ) + ResizeHandle( + onDragPx = { dx -> + onLeftFraction((leftFraction + dx / totalPx).coerceIn(0.14f, 0.38f), false) + }, + onDragEnd = { onLeftFraction(leftFraction, true) } + ) + Surface( + modifier = Modifier.weight(1f).fillMaxHeight(), + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.surface + ) { + Box(Modifier.fillMaxSize()) { content() } + } + if (showSidebar) { + ResizeHandle( + onDragPx = { dx -> + onRightFraction((rightFraction - dx / totalPx).coerceIn(0.16f, 0.42f), false) + }, + onDragEnd = { onRightFraction(rightFraction, true) } + ) + Surface( + modifier = Modifier.width(rightW.coerceAtLeast(200.dp)).fillMaxHeight(), + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.surface + ) { + sidebar() + } + } + } + } + Box(Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp)) { bottomBar() } + } +} + +@Composable +private fun ResizeHandle( + onDragPx: (Float) -> Unit, + onDragEnd: () -> Unit +) { + Box( + Modifier + .width(6.dp) + .fillMaxHeight() + .pointerInput(Unit) { + detectHorizontalDragGestures( + onDragEnd = { onDragEnd() }, + onDragCancel = { onDragEnd() }, + onHorizontalDrag = { _, dragAmount -> onDragPx(dragAmount) } + ) + } + ) +} + +@Composable +private fun TopChrome( + nav: DesktopNav, + searchQuery: String, + onSearchQuery: (String) -> Unit, + onHome: () -> Unit, + onBack: () -> Unit, + onForward: () -> Unit, + canBack: Boolean, + canForward: Boolean, + onSettings: () -> Unit +) { + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton(onClick = onBack, enabled = canBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + IconButton(onClick = onForward, enabled = canForward) { + Icon(Icons.AutoMirrored.Filled.ArrowForward, contentDescription = "Forward") + } + FilledIconButton( + onClick = onHome, + shape = CircleShape, + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface + ) + ) { + Icon(Icons.Default.Home, contentDescription = "Home") + } + Spacer(Modifier.weight(1f)) + OutlinedTextField( + value = searchQuery, + onValueChange = onSearchQuery, + modifier = Modifier.widthIn(min = 280.dp, max = 520.dp).weight(1.2f).height(48.dp), + placeholder = { Text("What do you want to play?") }, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, + singleLine = true, + shape = CircleShape, + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.surface, + unfocusedContainerColor = MaterialTheme.colorScheme.surface + ) + ) + Spacer(Modifier.weight(1f)) + FilledIconButton( + onClick = onSettings, + shape = CircleShape, + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Icon(Icons.Default.Settings, contentDescription = "Settings") + } + } +} + +@Composable +private fun LibraryRail( + filter: LibraryFilter, + onFilter: (LibraryFilter) -> Unit, + items: List, + selectedId: String?, + onItem: (LibraryRailItem) -> Unit, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier, + shape = MaterialTheme.shapes.large, + color = MaterialTheme.colorScheme.surface + ) { + Column(Modifier.fillMaxSize().padding(12.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Outlined.LibraryMusic, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface + ) + Spacer(Modifier.width(8.dp)) + Text( + "Your library", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + } + Row( + Modifier.padding(top = 12.dp, bottom = 8.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + FilterChip( + selected = filter == LibraryFilter.Recents, + onClick = { onFilter(LibraryFilter.Recents) }, + label = { Text("Recents") } + ) + FilterChip( + selected = filter == LibraryFilter.Albums, + onClick = { onFilter(LibraryFilter.Albums) }, + label = { Text("Albums") } + ) + FilterChip( + selected = filter == LibraryFilter.Artists, + onClick = { onFilter(LibraryFilter.Artists) }, + label = { Text("Artists") } + ) + } + LazyColumn(Modifier.weight(1f)) { + items(items, key = { it.id }) { item -> + val selected = item.id == selectedId + Row( + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background( + if (selected) MaterialTheme.colorScheme.primary.copy(alpha = 0.14f) + else MaterialTheme.colorScheme.surface + ) + .clickable { onItem(item) } + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CoverArt( + model = item.artworkUri, + size = 48.dp, + corner = if (item.circular) 24.dp else 8.dp + ) + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f)) { + Text( + item.title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + item.subtitle, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + if (item.pinned) { + Icon( + Icons.Default.PushPin, + contentDescription = "Pinned", + modifier = Modifier.size(14.dp), + tint = MaterialTheme.colorScheme.primary + ) + } + } + } + } + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/SpotifyShell.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/SpotifyShell.kt deleted file mode 100644 index b87376bc..00000000 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/SpotifyShell.kt +++ /dev/null @@ -1,168 +0,0 @@ -package capital.yuri.yuriplayer.components.layout - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material.icons.outlined.LibraryMusic -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp - -enum class DesktopNav { Home, Search, Library } - -/** - * Spotify-shaped chrome: left bento nav, center stage, right now-playing/queue, - * continuous bottom bar. Material 3 Expressive corners, not Spotify's squircle. - */ -@Composable -fun SpotifyShell( - nav: DesktopNav, - onNav: (DesktopNav) -> Unit, - onSettings: () -> Unit, - bottomBar: @Composable () -> Unit, - sidebar: @Composable () -> Unit, - modifier: Modifier = Modifier, - content: @Composable () -> Unit -) { - val bg = MaterialTheme.colorScheme.background - Column(modifier.fillMaxSize().background(bg)) { - Row( - Modifier - .weight(1f) - .fillMaxWidth() - .padding(start = 8.dp, end = 8.dp, top = 8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - LeftNav( - nav = nav, - onNav = onNav, - onSettings = onSettings, - modifier = Modifier - .width(248.dp) - .fillMaxHeight() - ) - Surface( - modifier = Modifier.weight(1f).fillMaxHeight(), - shape = MaterialTheme.shapes.large, - color = MaterialTheme.colorScheme.surface, - tonalElevation = 0.dp - ) { - Box(Modifier.fillMaxSize()) { content() } - } - Surface( - modifier = Modifier.width(360.dp).fillMaxHeight(), - shape = MaterialTheme.shapes.large, - color = MaterialTheme.colorScheme.surface, - tonalElevation = 0.dp - ) { - sidebar() - } - } - Box(Modifier.fillMaxWidth().padding(8.dp)) { bottomBar() } - } -} - -@Composable -private fun LeftNav( - nav: DesktopNav, - onNav: (DesktopNav) -> Unit, - onSettings: () -> Unit, - modifier: Modifier = Modifier -) { - Column(modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) { - Surface( - shape = MaterialTheme.shapes.large, - color = MaterialTheme.colorScheme.surface, - modifier = Modifier.fillMaxWidth() - ) { - Column(Modifier.padding(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { - NavRow(Icons.Default.Home, "Home", nav == DesktopNav.Home) { onNav(DesktopNav.Home) } - NavRow(Icons.Default.Search, "Search", nav == DesktopNav.Search) { onNav(DesktopNav.Search) } - } - } - Surface( - shape = MaterialTheme.shapes.large, - color = MaterialTheme.colorScheme.surface, - modifier = Modifier.weight(1f).fillMaxWidth() - ) { - Column(Modifier.padding(12.dp).fillMaxSize()) { - NavRow( - Icons.Outlined.LibraryMusic, - "Your library", - nav == DesktopNav.Library - ) { onNav(DesktopNav.Library) } - Spacer(Modifier.height(8.dp)) - Text( - "Local folders show up here.", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), - modifier = Modifier.padding(horizontal = 8.dp) - ) - Spacer(Modifier.weight(1f)) - NavRow(Icons.Default.Settings, "Settings", selected = false, onClick = onSettings) - } - } - } -} - -@Composable -private fun NavRow( - icon: ImageVector, - label: String, - selected: Boolean, - onClick: () -> Unit -) { - val shape = if (selected) CircleShape else RoundedCornerShape(28.dp) - Row( - Modifier - .fillMaxWidth() - .clip(shape) - .clickable(onClick = onClick) - .background( - if (selected) MaterialTheme.colorScheme.primary.copy(alpha = 0.16f) - else MaterialTheme.colorScheme.surface - ) - .padding(horizontal = 14.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - icon, - contentDescription = label, - modifier = Modifier.size(24.dp), - tint = if (selected) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.onSurface - ) - Spacer(Modifier.width(12.dp)) - Text( - label, - fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, - color = if (selected) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.onSurface - ) - } -} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt index d861f663..6c0eb611 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt @@ -1,18 +1,29 @@ package capital.yuri.yuriplayer.components.player +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.QueueMusic +import androidx.compose.material.icons.filled.Repeat +import androidx.compose.material.icons.filled.RepeatOne +import androidx.compose.material.icons.filled.Shuffle import androidx.compose.material.icons.filled.SkipNext import androidx.compose.material.icons.filled.SkipPrevious +import androidx.compose.material.icons.filled.VolumeUp +import androidx.compose.material.icons.outlined.FavoriteBorder import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -32,6 +43,7 @@ import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.list.formatTime import capital.yuri.yuriplayer.components.model.CoverRef +import capital.yuri.yuriplayer.core.player.RepeatMode @Composable fun BottomPlayerBar( @@ -44,40 +56,45 @@ fun BottomPlayerBar( onNext: () -> Unit, onSeek: (Long) -> Unit, modifier: Modifier = Modifier, - accent: Color = MaterialTheme.colorScheme.primary + accent: Color = MaterialTheme.colorScheme.primary, + shuffle: Boolean = false, + repeat: RepeatMode = RepeatMode.OFF, + onToggleShuffle: () -> Unit = {}, + onCycleRepeat: () -> Unit = {}, + volume: Float = 1f, + onVolume: (Float) -> Unit = {}, + liked: Boolean = false, + onToggleLike: () -> Unit = {}, + queueVisible: Boolean = true, + onToggleQueue: () -> Unit = {} ) { + val onAccent = MaterialTheme.colorScheme.onPrimary + val muted = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) Surface( modifier = modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface, - shape = MaterialTheme.shapes.large, - tonalElevation = 0.dp + shape = MaterialTheme.shapes.large ) { - Column(Modifier.padding(horizontal = 16.dp, vertical = 10.dp)) { - if (durationMs > 0) { - Slider( - value = (positionMs.toFloat() / durationMs).coerceIn(0f, 1f), - onValueChange = { onSeek((it * durationMs).toLong()) }, - modifier = Modifier.fillMaxWidth(), - colors = SliderDefaults.colors( - thumbColor = accent, - activeTrackColor = accent, - inactiveTrackColor = accent.copy(alpha = 0.24f) - ) - ) - } + Box( + Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + .height(72.dp) + ) { Row( - Modifier.fillMaxWidth(), + Modifier.align(Alignment.CenterStart).widthIn(max = 360.dp), verticalAlignment = Alignment.CenterVertically ) { - CoverArt(model = track?.artworkUri, size = 56.dp, corner = 12.dp) + CoverArt(model = track?.artworkUri, size = 56.dp, corner = 6.dp) Spacer(Modifier.width(12.dp)) - Column(Modifier.weight(1f)) { + Column(Modifier.weight(1f, fill = false).widthIn(max = 220.dp)) { Text( track?.title ?: "Nothing playing", maxLines = 1, overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface ) Text( @@ -85,39 +102,126 @@ fun BottomPlayerBar( maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + color = muted ) } - IconButton(onClick = onPrev) { - Icon(Icons.Default.SkipPrevious, contentDescription = "Previous", tint = MaterialTheme.colorScheme.onSurface) + if (track != null) { + IconButton(onClick = onToggleLike) { + Icon( + if (liked) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = if (liked) "Unlike" else "Like", + tint = if (liked) accent else muted + ) + } } - FilledIconButton( - onClick = onToggle, - modifier = Modifier.size(56.dp), - shape = CircleShape, - colors = IconButtonDefaults.filledIconButtonColors( - containerColor = accent, - contentColor = MaterialTheme.colorScheme.onPrimary - ) + } + Column( + Modifier + .align(Alignment.Center) + .fillMaxWidth(0.42f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp) ) { - Icon( - if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, - contentDescription = if (playing) "Pause" else "Play", - modifier = Modifier.size(28.dp) - ) - } - IconButton(onClick = onNext) { - Icon(Icons.Default.SkipNext, contentDescription = "Next", tint = MaterialTheme.colorScheme.onSurface) + IconButton(onClick = onToggleShuffle, modifier = Modifier.size(36.dp)) { + Icon( + Icons.Default.Shuffle, + contentDescription = "Shuffle", + modifier = Modifier.size(18.dp), + tint = if (shuffle) accent else muted + ) + } + IconButton(onClick = onPrev, modifier = Modifier.size(36.dp)) { + Icon( + Icons.Default.SkipPrevious, + contentDescription = "Previous", + modifier = Modifier.size(22.dp) + ) + } + FilledIconButton( + onClick = onToggle, + modifier = Modifier.size(36.dp), + shape = CircleShape, + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.onSurface, + contentColor = MaterialTheme.colorScheme.surface + ) + ) { + Icon( + if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, + contentDescription = if (playing) "Pause" else "Play", + modifier = Modifier.size(20.dp) + ) + } + IconButton(onClick = onNext, modifier = Modifier.size(36.dp)) { + Icon( + Icons.Default.SkipNext, + contentDescription = "Next", + modifier = Modifier.size(22.dp) + ) + } + IconButton(onClick = onCycleRepeat, modifier = Modifier.size(36.dp)) { + Icon( + if (repeat == RepeatMode.ONE) Icons.Default.RepeatOne else Icons.Default.Repeat, + contentDescription = "Repeat", + modifier = Modifier.size(18.dp), + tint = if (repeat != RepeatMode.OFF) accent else muted + ) + } } - if (durationMs > 0) { - Spacer(Modifier.width(8.dp)) + Row( + Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { Text( - "${formatTime(positionMs)} / ${formatTime(durationMs)}", + formatTime(positionMs), style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + color = muted + ) + Slider( + value = if (durationMs > 0) { + (positionMs.toFloat() / durationMs).coerceIn(0f, 1f) + } else 0f, + onValueChange = { if (durationMs > 0) onSeek((it * durationMs).toLong()) }, + modifier = Modifier.weight(1f).height(20.dp).padding(horizontal = 8.dp), + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.onSurface, + activeTrackColor = MaterialTheme.colorScheme.onSurface, + inactiveTrackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.22f) + ) + ) + Text( + formatTime(durationMs), + style = MaterialTheme.typography.labelSmall, + color = muted ) } } + Row( + Modifier.align(Alignment.CenterEnd), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton(onClick = onToggleQueue) { + Icon( + Icons.Default.QueueMusic, + contentDescription = "Queue", + tint = if (queueVisible) accent else muted + ) + } + Icon(Icons.Default.VolumeUp, contentDescription = "Volume", tint = muted) + Slider( + value = volume, + onValueChange = onVolume, + modifier = Modifier.width(100.dp).height(20.dp), + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.onSurface, + activeTrackColor = MaterialTheme.colorScheme.onSurface, + inactiveTrackColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.22f) + ) + ) + } } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index 8bbf762f..ed4c9afb 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -2,15 +2,18 @@ package capital.yuri.yuriplayer.components.player import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -32,34 +35,52 @@ fun NowPlayingSidebar( Column( modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.background) + .background(MaterialTheme.colorScheme.surface) .padding(12.dp) ) { + Text( + "Queue", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(Modifier.height(12.dp)) + if (track != null) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { + CoverArt(model = track.artworkUri, size = 48.dp, corner = 8.dp) + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f)) { + Text( + "Now playing", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary + ) + Text( + track.title, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary + ) + Text( + track.subtitle, + style = MaterialTheme.typography.bodySmall, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + } + } + Spacer(Modifier.height(8.dp)) + } CoverArt( model = track?.artworkUri, modifier = Modifier .fillMaxWidth() - .aspectRatio(1f) - .padding(4.dp), - corner = 20.dp + .padding(vertical = 8.dp) + .height(220.dp), + corner = 16.dp ) - Spacer(Modifier.height(12.dp)) - Text( - track?.title ?: "Nothing playing", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onBackground - ) - Text( - track?.subtitle ?: " ", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Spacer(Modifier.height(12.dp)) QueuePanel( nowPlaying = track, upcoming = queue.filter { !it.highlighted }, diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/AccentPicker.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/AccentPicker.kt new file mode 100644 index 00000000..d3734bdf --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/AccentPicker.kt @@ -0,0 +1,76 @@ +package capital.yuri.yuriplayer.components.theme + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.FilterChip +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun ThemeModePicker( + mode: ThemeMode, + onMode: (ThemeMode) -> Unit, + modifier: Modifier = Modifier +) { + FlowRow( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + ThemeMode.entries.forEach { m -> + FilterChip( + selected = mode == m, + onClick = { onMode(m) }, + label = { Text(m.label) } + ) + } + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun AccentPicker( + selectedId: String, + onSelect: (String) -> Unit, + modifier: Modifier = Modifier +) { + Column(modifier) { + Text( + "Accent", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 8.dp) + ) + FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + AccentCatalog.all.forEach { swatch -> + val selected = swatch.id == selectedId + Box( + Modifier + .size(40.dp) + .clip(CircleShape) + .background(swatch.color) + .border( + width = if (selected) 3.dp else 1.dp, + color = if (selected) MaterialTheme.colorScheme.onSurface + else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.25f), + shape = CircleShape + ) + .clickable { onSelect(swatch.id) } + ) + } + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/ThemeCatalog.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/ThemeCatalog.kt new file mode 100644 index 00000000..24f32c08 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/ThemeCatalog.kt @@ -0,0 +1,115 @@ +package capital.yuri.yuriplayer.components.theme + +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance + +/** + * Appearance is **not** glued to Material 3 forever. + * + * [ThemeFamily.MATERIAL3] is the default (light/dark + accent). Other families + * (Jewel, macOS, Windows) plug in later — see github.com/LizAinslie/yuriplayer/issues/16. + */ +enum class ThemeFamily(val id: String, val label: String) { + MATERIAL3("material3", "Material 3") +} + +enum class ThemeMode(val id: String, val label: String) { + DARK("dark", "Dark"), + LIGHT("light", "Light"), + SYSTEM("system", "Follow system"); + + companion object { + fun fromId(raw: String?): ThemeMode = + entries.firstOrNull { it.id.equals(raw, true) } ?: DARK + } +} + +data class AccentSwatch( + val id: String, + val label: String, + val color: Color +) + +object AccentCatalog { + val yuri = AccentSwatch("yuri", "Yuri purple", Color(0xFFB388FF)) + val all: List = listOf( + yuri, + AccentSwatch("violet", "Violet", Color(0xFF7C4DFF)), + AccentSwatch("rose", "Rose", Color(0xFFFF8A80)), + AccentSwatch("coral", "Coral", Color(0xFFFF6E40)), + AccentSwatch("amber", "Amber", Color(0xFFFFC107)), + AccentSwatch("lime", "Lime", Color(0xFFC6FF00)), + AccentSwatch("teal", "Teal", Color(0xFF1DE9B6)), + AccentSwatch("sky", "Sky", Color(0xFF40C4FF)), + AccentSwatch("indigo", "Indigo", Color(0xFF5C6BC0)), + AccentSwatch("graphite", "Graphite", Color(0xFF90A4AE)) + ) + + fun byId(id: String?): AccentSwatch = + all.firstOrNull { it.id.equals(id, true) } ?: yuri +} + +data class ThemeChoice( + val mode: ThemeMode = ThemeMode.DARK, + val accentId: String = AccentCatalog.yuri.id, + val family: ThemeFamily = ThemeFamily.MATERIAL3 +) + +fun ThemeChoice.isDark(systemDark: Boolean): Boolean = when (mode) { + ThemeMode.DARK -> true + ThemeMode.LIGHT -> false + ThemeMode.SYSTEM -> systemDark +} + +fun ThemeChoice.colorScheme( + systemDark: Boolean, + dynamic: ColorScheme? = null +): ColorScheme { + if (dynamic != null && family == ThemeFamily.MATERIAL3) return dynamic + val accent = AccentCatalog.byId(accentId).color + return material3Scheme(isDark(systemDark), accent) +} + +fun material3Scheme(dark: Boolean, accent: Color): ColorScheme { + val onAccent = if (accent.luminance() > 0.55f) Color(0xFF1A1224) else Color.White + return if (dark) { + darkColorScheme( + primary = accent, + onPrimary = onAccent, + primaryContainer = accent.copy(alpha = 0.28f), + onPrimaryContainer = Color(0xFFE8E0F0), + secondary = accent.copy(alpha = 0.85f), + onSecondary = onAccent, + tertiary = Color(0xFFEFB8C8), + onTertiary = Color(0xFF1A1224), + background = Color(0xFF121018), + onBackground = Color(0xFFE8E0F0), + surface = Color(0xFF1C1826), + onSurface = Color(0xFFE8E0F0), + surfaceVariant = Color(0xFF2A2438), + onSurfaceVariant = Color(0xFFB0A4C0), + outline = Color(0xFFB0A4C0).copy(alpha = 0.5f) + ) + } else { + lightColorScheme( + primary = accent, + onPrimary = onAccent, + primaryContainer = accent.copy(alpha = 0.18f), + onPrimaryContainer = Color(0xFF1A1224), + secondary = accent, + onSecondary = onAccent, + tertiary = Color(0xFFB0005A), + onTertiary = Color.White, + background = Color(0xFFF6F0FA), + onBackground = Color(0xFF1A1224), + surface = Color(0xFFFFFBFF), + onSurface = Color(0xFF1A1224), + surfaceVariant = Color(0xFFE8DFF0), + onSurfaceVariant = Color(0xFF4A4258), + outline = Color(0xFF7A7088) + ) + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt index 3e8dd168..985762a3 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/theme/YuriTheme.kt @@ -1,5 +1,6 @@ package capital.yuri.yuriplayer.components.theme +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme @@ -9,17 +10,19 @@ import androidx.compose.ui.Modifier @Composable fun YuriTheme( - colorScheme: ColorScheme = YuriDarkColorScheme, + choice: ThemeChoice = ThemeChoice(), + colorScheme: ColorScheme? = null, content: @Composable () -> Unit ) { + val scheme = colorScheme ?: choice.colorScheme(isSystemInDarkTheme()) MaterialTheme( - colorScheme = colorScheme, + colorScheme = scheme, shapes = YuriShapes ) { Surface( modifier = Modifier.fillMaxSize(), - color = colorScheme.background, - contentColor = colorScheme.onBackground + color = scheme.background, + contentColor = scheme.onBackground ) { content() } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 69728821..5f537df8 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -23,12 +23,15 @@ kotlin { commonMain.dependencies { implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.serialization.json) + implementation(libs.ktor.client.core) } androidMain.dependencies { implementation(libs.kotlinx.coroutines.android) + implementation(libs.ktor.client.cio) } jvmMain.dependencies { implementation(libs.jaudiotagger) + implementation(libs.ktor.client.cio) } jvmTest.dependencies { implementation(kotlin("test")) diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/http/UrlDsl.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/http/UrlDsl.kt new file mode 100644 index 00000000..a8c35a38 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/http/UrlDsl.kt @@ -0,0 +1,35 @@ +package capital.yuri.yuriplayer.core.http + +import io.ktor.http.URLBuilder +import io.ktor.http.appendPathSegments + +fun url(base: String, block: UrlScope.() -> Unit = {}): String = + UrlScope(URLBuilder(base.trim())).apply(block).build() + +class UrlScope internal constructor(private val builder: URLBuilder) { + fun path(vararg segments: String, encodeSlash: Boolean = false) { + val parts = segments.filter { it.isNotBlank() } + if (parts.isNotEmpty()) { + builder.appendPathSegments(*parts.toTypedArray(), encodeSlash = encodeSlash) + } + } + + fun param(name: String, value: Any?) { + if (value == null) return + val text = value.toString() + if (text.isEmpty()) return + builder.parameters.append(name, text) + } + + internal fun build(): String = builder.buildString() +} + +fun normalizeBaseUrl(raw: String): String { + var u = raw.trim().trimEnd('/') + if (!u.startsWith("http://", ignoreCase = true) && + !u.startsWith("https://", ignoreCase = true) + ) { + u = "https://$u" + } + return u +} diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlaybackEngine.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlaybackEngine.kt index 6b9fa03a..5b497eab 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlaybackEngine.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlaybackEngine.kt @@ -25,6 +25,9 @@ interface PlaybackEngine { fun getPositionMs(): Long fun getDurationMs(): Long + fun setVolume(percent: Int) {} + fun getVolume(): Int = 100 + fun setNext(item: PlaybackMedia?) {} fun hasPreparedNext(): Boolean = false fun playPreparedNext(): Boolean = false diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt index 3782fc27..10804f9d 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -26,9 +26,35 @@ class PlayerSession( val isPlaying: StateFlow = engine.isPlaying + private val _shuffle = MutableStateFlow(false) + val shuffle: StateFlow = _shuffle.asStateFlow() + + private val _repeat = MutableStateFlow(RepeatMode.OFF) + val repeat: StateFlow = _repeat.asStateFlow() + + private val _volume = MutableStateFlow(1f) + val volume: StateFlow = _volume.asStateFlow() + private val listener = object : PlaybackEngine.Listener { override fun onEnded() { - if (!engine.playPreparedNext()) next() + when (_repeat.value) { + RepeatMode.ONE -> { + engine.seekTo(0) + engine.play() + } + RepeatMode.ALL -> { + val q = _queue.value + if (q.isEmpty()) return + if (!engine.playPreparedNext()) { + val i = _index.value + 1 + if (i in q.indices) next() + else loadAt(0, 0L, play = true) + } + } + RepeatMode.OFF -> { + if (!engine.playPreparedNext()) next() + } + } } override fun onAutoAdvanced() { @@ -95,6 +121,30 @@ class PlayerSession( fun seekTo(positionMs: Long) = engine.seekTo(positionMs.coerceAtLeast(0)) + fun toggleShuffle() { + _shuffle.value = !_shuffle.value + if (_shuffle.value) { + val cur = _current.value + val rest = _queue.value.filter { it.id != cur?.id }.shuffled() + if (cur != null) { + _queue.value = listOf(cur) + rest + _index.value = 0 + } else { + _queue.value = _queue.value.shuffled() + } + } + } + + fun cycleRepeat() { + _repeat.value = _repeat.value.next() + } + + fun setVolume(value: Float) { + val v = value.coerceIn(0f, 1f) + _volume.value = v + engine.setVolume((v * 100).toInt()) + } + fun skipTo(index: Int) { if (index in _queue.value.indices) loadAt(index, 0L, play = true) } diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/RepeatMode.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/RepeatMode.kt new file mode 100644 index 00000000..5715b4a7 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/RepeatMode.kt @@ -0,0 +1,9 @@ +package capital.yuri.yuriplayer.core.player + +enum class RepeatMode { OFF, ALL, ONE } + +fun RepeatMode.next(): RepeatMode = when (this) { + RepeatMode.OFF -> RepeatMode.ALL + RepeatMode.ALL -> RepeatMode.ONE + RepeatMode.ONE -> RepeatMode.OFF +} diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/source/RemoteAccount.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/source/RemoteAccount.kt new file mode 100644 index 00000000..8ee4e899 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/source/RemoteAccount.kt @@ -0,0 +1,29 @@ +package capital.yuri.yuriplayer.core.source + +import kotlinx.serialization.Serializable + +enum class SourceKind { + LOCAL, + JELLYFIN, + SUBSONIC +} + +@Serializable +data class RemoteAccount( + val id: String, + val kind: SourceKind, + val name: String, + val baseUrl: String, + val username: String, + val secret: String, + val enabled: Boolean = true, + val accessToken: String? = null, + val userId: String? = null +) + +@Serializable +data class LibrarySourcesFile( + val extraFolders: List = emptyList(), + val remotes: List = emptyList(), + val deviceId: String = "" +) diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt index 0926078c..f0ce96c6 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/CoverPixels.kt @@ -21,12 +21,18 @@ object CoverPixels { private fun load(uri: String?): BufferedImage? { if (uri.isNullOrBlank()) return null return try { - val file = when { - uri.startsWith("file:") -> File(URI(uri)) - else -> File(uri) + when { + uri.startsWith("http://", true) || uri.startsWith("https://", true) -> + ImageIO.read(URI(uri).toURL()) + uri.startsWith("file:") -> { + val file = File(URI(uri)) + if (!file.isFile) null else ImageIO.read(file) + } + else -> { + val file = File(uri) + if (!file.isFile) null else ImageIO.read(file) + } } - if (!file.isFile) return null - ImageIO.read(file) } catch (_: Exception) { null } diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt index 1b533159..e0c69461 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt @@ -1,6 +1,7 @@ package capital.yuri.yuriplayer.core.library import capital.yuri.yuriplayer.core.platform.appDirectories +import capital.yuri.yuriplayer.core.platform.coverCacheDir import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.tag.FieldKey import java.io.File @@ -13,10 +14,12 @@ object LocalLibraryScanner { "wav", "aiff", "aif", "wma", "alac" ) private val COVER_NAMES = listOf( - "cover.jpg", "cover.jpeg", "cover.png", - "folder.jpg", "folder.png", - "AlbumArt.jpg", "front.jpg" + "cover.jpg", "cover.jpeg", "cover.png", "cover.webp", + "folder.jpg", "folder.jpeg", "folder.png", + "AlbumArt.jpg", "AlbumArt.png", "AlbumArtSmall.jpg", + "front.jpg", "front.png", "album.jpg", "album.png" ) + private val IMAGE_EXT = setOf("jpg", "jpeg", "png", "webp") init { Logger.getLogger("org.jaudiotagger").level = Level.OFF @@ -29,6 +32,8 @@ object LocalLibraryScanner { if (roots.isEmpty()) return emptyList() val out = ArrayList(512) val seen = HashSet() + val coverByDir = HashMap() + File(coverCacheDir()).mkdirs() for (root in roots) { if (!root.isDirectory) continue root.walkTopDown() @@ -38,7 +43,7 @@ object LocalLibraryScanner { .forEach { file -> val path = file.absolutePath if (!seen.add(path)) return@forEach - out += readTrack(file) + out += readTrack(file, coverByDir) } } out.sortWith( @@ -50,7 +55,7 @@ object LocalLibraryScanner { return out } - private fun readTrack(file: File): Track { + private fun readTrack(file: File, coverByDir: MutableMap): Track { val fallbackTitle = file.nameWithoutExtension return try { val audio = AudioFileIO.read(file) @@ -58,11 +63,6 @@ object LocalLibraryScanner { val header = audio.audioHeader fun field(key: FieldKey): String? = tag?.getFirst(key)?.trim()?.takeIf { it.isNotEmpty() } - val folderCover = COVER_NAMES - .map { File(file.parentFile, it) } - .firstOrNull { it.isFile } - ?.toURI() - ?.toString() Track( id = file.absolutePath, uri = file.toURI().toString(), @@ -75,7 +75,7 @@ object LocalLibraryScanner { discNumber = field(FieldKey.DISC_NO)?.substringBefore('/')?.toIntOrNull(), year = field(FieldKey.YEAR)?.take(4)?.toIntOrNull(), genre = field(FieldKey.GENRE), - artworkUri = folderCover, + artworkUri = resolveCover(file, audio, coverByDir), path = file.absolutePath ) } catch (_: Exception) { @@ -83,8 +83,69 @@ object LocalLibraryScanner { id = file.absolutePath, uri = file.toURI().toString(), title = fallbackTitle, + artworkUri = resolveCover(file, null, coverByDir), path = file.absolutePath ) } } + + /** + * Folder `cover.jpg` / `folder.jpg` first (this dir, then parent), then any + * image in the folder, then embedded tag art extracted into the cover cache. + */ + private fun resolveCover( + file: File, + audio: org.jaudiotagger.audio.AudioFile?, + coverByDir: MutableMap + ): String? { + val dir = file.parentFile ?: return extractEmbedded(file, audio) + val key = dir.absolutePath + coverByDir[key]?.let { return it } + val folder = namedCover(dir) ?: namedCover(dir.parentFile) ?: firstImage(dir) + if (folder != null) { + val uri = folder.toURI().toString() + coverByDir[key] = uri + return uri + } + val embedded = extractEmbedded(file, audio) + coverByDir[key] = embedded + return embedded + } + + private fun namedCover(dir: File?): File? { + if (dir == null || !dir.isDirectory) return null + for (name in COVER_NAMES) { + val f = File(dir, name) + if (f.isFile && f.length() > 32) return f + } + return null + } + + private fun firstImage(dir: File): File? { + val images = dir.listFiles { f -> + f.isFile && f.extension.lowercase() in IMAGE_EXT && f.length() > 32 && + !f.name.startsWith('.') && + !f.name.contains("fanart", true) && + !f.name.contains("artist", true) && + !f.name.contains("logo", true) + } ?: return null + return images.minByOrNull { it.name.length } + } + + private fun extractEmbedded( + file: File, + audio: org.jaudiotagger.audio.AudioFile? + ): String? { + val cache = File(coverCacheDir(), "${file.parentFile?.absolutePath.hashCode() and 0x7fffffff}.jpg") + if (cache.isFile && cache.length() > 32) return cache.toURI().toString() + return try { + val art = (audio ?: AudioFileIO.read(file)).tag?.firstArtwork ?: return null + val bytes = art.binaryData?.takeIf { it.size > 32 } ?: return null + cache.parentFile?.mkdirs() + cache.writeBytes(bytes) + cache.toURI().toString() + } catch (_: Exception) { + null + } + } } diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt new file mode 100644 index 00000000..8d319271 --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt @@ -0,0 +1,175 @@ +package capital.yuri.yuriplayer.core.source + +import capital.yuri.yuriplayer.core.http.normalizeBaseUrl +import capital.yuri.yuriplayer.core.http.url +import capital.yuri.yuriplayer.core.library.Track +import io.ktor.client.HttpClient +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.get +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.content.TextContent +import io.ktor.http.isSuccess +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json + +class JellyfinCatalog( + private val http: HttpClient, + private val deviceId: String, + private val json: Json = Json { ignoreUnknownKeys = true; isLenient = true } +) { + suspend fun authenticate(account: RemoteAccount): Result = runCatching { + val root = normalizeBaseUrl(account.baseUrl) + val response = http.post("$root/Users/AuthenticateByName") { + header("X-Emby-Authorization", authHeader(token = null)) + setBody( + TextContent( + json.encodeToString( + AuthRequest.serializer(), + AuthRequest(account.username.trim(), account.secret) + ), + ContentType.Application.Json + ) + ) + } + if (!response.status.isSuccess()) { + if (response.status.value == 401) error("Invalid username or password") + error("Jellyfin HTTP ${response.status.value}") + } + val body = json.decodeFromString(response.bodyAsText()) + val token = body.accessToken ?: error("No access token") + val userId = body.user?.id ?: error("No user id") + account.copy( + baseUrl = root, + accessToken = token, + userId = userId, + name = account.name.ifBlank { body.user?.name ?: "Jellyfin" } + ) + } + + suspend fun listTracks(account: RemoteAccount, maxItems: Int = 50_000): Result> = + runCatching { + val token = account.accessToken ?: error("Not signed in") + val userId = account.userId ?: error("Not signed in") + val root = normalizeBaseUrl(account.baseUrl) + val out = ArrayList(512) + var start = 0 + val pageSize = 400 + while (out.size < maxItems) { + val take = minOf(pageSize, maxItems - out.size) + val requestUrl = url(root) { + path("Users", userId, "Items") + param("IncludeItemTypes", "Audio") + param("Recursive", "true") + param("Fields", "Album,AlbumArtist,Artists,RunTimeTicks,IndexNumber,ParentIndexNumber,ProductionYear,Genres,PrimaryImageAspectRatio,Container") + param("EnableImageTypes", "Primary") + param("ImageTypeLimit", "1") + param("StartIndex", start) + param("Limit", take) + param("SortBy", "Album,SortName") + } + val response = http.get(requestUrl) { + header("X-Emby-Authorization", authHeader(token)) + header("X-Emby-Token", token) + } + if (!response.status.isSuccess()) error("Items HTTP ${response.status.value}") + val page = json.decodeFromString(response.bodyAsText()) + val items = page.items + if (items.isEmpty()) break + items.forEach { item -> + toTrack(account.copy(baseUrl = root), item)?.let { out += it } + } + start += items.size + if (items.size < take) break + val total = page.total + if (total != null && start >= total) break + } + out + } + + private fun toTrack(account: RemoteAccount, item: JfItem): Track? { + val id = item.id ?: return null + val token = account.accessToken ?: return null + val artist = item.artists.firstOrNull()?.takeIf { it.isNotBlank() } ?: item.albumArtist + val artId = when { + item.imageTags?.containsKey("Primary") == true -> id + !item.albumId.isNullOrBlank() -> item.albumId + else -> null + } + val art = artId?.let { + url(account.baseUrl) { + path("Items", it, "Images", "Primary") + param("maxWidth", 600) + param("quality", 90) + param("api_key", token) + } + } + val stream = url(account.baseUrl) { + path("Audio", id, "stream") + param("static", true) + param("api_key", token) + item.container?.let { param("Container", it) } + } + return Track( + id = "jellyfin:$id", + uri = stream, + title = item.name, + artist = artist, + albumArtist = item.albumArtist ?: artist, + album = item.album, + durationMs = item.runTimeTicks?.div(10_000L)?.takeIf { it > 0 }, + trackNumber = item.indexNumber, + discNumber = item.parentIndexNumber, + year = item.year, + artworkUri = art + ) + } + + private fun authHeader(token: String?): String { + val base = "MediaBrowser Client=\"Yuri Player\", Device=\"Desktop\", DeviceId=\"$deviceId\", Version=\"1.0\"" + return if (token.isNullOrBlank()) base else "$base, Token=\"$token\"" + } + + @Serializable + private data class AuthRequest( + @SerialName("Username") val username: String, + @SerialName("Pw") val password: String + ) + + @Serializable + private data class AuthResponse( + @SerialName("AccessToken") val accessToken: String? = null, + @SerialName("User") val user: AuthUser? = null + ) + + @Serializable + private data class AuthUser( + @SerialName("Id") val id: String? = null, + @SerialName("Name") val name: String? = null + ) + + @Serializable + private data class ItemsResponse( + @SerialName("Items") val items: List = emptyList(), + @SerialName("TotalRecordCount") val total: Int? = null + ) + + @Serializable + private data class JfItem( + @SerialName("Id") val id: String? = null, + @SerialName("Name") val name: String? = null, + @SerialName("Album") val album: String? = null, + @SerialName("AlbumArtist") val albumArtist: String? = null, + @SerialName("Artists") val artists: List = emptyList(), + @SerialName("RunTimeTicks") val runTimeTicks: Long? = null, + @SerialName("IndexNumber") val indexNumber: Int? = null, + @SerialName("ParentIndexNumber") val parentIndexNumber: Int? = null, + @SerialName("ProductionYear") val year: Int? = null, + @SerialName("AlbumId") val albumId: String? = null, + @SerialName("ImageTags") val imageTags: Map? = null, + @SerialName("Container") val container: String? = null + ) +} diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/LibrarySourceStore.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/LibrarySourceStore.kt new file mode 100644 index 00000000..d4c1ee12 --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/LibrarySourceStore.kt @@ -0,0 +1,85 @@ +package capital.yuri.yuriplayer.core.source + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File +import java.util.UUID + +class LibrarySourceStore(configDir: String) { + private val file = File(configDir, "sources.json") + private val json = Json { prettyPrint = true; ignoreUnknownKeys = true; encodeDefaults = true } + + private val _folders = MutableStateFlow>(emptyList()) + val extraFolders: StateFlow> = _folders.asStateFlow() + + private val _remotes = MutableStateFlow>(emptyList()) + val remotes: StateFlow> = _remotes.asStateFlow() + + var deviceId: String = UUID.randomUUID().toString() + private set + + init { + load() + } + + fun addFolder(path: String) { + val p = path.trim() + if (p.isEmpty()) return + if (_folders.value.any { it.equals(p, ignoreCase = true) }) return + _folders.value = _folders.value + p + persist() + } + + fun removeFolder(path: String) { + _folders.value = _folders.value.filterNot { it.equals(path, ignoreCase = true) } + persist() + } + + fun upsertRemote(account: RemoteAccount) { + val cur = _remotes.value.toMutableList() + val i = cur.indexOfFirst { it.id == account.id } + if (i >= 0) cur[i] = account else cur += account + _remotes.value = cur + persist() + } + + fun removeRemote(id: String) { + _remotes.value = _remotes.value.filterNot { it.id == id } + persist() + } + + fun setRemoteEnabled(id: String, enabled: Boolean) { + _remotes.value = _remotes.value.map { + if (it.id == id) it.copy(enabled = enabled) else it + } + persist() + } + + private fun load() { + if (!file.exists()) return + runCatching { + val data = json.decodeFromString(file.readText()) + _folders.value = data.extraFolders + _remotes.value = data.remotes + if (data.deviceId.isNotBlank()) deviceId = data.deviceId + } + } + + private fun persist() { + runCatching { + file.parentFile?.mkdirs() + file.writeText( + json.encodeToString( + LibrarySourcesFile( + extraFolders = _folders.value, + remotes = _remotes.value, + deviceId = deviceId + ) + ) + ) + } + } +} diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt new file mode 100644 index 00000000..573d88bf --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt @@ -0,0 +1,219 @@ +package capital.yuri.yuriplayer.core.source + +import capital.yuri.yuriplayer.core.http.UrlScope +import capital.yuri.yuriplayer.core.http.normalizeBaseUrl +import capital.yuri.yuriplayer.core.http.url +import capital.yuri.yuriplayer.core.library.Track +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.isSuccess +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import java.security.MessageDigest +import java.util.UUID + +class SubsonicCatalog( + private val http: HttpClient, + private val json: Json = Json { ignoreUnknownKeys = true; isLenient = true } +) { + data class Session( + val baseUrl: String, + val username: String, + val password: String, + val clientName: String = "YuriPlayer" + ) + + suspend fun ping(account: RemoteAccount): Result = runCatching { + val session = sessionOf(account) + val body = apiGet(session, "ping") + val resp = json.decodeFromString(body).subsonicResponse + if (resp.status != "ok") error(resp.error?.message ?: "Could not reach server") + account.copy( + name = account.name.ifBlank { + resp.type?.replaceFirstChar { it.uppercase() } ?: "Subsonic" + }, + baseUrl = session.baseUrl + ) + } + + suspend fun listTracks(account: RemoteAccount, maxSongs: Int = 80_000): Result> = + runCatching { + val session = sessionOf(account) + val out = ArrayList(512) + var offset = 0 + val pageSize = 100 + while (out.size < maxSongs) { + val page = listAlbums(session, offset, pageSize) + if (page.isEmpty()) break + for (album in page) { + val id = album.id ?: continue + out += songsForAlbum(session, id, album) + if (out.size >= maxSongs) break + } + offset += page.size + if (page.size < pageSize) break + } + out + } + + private suspend fun listAlbums(session: Session, offset: Int, size: Int): List { + val body = apiGet(session, "getAlbumList2") { + param("type", "alphabeticalByName") + param("size", size) + param("offset", offset) + } + return json.decodeFromString(body) + .subsonicResponse.albumList2?.album.orEmpty() + } + + private suspend fun songsForAlbum( + session: Session, + albumId: String, + albumRef: SubsonicAlbum + ): List { + val body = apiGet(session, "getAlbum") { param("id", albumId) } + val album = json.decodeFromString(body).subsonicResponse.album + ?: return emptyList() + val cover = coverUrl(session, album.coverArt ?: album.id ?: albumRef.coverArt) + val albumName = album.name ?: albumRef.name + val albumArtist = album.albumArtist ?: album.artist ?: albumRef.artist + return album.song.orEmpty().mapNotNull { child -> + val sid = child.id ?: return@mapNotNull null + if (child.isDir == true) return@mapNotNull null + Track( + id = "subsonic:$sid", + uri = streamUrl(session, sid), + title = child.title ?: child.name, + artist = child.artist ?: albumArtist, + albumArtist = albumArtist, + album = child.album ?: albumName, + durationMs = child.duration?.times(1000L), + trackNumber = child.track, + discNumber = child.discNumber, + year = child.year ?: album.year, + genre = child.genre, + artworkUri = coverUrl(session, child.coverArt) ?: cover + ) + } + } + + fun streamUrl(session: Session, id: String): String = + restUrl(session, "stream") { + param("id", id) + param("format", "raw") + } + + fun coverUrl(session: Session, coverArtId: String?, size: Int = 600): String? { + if (coverArtId.isNullOrBlank()) return null + return restUrl(session, "getCoverArt") { + param("id", coverArtId) + param("size", size) + } + } + + private fun sessionOf(account: RemoteAccount) = Session( + baseUrl = normalizeBaseUrl(account.baseUrl), + username = account.username, + password = account.secret + ) + + private fun restUrl(session: Session, action: String, extra: UrlScope.() -> Unit = {}): String { + val (token, salt) = tokenPair(session.password) + return url(session.baseUrl) { + path("rest", "$action.view") + param("u", session.username) + param("t", token) + param("s", salt) + param("v", API_VERSION) + param("c", session.clientName) + extra() + } + } + + private suspend fun apiGet( + session: Session, + action: String, + extra: UrlScope.() -> Unit = {} + ): String { + val requestUrl = restUrl(session, action) { + param("f", "json") + extra() + } + val response = http.get(requestUrl) + if (!response.status.isSuccess()) error("$action HTTP ${response.status.value}") + return response.bodyAsText() + } + + private fun tokenPair(password: String): Pair { + val salt = UUID.randomUUID().toString().replace("-", "").take(12) + val token = md5(password + salt) + return token to salt + } + + private fun md5(value: String): String { + val digest = MessageDigest.getInstance("MD5").digest(value.toByteArray(Charsets.UTF_8)) + return digest.joinToString("") { "%02x".format(it) } + } + + @Serializable + private data class SubsonicResponse( + @SerialName("subsonic-response") val subsonicResponse: SubsonicBody + ) + + @Serializable + private data class SubsonicBody( + val status: String? = null, + val type: String? = null, + val error: SubsonicError? = null, + val albumList2: SubsonicAlbumList? = null, + val album: SubsonicAlbumDetail? = null + ) + + @Serializable + private data class SubsonicError(val message: String? = null) + + @Serializable + private data class SubsonicAlbumList(val album: List = emptyList()) + + @Serializable + private data class SubsonicAlbum( + val id: String? = null, + val name: String? = null, + val artist: String? = null, + val coverArt: String? = null, + val year: Int? = null + ) + + @Serializable + private data class SubsonicAlbumDetail( + val id: String? = null, + val name: String? = null, + val artist: String? = null, + val albumArtist: String? = null, + val coverArt: String? = null, + val year: Int? = null, + val song: List = emptyList() + ) + + @Serializable + private data class SubsonicChild( + val id: String? = null, + val title: String? = null, + val name: String? = null, + val artist: String? = null, + val album: String? = null, + val coverArt: String? = null, + val duration: Int? = null, + val track: Int? = null, + val discNumber: Int? = null, + val year: Int? = null, + val genre: String? = null, + val isDir: Boolean? = null + ) + + companion object { + private const val API_VERSION = "1.16.1" + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt new file mode 100644 index 00000000..aacc1543 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt @@ -0,0 +1,83 @@ +package capital.yuri.yuriplayer.desktop + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import java.io.File +import java.util.Properties + +/** + * Desktop My Stuff: liked songs + pinned albums/artists, newest first. + */ +class DesktopCollection(configDir: String) { + private val file = File(configDir, "collection.properties") + + private val _liked = MutableStateFlow>(emptySet()) + val liked: StateFlow> = _liked.asStateFlow() + + private val _pinned = MutableStateFlow>(emptyList()) + val pinned: StateFlow> = _pinned.asStateFlow() + + data class Pin(val kind: Kind, val id: String, val title: String, val subtitle: String) + + enum class Kind { ALBUM, ARTIST, SONG } + + init { + load() + } + + fun isLiked(trackId: String): Boolean = trackId in _liked.value + + fun toggleLike(trackId: String) { + val next = _liked.value.toMutableSet() + if (!next.add(trackId)) next.remove(trackId) + _liked.value = next + persist() + } + + fun pin(pin: Pin) { + val cur = _pinned.value.toMutableList() + cur.removeAll { it.kind == pin.kind && it.id == pin.id } + cur.add(0, pin) + _pinned.value = cur + persist() + } + + fun unpin(kind: Kind, id: String) { + _pinned.value = _pinned.value.filterNot { it.kind == kind && it.id == id } + persist() + } + + fun isPinned(kind: Kind, id: String): Boolean = + _pinned.value.any { it.kind == kind && it.id == id } + + private fun load() { + if (!file.exists()) return + runCatching { + val p = Properties() + file.inputStream().use { p.load(it) } + _liked.value = p.getProperty("liked").orEmpty() + .split('|').filter { it.isNotBlank() }.toSet() + val pins = p.getProperty("pins").orEmpty().split('\n').mapNotNull { line -> + val parts = line.split('\t') + if (parts.size < 4) return@mapNotNull null + val kind = runCatching { Kind.valueOf(parts[0]) }.getOrNull() ?: return@mapNotNull null + Pin(kind, parts[1], parts[2], parts[3]) + } + _pinned.value = pins + } + } + + private fun persist() { + runCatching { + file.parentFile?.mkdirs() + val p = Properties() + p.setProperty("liked", _liked.value.joinToString("|")) + p.setProperty( + "pins", + _pinned.value.joinToString("\n") { "${it.kind}\t${it.id}\t${it.title}\t${it.subtitle}" } + ) + file.outputStream().use { p.store(it, "Yuri Player collection") } + } + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopLayoutStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopLayoutStore.kt new file mode 100644 index 00000000..55f6690e --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopLayoutStore.kt @@ -0,0 +1,61 @@ +package capital.yuri.yuriplayer.desktop + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import java.io.File +import java.util.Properties + +class DesktopLayoutStore(configDir: String) { + private val file = File(configDir, "layout.properties") + + private val _left = MutableStateFlow(DEFAULT_LEFT) + val leftFraction: StateFlow = _left.asStateFlow() + + private val _right = MutableStateFlow(DEFAULT_RIGHT) + val rightFraction: StateFlow = _right.asStateFlow() + + init { + load() + } + + fun setLeft(fraction: Float, persist: Boolean = true) { + _left.value = fraction.coerceIn(MIN_LEFT, MAX_LEFT) + if (persist) persist() + } + + fun setRight(fraction: Float, persist: Boolean = true) { + _right.value = fraction.coerceIn(MIN_RIGHT, MAX_RIGHT) + if (persist) persist() + } + + fun persist() { + runCatching { + file.parentFile?.mkdirs() + val p = Properties() + p.setProperty("left", _left.value.toString()) + p.setProperty("right", _right.value.toString()) + file.outputStream().use { p.store(it, "Yuri Player layout") } + } + } + + private fun load() { + if (!file.exists()) return + runCatching { + val p = Properties() + file.inputStream().use { p.load(it) } + p.getProperty("left")?.toFloatOrNull()?.let { _left.value = it.coerceIn(MIN_LEFT, MAX_LEFT) } + p.getProperty("right")?.toFloatOrNull()?.let { _right.value = it.coerceIn(MIN_RIGHT, MAX_RIGHT) } + } + } + + companion object { + const val DEFAULT_LEFT = 0.22f + const val DEFAULT_RIGHT = 0.26f + const val MIN_LEFT = 0.14f + const val MAX_LEFT = 0.38f + const val MIN_RIGHT = 0.16f + const val MAX_RIGHT = 0.42f + const val MIN_CENTER = 0.28f + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index d0389069..247a825f 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -6,8 +6,15 @@ import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.os.OsMediaControls import capital.yuri.yuriplayer.core.platform.appDirectories import capital.yuri.yuriplayer.core.player.PlayerSession +import capital.yuri.yuriplayer.core.source.JellyfinCatalog +import capital.yuri.yuriplayer.core.source.LibrarySourceStore +import capital.yuri.yuriplayer.core.source.RemoteAccount +import capital.yuri.yuriplayer.core.source.SourceKind +import capital.yuri.yuriplayer.core.source.SubsonicCatalog import capital.yuri.yuriplayer.desktop.os.createOsMediaControls import capital.yuri.yuriplayer.desktop.player.VlcjPlaybackEngine +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -20,12 +27,14 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.swing.Swing import java.io.File +import java.util.UUID class DesktopSession { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Swing) private val engine = VlcjPlaybackEngine() val player = PlayerSession(engine) private val media: OsMediaControls = createOsMediaControls() + private val http = HttpClient(CIO) private val _engineMessage = MutableStateFlow(engine.nativeError) val engineMessage: StateFlow = _engineMessage.asStateFlow() @@ -46,6 +55,12 @@ class DesktopSession { val coverPixels: StateFlow = _coverPixels.asStateFlow() val dirs = appDirectories() + val theme = DesktopThemeStore(dirs.configDir) + val layout = DesktopLayoutStore(dirs.configDir) + val collection = DesktopCollection(dirs.configDir) + val sources = LibrarySourceStore(dirs.configDir) + val jellyfin = JellyfinCatalog(http, sources.deviceId) + val subsonic = SubsonicCatalog(http) private var ticker: Job? = null @@ -85,21 +100,7 @@ class DesktopSession { _coverPixels.value = CoverPixels.argb(track?.artworkUri, track?.path) } } - scope.launch(Dispatchers.IO) { - val roots = LocalLibraryScanner.defaultRoots() - _scanMessage.value = if (roots.isEmpty()) { - "No default music folder found" - } else { - "Scanning ${roots.joinToString { it.name }}…" - } - val found = LocalLibraryScanner.scan(roots) - _tracks.value = found - _scanMessage.value = when { - found.isEmpty() -> - "No audio in ${roots.joinToString { it.absolutePath }}" - else -> "${found.size} tracks" - } - } + rescan() } fun playTrack(track: Track) { @@ -108,21 +109,108 @@ class DesktopSession { player.play(if (list.isNotEmpty()) list else listOf(track), i) } - fun extraScan(path: String) { + fun addFolder(path: String) { + sources.addFolder(path) + rescan() + } + + fun removeFolder(path: String) { + sources.removeFolder(path) + rescan() + } + + fun addJellyfin(name: String, baseUrl: String, username: String, password: String) { + scope.launch(Dispatchers.IO) { + _scanMessage.value = "Signing in to Jellyfin…" + val seed = RemoteAccount( + id = UUID.randomUUID().toString(), + kind = SourceKind.JELLYFIN, + name = name.ifBlank { "Jellyfin" }, + baseUrl = baseUrl, + username = username, + secret = password + ) + jellyfin.authenticate(seed) + .onSuccess { + sources.upsertRemote(it) + rescan() + } + .onFailure { _scanMessage.value = "Jellyfin: ${it.message}" } + } + } + + fun addSubsonic(name: String, baseUrl: String, username: String, password: String) { + scope.launch(Dispatchers.IO) { + _scanMessage.value = "Signing in to Subsonic…" + val seed = RemoteAccount( + id = UUID.randomUUID().toString(), + kind = SourceKind.SUBSONIC, + name = name.ifBlank { "Subsonic" }, + baseUrl = baseUrl, + username = username, + secret = password + ) + subsonic.ping(seed) + .onSuccess { + sources.upsertRemote(it) + rescan() + } + .onFailure { _scanMessage.value = "Subsonic: ${it.message}" } + } + } + + fun removeRemote(id: String) { + sources.removeRemote(id) + rescan() + } + + fun rescan() { scope.launch(Dispatchers.IO) { - val dir = File(path) - if (!dir.isDirectory) return@launch - val extra = LocalLibraryScanner.scan(listOf(dir)) - val merged = (_tracks.value + extra).distinctBy { it.id } + val defaultRoots = LocalLibraryScanner.defaultRoots() + val extra = sources.extraFolders.value.map { File(it) }.filter { it.isDirectory } + val roots = (defaultRoots + extra).distinctBy { it.absolutePath } + _scanMessage.value = if (roots.isEmpty()) { + "No local folders yet" + } else { + "Scanning ${roots.joinToString { it.name }}…" + } + val local = if (roots.isEmpty()) emptyList() else LocalLibraryScanner.scan(roots) + val remoteTracks = mutableListOf() + for (remote in sources.remotes.value.filter { it.enabled }) { + _scanMessage.value = "Indexing ${remote.name}…" + val result = when (remote.kind) { + SourceKind.JELLYFIN -> { + val authed = if (remote.accessToken.isNullOrBlank()) { + jellyfin.authenticate(remote).getOrElse { + _scanMessage.value = "${remote.name}: ${it.message}" + continue + }.also { sources.upsertRemote(it) } + } else remote + jellyfin.listTracks(authed) + } + SourceKind.SUBSONIC -> subsonic.listTracks(remote) + SourceKind.LOCAL -> continue + } + result + .onSuccess { remoteTracks += it } + .onFailure { _scanMessage.value = "${remote.name}: ${it.message}" } + } + val merged = (local + remoteTracks).distinctBy { it.id } _tracks.value = merged - _scanMessage.value = "${merged.size} tracks" + _scanMessage.value = when { + merged.isEmpty() -> "No tracks yet — add a folder or a server in Settings → Library" + else -> "${merged.size} tracks" + } } } + fun extraScan(path: String) = addFolder(path) + fun release() { ticker?.cancel() media.release() player.release() + runCatching { http.close() } scope.coroutineContext[Job]?.cancel() } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopThemeStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopThemeStore.kt new file mode 100644 index 00000000..13fe8c72 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopThemeStore.kt @@ -0,0 +1,50 @@ +package capital.yuri.yuriplayer.desktop + +import capital.yuri.yuriplayer.components.theme.AccentCatalog +import capital.yuri.yuriplayer.components.theme.ThemeChoice +import capital.yuri.yuriplayer.components.theme.ThemeFamily +import capital.yuri.yuriplayer.components.theme.ThemeMode +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import java.io.File +import java.util.Properties + +class DesktopThemeStore(configDir: String) { + private val file = File(configDir, "theme.properties") + private val _choice = MutableStateFlow(load()) + val choice: StateFlow = _choice.asStateFlow() + + fun setMode(mode: ThemeMode) = update(_choice.value.copy(mode = mode)) + + fun setAccent(id: String) = update(_choice.value.copy(accentId = AccentCatalog.byId(id).id)) + + private fun update(next: ThemeChoice) { + _choice.value = next + persist(next) + } + + private fun load(): ThemeChoice { + if (!file.exists()) return ThemeChoice() + return runCatching { + val p = Properties() + file.inputStream().use { p.load(it) } + ThemeChoice( + mode = ThemeMode.fromId(p.getProperty("mode")), + accentId = p.getProperty("accent") ?: AccentCatalog.yuri.id, + family = ThemeFamily.MATERIAL3 + ) + }.getOrElse { ThemeChoice() } + } + + private fun persist(choice: ThemeChoice) { + runCatching { + file.parentFile?.mkdirs() + val p = Properties() + p.setProperty("mode", choice.mode.id) + p.setProperty("accent", choice.accentId) + p.setProperty("family", choice.family.id) + file.outputStream().use { p.store(it, "Yuri Player theme") } + } + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt index 6a304e05..45c8003b 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt @@ -1,11 +1,15 @@ package capital.yuri.yuriplayer.desktop +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState +import capital.yuri.yuriplayer.components.theme.isDark import capital.yuri.yuriplayer.core.platform.appDirectories import capital.yuri.yuriplayer.desktop.player.LibVlcBootstrap import capital.yuri.yuriplayer.desktop.ui.YuriDesktopApp @@ -47,6 +51,10 @@ fun main() { onDispose { session.release() } } YuriDesktopApp(session) + val choice by session.theme.choice.collectAsState() + val dark = choice.isDark(isSystemInDarkTheme()) + window.background = if (dark) java.awt.Color(0x12, 0x10, 0x18) + else java.awt.Color(0xF6, 0xF0, 0xFA) } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index f1bc18b0..98b7a66c 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -153,6 +153,10 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun getDurationMs(): Long = durationMs.get() + override fun setVolume(percent: Int) { + onVlc { player?.audio()?.setVolume(percent.coerceIn(0, 100)) } + } + override fun setNext(item: PlaybackMedia?) { preparedNext.set(item) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt index b0c6dd0f..fbcde9d0 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt @@ -1,6 +1,8 @@ package capital.yuri.yuriplayer.desktop.ui +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -8,22 +10,49 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Folder +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import capital.yuri.yuriplayer.components.settings.SettingsCategory import capital.yuri.yuriplayer.components.settings.SettingsShell +import capital.yuri.yuriplayer.components.theme.AccentPicker +import capital.yuri.yuriplayer.components.theme.ThemeModePicker +import capital.yuri.yuriplayer.core.platform.appDirectories +import capital.yuri.yuriplayer.core.source.SourceKind +import capital.yuri.yuriplayer.desktop.DesktopSession +import javax.swing.JFileChooser @Composable -fun DesktopSettingsDialog(onDismiss: () -> Unit) { +fun DesktopSettingsDialog( + session: DesktopSession, + onDismiss: () -> Unit +) { var category by remember { mutableStateOf(SettingsCategory.Appearance) } Dialog( onDismissRequest = onDismiss, @@ -46,9 +75,9 @@ fun DesktopSettingsDialog(onDismiss: () -> Unit) { ) ) { when (category) { - SettingsCategory.Appearance -> AppearancePane() + SettingsCategory.Appearance -> AppearancePane(session) SettingsCategory.Playback -> PlaybackPane() - SettingsCategory.Library -> LibraryPane() + SettingsCategory.Library -> LibraryPane(session) SettingsCategory.About -> AboutPane() } } @@ -56,16 +85,27 @@ fun DesktopSettingsDialog(onDismiss: () -> Unit) { } @Composable -private fun AppearancePane() { +private fun AppearancePane(session: DesktopSession) { + val choice by session.theme.choice.collectAsState() Column { Text( - "Yuri’s dark stage plus colors from whatever is playing. " + - "System Material You is an Android thing.", + "Dark, light, or follow the system. Accent is the Material 3 primary. " + + "Cover art still tints now playing and the play button.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) ) Spacer(Modifier.height(16.dp)) - Text("Cover art tints the now-playing rail and the play button.", color = MaterialTheme.colorScheme.onSurface) + Text("Theme", style = MaterialTheme.typography.titleSmall) + Spacer(Modifier.height(8.dp)) + ThemeModePicker(mode = choice.mode, onMode = session.theme::setMode) + Spacer(Modifier.height(20.dp)) + AccentPicker(selectedId = choice.accentId, onSelect = session.theme::setAccent) + Spacer(Modifier.height(20.dp)) + Text( + "Jewel, macOS, and Windows families are next — they won’t depend on Material 3.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + ) } } @@ -75,25 +115,158 @@ private fun PlaybackPane() { Text("Audio engine", style = MaterialTheme.typography.titleMedium) Spacer(Modifier.height(8.dp)) Text( - "Desktop uses LibVLC (vlcj). That’s the same family as the default " + - "Android engine. Media3 and FFmpeg are phone-only options.", + "Desktop uses LibVLC (vlcj). Same family as the default Android engine. " + + "Media3 is phone-only.", color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f) ) Spacer(Modifier.height(12.dp)) - Text("LibVLC — bundled on Windows/macOS, system package on Linux.", color = MaterialTheme.colorScheme.onSurface) + Text("LibVLC — bundled on Windows/macOS, system VLC on Linux packages.", color = MaterialTheme.colorScheme.onSurface) } } @Composable -private fun LibraryPane() { - Column { +private fun LibraryPane(session: DesktopSession) { + val folders by session.sources.extraFolders.collectAsState() + val remotes by session.sources.remotes.collectAsState() + val status by session.scanMessage.collectAsState() + val defaults = remember { appDirectories().defaultMusicRoots } + var jfName by remember { mutableStateOf("") } + var jfUrl by remember { mutableStateOf("") } + var jfUser by remember { mutableStateOf("") } + var jfPass by remember { mutableStateOf("") } + var ssName by remember { mutableStateOf("") } + var ssUrl by remember { mutableStateOf("") } + var ssUser by remember { mutableStateOf("") } + var ssPass by remember { mutableStateOf("") } + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + Text(status, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.primary) + Spacer(Modifier.height(16.dp)) + Text("Local folders", style = MaterialTheme.typography.titleMedium) Text( - "Scans the OS music folder on launch (XDG Music, ~/Music, or %USERPROFILE%\\Music).", - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f) + "Default music folders are scanned automatically.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + defaults.forEach { path -> + Text(path, modifier = Modifier.padding(top = 4.dp), color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)) + } + folders.forEach { path -> + Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + Text(path, modifier = Modifier.weight(1f)) + TextButton(onClick = { session.removeFolder(path) }) { Text("Remove") } + } + } + Spacer(Modifier.height(8.dp)) + OutlinedButton(onClick = { + pickDirectory()?.let { session.addFolder(it) } + }) { + Icon(Icons.Default.Folder, contentDescription = null) + Text(" Add folder") + } + Spacer(Modifier.height(24.dp)) + Text("Servers", style = MaterialTheme.typography.titleMedium) + Text( + "Jellyfin and Subsonic / Navidrome / OpenSubsonic — same as on Android.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) + remotes.forEach { remote -> + Row(Modifier.fillMaxWidth().padding(top = 8.dp), verticalAlignment = Alignment.CenterVertically) { + Column(Modifier.weight(1f)) { + Text(remote.name.ifBlank { remote.kind.name }) + Text( + "${remote.kind.name.lowercase()} · ${remote.baseUrl}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + TextButton(onClick = { session.removeRemote(remote.id) }) { Text("Remove") } + } + } + Spacer(Modifier.height(20.dp)) + Text("Add Jellyfin", style = MaterialTheme.typography.titleSmall) + ServerForm( + name = jfName, onName = { jfName = it }, + url = jfUrl, onUrl = { jfUrl = it }, + user = jfUser, onUser = { jfUser = it }, + password = jfPass, onPassword = { jfPass = it }, + urlHint = "https://jellyfin.example.com" + ) + Button( + onClick = { + session.addJellyfin(jfName, jfUrl, jfUser, jfPass) + jfPass = "" + }, + enabled = jfUrl.isNotBlank() && jfUser.isNotBlank() && jfPass.isNotBlank() + ) { Text("Connect Jellyfin") } + Spacer(Modifier.height(20.dp)) + Text("Add Subsonic / Navidrome", style = MaterialTheme.typography.titleSmall) + ServerForm( + name = ssName, onName = { ssName = it }, + url = ssUrl, onUrl = { ssUrl = it }, + user = ssUser, onUser = { ssUser = it }, + password = ssPass, onPassword = { ssPass = it }, + urlHint = "https://navidrome.example.com" + ) + Button( + onClick = { + session.addSubsonic(ssName, ssUrl, ssUser, ssPass) + ssPass = "" + }, + enabled = ssUrl.isNotBlank() && ssUser.isNotBlank() && ssPass.isNotBlank() + ) { Text("Connect Subsonic") } + Spacer(Modifier.height(16.dp)) + OutlinedButton(onClick = { session.rescan() }) { Text("Rescan libraries") } } } +@Composable +private fun ServerForm( + name: String, + onName: (String) -> Unit, + url: String, + onUrl: (String) -> Unit, + user: String, + onUser: (String) -> Unit, + password: String, + onPassword: (String) -> Unit, + urlHint: String +) { + var showPass by remember { mutableStateOf(false) } + Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(vertical = 8.dp)) { + OutlinedTextField(name, onName, label = { Text("Label") }, singleLine = true, modifier = Modifier.fillMaxWidth()) + OutlinedTextField(url, onUrl, label = { Text("Server URL") }, placeholder = { Text(urlHint) }, singleLine = true, modifier = Modifier.fillMaxWidth()) + OutlinedTextField(user, onUser, label = { Text("Username") }, singleLine = true, modifier = Modifier.fillMaxWidth()) + OutlinedTextField( + password, + onPassword, + label = { Text("Password") }, + singleLine = true, + modifier = Modifier.fillMaxWidth(), + visualTransformation = if (showPass) VisualTransformation.None else PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + trailingIcon = { + IconButton(onClick = { showPass = !showPass }) { + Icon( + if (showPass) Icons.Default.VisibilityOff else Icons.Default.Visibility, + contentDescription = if (showPass) "Hide password" else "Show password" + ) + } + } + ) + } +} + +private fun pickDirectory(): String? { + val chooser = JFileChooser() + chooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY + chooser.dialogTitle = "Add music folder" + chooser.isAcceptAllFileFilterUsed = false + return if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { + chooser.selectedFile?.absolutePath + } else null +} + @Composable private fun AboutPane() { Column(Modifier.fillMaxSize().padding(top = 8.dp)) { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 27aa122c..b0080b4b 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -1,17 +1,31 @@ package capital.yuri.yuriplayer.desktop.ui +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.FilterChip import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -20,10 +34,16 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.album.AlbumPage +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.layout.DesktopBentoLayout import capital.yuri.yuriplayer.components.layout.DesktopNav -import capital.yuri.yuriplayer.components.layout.SpotifyShell +import capital.yuri.yuriplayer.components.layout.LibraryFilter +import capital.yuri.yuriplayer.components.layout.LibraryRailItem import capital.yuri.yuriplayer.components.list.AlbumCard import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.model.albums @@ -34,10 +54,13 @@ import capital.yuri.yuriplayer.components.player.NowPlayingSidebar import capital.yuri.yuriplayer.components.theme.PlayerChromeTheme import capital.yuri.yuriplayer.components.theme.YuriTheme import capital.yuri.yuriplayer.components.theme.playerColorsFromPixels +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.desktop.DesktopCollection import capital.yuri.yuriplayer.desktop.DesktopSession private sealed class Route { - data object Library : Route() + data object Home : Route() + data object Search : Route() data class Album(val album: AlbumPageModel) : Route() } @@ -47,7 +70,8 @@ fun YuriDesktopApp(session: DesktopSession) { val playerColors = remember(coverPixels) { coverPixels?.let { playerColorsFromPixels(it) } } - YuriTheme { + val themeChoice by session.theme.choice.collectAsState() + YuriTheme(choice = themeChoice) { val tracks by session.tracks.collectAsState() val current by session.player.current.collectAsState() val playing by session.player.isPlaying.collectAsState() @@ -57,19 +81,141 @@ fun YuriDesktopApp(session: DesktopSession) { val position by session.positionMs.collectAsState() val duration by session.durationMs.collectAsState() val engineMessage by session.engineMessage.collectAsState() + val shuffle by session.player.shuffle.collectAsState() + val repeat by session.player.repeat.collectAsState() + val volume by session.player.volume.collectAsState() + val liked by session.collection.liked.collectAsState() + val pins by session.collection.pinned.collectAsState() + val leftFrac by session.layout.leftFraction.collectAsState() + val rightFrac by session.layout.rightFraction.collectAsState() val albums = remember(tracks) { tracks.albums() } - var route by remember { mutableStateOf(Route.Library) } - var nav by remember { mutableStateOf(DesktopNav.Home) } - var showSettings by remember { mutableStateOf(false) } + var stack by remember { mutableStateOf(listOf(Route.Home)) } + var forward by remember { mutableStateOf(listOf()) } var query by remember { mutableStateOf("") } + var libraryFilter by remember { mutableStateOf(LibraryFilter.Recents) } + var showSettings by remember { mutableStateOf(false) } + var showSidebar by remember { mutableStateOf(true) } + val route = stack.last() + val nav = if (route is Route.Search) DesktopNav.Search else DesktopNav.Home + + fun push(next: Route) { + stack = stack + next + forward = emptyList() + } + + fun goBack() { + if (stack.size > 1) { + forward = listOf(stack.last()) + forward + stack = stack.dropLast(1) + } + } + + fun goForward() { + val n = forward.firstOrNull() ?: return + forward = forward.drop(1) + stack = stack + n + } + + fun openAlbum(album: AlbumPageModel) = push(Route.Album(album)) - SpotifyShell( + val recency = remember(history) { + history.mapIndexed { i, t -> t.id to i }.toMap() + } + fun albumRecency(album: AlbumPageModel): Int = + album.tracks.minOfOrNull { recency[it.id] ?: Int.MAX_VALUE } ?: Int.MAX_VALUE + + val libraryItems = remember(albums, tracks, libraryFilter, pins, history, liked) { + val pinItems = pins.map { pin -> + when (pin.kind) { + DesktopCollection.Kind.ALBUM -> { + val album = albums.firstOrNull { it.id == pin.id } + LibraryRailItem( + pin.id, pin.title, pin.subtitle, + album?.artworkUri, pinned = true + ) + } + DesktopCollection.Kind.ARTIST -> LibraryRailItem( + pin.id, pin.title, pin.subtitle, + tracks.firstOrNull { it.displayArtist == pin.title }?.artworkUri, + circular = true, pinned = true + ) + DesktopCollection.Kind.SONG -> { + val t = tracks.firstOrNull { it.id == pin.id } + LibraryRailItem( + pin.id, pin.title, pin.subtitle, + t?.artworkUri, pinned = true + ) + } + } + } + val rest = when (libraryFilter) { + LibraryFilter.Artists -> tracks + .map { it.displayArtist } + .distinct() + .map { name -> + val cover = tracks.firstOrNull { it.displayArtist == name }?.artworkUri + val rec = tracks.filter { it.displayArtist == name } + .minOfOrNull { recency[it.id] ?: Int.MAX_VALUE } ?: Int.MAX_VALUE + Triple(rec, name, cover) + } + .sortedBy { it.first } + .map { (_, name, cover) -> + LibraryRailItem("artist:$name", name, "Artist", cover, circular = true) + } + LibraryFilter.Albums -> albums + .sortedBy { albumRecency(it) } + .map { LibraryRailItem(it.id, it.title, it.artist, it.artworkUri) } + LibraryFilter.Playlists, LibraryFilter.Recents -> albums + .sortedBy { albumRecency(it) } + .map { + LibraryRailItem( + it.id, it.title, + if (albumRecency(it) < Int.MAX_VALUE) "Album · ${it.artist}" else "Album · ${it.artist}", + it.artworkUri + ) + } + } + val pinIds = pinItems.map { it.id }.toSet() + pinItems + rest.filter { it.id !in pinIds } + } + val selectedLibraryId = (route as? Route.Album)?.album?.id + val visibleAlbums = remember(albums, query) { + if (query.isBlank()) albums + else albums.filter { + it.title.contains(query, true) || it.artist.contains(query, true) + } + } + + DesktopBentoLayout( nav = nav, - onNav = { - nav = it - route = Route.Library + searchQuery = query, + onSearchQuery = { + query = it + if (it.isNotBlank() && route !is Route.Search) push(Route.Search) }, + onHome = { stack = listOf(Route.Home); forward = emptyList(); query = "" }, + onBack = ::goBack, + onForward = ::goForward, + canBack = stack.size > 1, + canForward = forward.isNotEmpty(), onSettings = { showSettings = true }, + libraryFilter = libraryFilter, + onLibraryFilter = { libraryFilter = it }, + libraryItems = libraryItems, + selectedLibraryId = selectedLibraryId, + onLibraryItem = { item -> + if (item.id.startsWith("artist:")) { + query = item.title + push(Route.Search) + } else { + albums.firstOrNull { it.id == item.id }?.let { openAlbum(it) } + } + }, + showSidebar = showSidebar, + leftFraction = leftFrac, + rightFraction = rightFrac, + onLeftFraction = { f, persist -> session.layout.setLeft(f, persist) }, + onRightFraction = { f, persist -> session.layout.setRight(f, persist) }, bottomBar = { BottomPlayerBar( track = current?.toCover(), @@ -80,7 +226,31 @@ fun YuriDesktopApp(session: DesktopSession) { onToggle = session.player::togglePlay, onNext = session.player::next, onSeek = session.player::seekTo, - accent = playerColors?.accent ?: MaterialTheme.colorScheme.primary + accent = playerColors?.accent ?: MaterialTheme.colorScheme.primary, + shuffle = shuffle, + repeat = repeat, + onToggleShuffle = session.player::toggleShuffle, + onCycleRepeat = session.player::cycleRepeat, + volume = volume, + onVolume = session.player::setVolume, + liked = current?.id in liked, + onToggleLike = { + val t = current ?: return@BottomPlayerBar + session.collection.toggleLike(t.id) + val album = albums.firstOrNull { a -> a.tracks.any { it.id == t.id } } + if (album != null && session.collection.isLiked(t.id)) { + session.collection.pin( + DesktopCollection.Pin( + DesktopCollection.Kind.ALBUM, + album.id, + album.title, + album.artist + ) + ) + } + }, + queueVisible = showSidebar, + onToggleQueue = { showSidebar = !showSidebar } ) }, sidebar = { @@ -104,18 +274,18 @@ fun YuriDesktopApp(session: DesktopSession) { } ) { when (val r = route) { - Route.Library -> LibraryGrid( - albums = albums.filter { - nav != DesktopNav.Search || query.isBlank() || - it.title.contains(query, true) || - it.artist.contains(query, true) - }, + Route.Home -> HomeFeed( + albums = albums, + recents = history, status = engineMessage ?: status, - roots = session.dirs.defaultMusicRoots, - showSearch = nav == DesktopNav.Search, - query = query, - onQuery = { query = it }, - onOpen = { route = Route.Album(it) } + onOpenAlbum = ::openAlbum, + onPlayRecent = { t -> session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) } + ) + Route.Search -> LibraryGrid( + albums = visibleAlbums, + status = if (query.isBlank()) "Search albums and artists" else "${visibleAlbums.size} matches", + emptyHint = if (query.isBlank()) "Type in the search bar." else "Nothing matches.", + onOpen = ::openAlbum ) is Route.Album -> { val live = albums.firstOrNull { it.id == r.album.id } ?: r.album @@ -129,7 +299,7 @@ fun YuriDesktopApp(session: DesktopSession) { } ), playing = playing && albumTracks.any { it.id == current?.id }, - onBack = { route = Route.Library }, + onBack = ::goBack, onPlay = { if (albumTracks.isNotEmpty()) session.player.play(albumTracks, 0) }, @@ -141,52 +311,162 @@ fun YuriDesktopApp(session: DesktopSession) { } } if (showSettings) { - DesktopSettingsDialog(onDismiss = { showSettings = false }) + DesktopSettingsDialog( + session = session, + onDismiss = { showSettings = false } + ) } } } @Composable -private fun LibraryGrid( +private fun HomeFeed( albums: List, + recents: List, status: String, - roots: List, - showSearch: Boolean, - query: String, - onQuery: (String) -> Unit, - onOpen: (AlbumPageModel) -> Unit + onOpenAlbum: (AlbumPageModel) -> Unit, + onPlayRecent: (Track) -> Unit ) { - Column(Modifier.fillMaxSize().padding(horizontal = 20.dp, vertical = 16.dp)) { + val recentAlbums = remember(recents, albums) { + recents.mapNotNull { t -> + albums.firstOrNull { a -> a.tracks.any { it.id == t.id } } + }.distinctBy { it.id } + } + Column( + Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(20.dp) + ) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + FilterChip(selected = true, onClick = {}, label = { Text("All") }) + FilterChip(selected = false, onClick = {}, label = { Text("Music") }) + } + Spacer(Modifier.height(16.dp)) + if (recentAlbums.isNotEmpty() || recents.isNotEmpty()) { + recentAlbums.take(8).chunked(4).forEach { row -> + Row( + Modifier.fillMaxWidth().padding(bottom = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + row.forEach { album -> + Surface( + modifier = Modifier.weight(1f).height(64.dp).clip(RoundedCornerShape(8.dp)) + .clickable { onOpenAlbum(album) }, + color = MaterialTheme.colorScheme.surfaceVariant, + shape = RoundedCornerShape(8.dp) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + CoverArt(model = album.artworkUri, size = 64.dp, corner = 0.dp) + Text( + album.title, + modifier = Modifier.padding(horizontal = 12.dp), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + repeat(4 - row.size) { Spacer(Modifier.weight(1f)) } + } + } + Spacer(Modifier.height(16.dp)) + } Text( - if (showSearch) "Search" else "Library", - style = MaterialTheme.typography.headlineSmall, - color = MaterialTheme.colorScheme.onBackground + status, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + modifier = Modifier.padding(bottom = 8.dp) ) - if (showSearch) { - OutlinedTextField( - value = query, - onValueChange = onQuery, - modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), - placeholder = { Text("Albums and artists") }, - singleLine = true, - shape = MaterialTheme.shapes.large - ) + if (recents.isNotEmpty()) { + SectionHeader("Recents") + LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + items(recents.take(12), key = { it.id }) { track -> + AlbumCard( + album = AlbumPageModel( + id = track.id, + title = track.displayTitle, + artist = track.displayArtist, + artworkUri = track.artworkUri, + tracks = emptyList() + ), + onClick = { onPlayRecent(track) } + ) + } + } + Spacer(Modifier.height(24.dp)) + } + if (albums.isNotEmpty()) { + SectionHeader("Albums") + LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + items(albums.shuffled().take(12), key = { it.id }) { album -> + AlbumCard(album = album, onClick = { onOpenAlbum(album) }) + } + } + Spacer(Modifier.height(24.dp)) + val artists = albums.map { it.artist }.distinct().take(12) + SectionHeader("Artists") + LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + items(artists, key = { it }) { name -> + val cover = albums.firstOrNull { it.artist == name }?.artworkUri + Column( + Modifier.width(140.dp).clickable { + albums.firstOrNull { it.artist == name }?.let(onOpenAlbum) + }, + horizontalAlignment = Alignment.CenterHorizontally + ) { + CoverArt(model = cover, size = 128.dp, corner = 64.dp) + Text( + name, + modifier = Modifier.padding(top = 8.dp), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Medium + ) + } + } + } } else { Text( - status, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), - modifier = Modifier.padding(bottom = 12.dp, top = 4.dp) + "Nothing in the default music folders yet.", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) } + } +} + +@Composable +private fun SectionHeader(title: String) { + Row( + Modifier.fillMaxWidth().padding(bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text(title, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Spacer(Modifier.weight(1f)) + TextButton(onClick = {}) { Text("Show all") } + } +} + +@Composable +private fun LibraryGrid( + albums: List, + status: String, + emptyHint: String, + onOpen: (AlbumPageModel) -> Unit +) { + Column(Modifier.fillMaxSize().padding(horizontal = 20.dp, vertical = 16.dp)) { + Text("Search", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) + Text( + status, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(bottom = 12.dp, top = 4.dp) + ) if (albums.isEmpty()) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Text( - if (showSearch) "Nothing matches." - else "Nothing in the default music folders yet.\n" + - roots.joinToString("\n").ifBlank { "Add files to Music, then restart." }, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) - ) + Text(emptyHint, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)) } } else { LazyVerticalGrid( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ded124e2..5a3bab61 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -84,6 +84,7 @@ dbus-java-transport-native-unix = { group = "com.github.hypfvieh", name = "dbus- jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" } jna-platform = { group = "net.java.dev.jna", name = "jna-platform", version.ref = "jna" } +ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } From 4ed1324cf80a17ac3dd83c10ee5f2a9ca01d6ac5 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:07:05 +0000 Subject: [PATCH 10/69] Fix HomeFeedScreen compile: RowScope and nullable titles HomeRow's slot was typed as Row, which is a composable not a receiver. Use RowScope, and pass AlbumItem/ArtistItem display strings. --- .../yuri/yuriplayer/activities/ui/HomeFeedScreen.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt index 1b18aa49..3206fcbf 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -149,8 +150,8 @@ fun HomeFeedScreen( HomeRow(title = "Random albums") { stuffAlbums.shuffled().take(10).forEach { album -> CoverTile( - title = album.name, - subtitle = album.artist, + title = album.displayName, + subtitle = album.displayArtist, onClick = { onOpenAlbum(album) }, art = { AlbumArt(song = album.songs.firstOrNull(), size = 112.dp, corner = 8.dp) } ) @@ -163,10 +164,10 @@ fun HomeFeedScreen( HomeRow(title = "Random artists") { stuffArtists.shuffled().take(10).forEach { artist -> CoverTile( - title = artist.name, + title = artist.displayName, subtitle = "${artist.albumCount} albums", onClick = { onOpenArtist(artist) }, - art = { ArtistArt(artistName = artist.name, size = 112.dp, circular = true) } + art = { ArtistArt(artistName = artist.displayName, size = 112.dp, circular = true) } ) } } @@ -222,7 +223,7 @@ fun HomeFeedScreen( @Composable private fun HomeRow( title: String, - content: @Composable Row.() -> Unit + content: @Composable RowScope.() -> Unit ) { Column(Modifier.fillMaxWidth().padding(top = 8.dp)) { Text( From 8b86c4d75a1600b3e5ae63f26975290672c99028 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:12:01 +0000 Subject: [PATCH 11/69] Let Home pins scroll with the rest of the feed Drop the 380dp nested LazyVerticalGrid so pins sit at the top of the page and the whole Home list is the only scroller. --- .../activities/ui/HomeFeedScreen.kt | 52 +++++++++---------- .../activities/ui/MyStuffPinsHost.kt | 52 +++++++++---------- 2 files changed, 49 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt index 3206fcbf..5d85d623 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt @@ -4,7 +4,6 @@ import android.widget.Toast import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope @@ -12,7 +11,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -102,34 +100,32 @@ fun HomeFeedScreen( LazyColumn(Modifier.fillMaxSize().testTag(TestTags.MYSTUFF_PINS)) { item { - Box(Modifier.heightIn(max = 380.dp)) { - MyStuffPinsHost( - pins = pins, - library = library, - playlists = playlists, - allSongs = allSongs, - onOpenPin = { pin -> - scope.launch { - openPin( - pin, library, playlists, allSongs, catalog, - onOpenAlbum, onOpenArtist, onOpenPlaylist, onOpenSongAlbum, context - ) - } - }, - onUnpin = { pinStore.unpin(it) }, - onAddPinSlot = { showAddPin = true }, - onPlayAll = { - scope.launch { - val songs = resolveCollectionSongs(entries, library, playlistsRepo) - if (songs.isEmpty()) { - Toast.makeText(context, "Nothing in My Stuff yet", Toast.LENGTH_SHORT).show() - } else { - player.startPlaylistRadio(songs, "My Stuff") - } + MyStuffPinsHost( + pins = pins, + library = library, + playlists = playlists, + allSongs = allSongs, + onOpenPin = { pin -> + scope.launch { + openPin( + pin, library, playlists, allSongs, catalog, + onOpenAlbum, onOpenArtist, onOpenPlaylist, onOpenSongAlbum, context + ) + } + }, + onUnpin = { pinStore.unpin(it) }, + onAddPinSlot = { showAddPin = true }, + onPlayAll = { + scope.launch { + val songs = resolveCollectionSongs(entries, library, playlistsRepo) + if (songs.isEmpty()) { + Toast.makeText(context, "Nothing in My Stuff yet", Toast.LENGTH_SHORT).show() + } else { + player.startPlaylistRadio(songs, "My Stuff") } } - ) - } + } + ) } if (HomeRowId.RECENTS in rows && historyEntries.isNotEmpty()) { item { diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt index a3310101..00b64385 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio @@ -19,8 +18,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -89,7 +86,7 @@ fun MyStuffPinsHost( pins.map { HostPinCell.Filled(it) } + List(emptyCount) { HostPinCell.Empty } } - Column(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier.fillMaxWidth()) { Row( modifier = Modifier .fillMaxWidth() @@ -117,32 +114,33 @@ fun MyStuffPinsHost( } } - LazyVerticalGrid( - columns = GridCells.Fixed(2), - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 8.dp, top = 4.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 20.dp, end = 20.dp, bottom = 8.dp, top = 4.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - items( - cells.size, - key = { i -> - when (val c = cells[i]) { - is HostPinCell.Filled -> c.pin.key - is HostPinCell.Empty -> "empty-$i" + cells.chunked(2).forEach { pair -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + pair.forEach { cell -> + Box(Modifier.weight(1f)) { + when (cell) { + is HostPinCell.Filled -> HostPinCard( + pin = cell.pin, + library = library, + playlists = playlists, + allSongs = allSongs, + onClick = { onOpenPin(cell.pin) }, + onLongClick = { pinForSheet = cell.pin } + ) + is HostPinCell.Empty -> HostEmptyPin(onClick = onAddPinSlot) + } + } } - } - ) { i -> - when (val cell = cells[i]) { - is HostPinCell.Filled -> HostPinCard( - pin = cell.pin, - library = library, - playlists = playlists, - allSongs = allSongs, - onClick = { onOpenPin(cell.pin) }, - onLongClick = { pinForSheet = cell.pin } - ) - is HostPinCell.Empty -> HostEmptyPin(onClick = onAddPinSlot) + if (pair.size == 1) Spacer(Modifier.weight(1f)) } } } From 180d105f389dd5052ee1fd559ff6e718a1d07ac2 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:14:32 +0000 Subject: [PATCH 12/69] One mobile chrome: Home / My Stuff / Explore Bottom nav and the top bar share those names. Drop the extra Catalog and My Stuff headlines so the tab title is not doubled. --- .../capital/yuri/yuriplayer/DeviceUiTest.kt | 6 ++-- .../yuriplayer/activities/MainActivity.kt | 29 ++++++++++++------- .../yuriplayer/activities/ui/ExploreScreen.kt | 2 +- .../activities/ui/MyStuffCatalogTab.kt | 14 +-------- .../activities/ui/MyStuffPinsHost.kt | 11 ++----- .../capital/yuri/yuriplayer/ui/TestTags.kt | 3 +- 6 files changed, 29 insertions(+), 36 deletions(-) diff --git a/app/src/androidTest/java/capital/yuri/yuriplayer/DeviceUiTest.kt b/app/src/androidTest/java/capital/yuri/yuriplayer/DeviceUiTest.kt index c20b895e..50139d25 100644 --- a/app/src/androidTest/java/capital/yuri/yuriplayer/DeviceUiTest.kt +++ b/app/src/androidTest/java/capital/yuri/yuriplayer/DeviceUiTest.kt @@ -40,14 +40,14 @@ class DeviceUiTest { rule.waitForTag(TestTags.TAB_HOME) rule.onNodeWithTag(TestTags.TAB_HOME).assertIsDisplayed() rule.onNodeWithTag(TestTags.TAB_MY_STUFF).assertIsDisplayed() - rule.onNodeWithTag(TestTags.TAB_SEARCH).assertIsDisplayed() + rule.onNodeWithTag(TestTags.TAB_EXPLORE).assertIsDisplayed() rule.onNodeWithTag(TestTags.SETTINGS).assertIsDisplayed() rule.onNodeWithTag(TestTags.TAB_MY_STUFF).performClick() rule.waitForTag(TestTags.CATALOG_TITLE) - rule.onNodeWithTag(TestTags.CATALOG_TITLE).assertTextContains("Catalog") + rule.onNodeWithTag(TestTags.CATALOG_TITLE).assertTextContains("My Stuff") - rule.onNodeWithTag(TestTags.TAB_SEARCH).performClick() + rule.onNodeWithTag(TestTags.TAB_EXPLORE).performClick() rule.onNodeWithTag(TestTags.TAB_HOME).performClick() rule.onNodeWithTag(TestTags.MYSTUFF_PINS).assertIsDisplayed() } diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt index 20d48596..98334eed 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt @@ -310,7 +310,7 @@ class MainActivity : ComponentActivity() { private enum class TopTab(val label: String) { Home("Home"), MyStuff("My Stuff"), - Search("Search") + Explore("Explore") } private sealed class DetailRoute { @@ -668,9 +668,18 @@ fun YuriApp( topBar = { if (detail == null) { TopAppBar( - title = { Text(topTab.label) }, + title = { + Text( + topTab.label, + modifier = if (topTab == TopTab.MyStuff) { + Modifier.testTag(TestTags.CATALOG_TITLE) + } else { + Modifier + } + ) + }, actions = { - if (topTab == TopTab.Search) { + if (topTab == TopTab.Explore) { ExploreScanMenu() } IconButton( @@ -724,16 +733,16 @@ fun YuriApp( label = { Text("My Stuff") } ) NavigationBarItem( - selected = topTab == TopTab.Search, - onClick = { topTab = TopTab.Search }, - modifier = Modifier.testTag(TestTags.TAB_SEARCH), + selected = topTab == TopTab.Explore, + onClick = { topTab = TopTab.Explore }, + modifier = Modifier.testTag(TestTags.TAB_EXPLORE), icon = { Icon( - if (topTab == TopTab.Search) Icons.Filled.Search else Icons.Outlined.Search, - contentDescription = "Search" + if (topTab == TopTab.Explore) Icons.Filled.Search else Icons.Outlined.Search, + contentDescription = "Explore" ) }, - label = { Text("Search") } + label = { Text("Explore") } ) } } @@ -959,7 +968,7 @@ fun YuriApp( }, onOpenSongAlbum = { openAlbumForSong(it) } ) - TopTab.Search -> ExploreScreen( + TopTab.Explore -> ExploreScreen( nowPlaying = currentSong, isPlaybackActive = playing, onPlay = { songs, index -> player.playSource(songs, index) }, diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ExploreScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ExploreScreen.kt index e3840a26..1dd2e758 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ExploreScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ExploreScreen.kt @@ -239,7 +239,7 @@ fun ExploreScreen( ) Spacer(modifier = Modifier.height(12.dp)) Text( - "Search across every library", + "Explore every library", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) ) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffCatalogTab.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffCatalogTab.kt index 5853dbb4..62383bf0 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffCatalogTab.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffCatalogTab.kt @@ -41,7 +41,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -65,7 +64,6 @@ import capital.yuri.yuriplayer.ui.AlbumRowSkeleton import capital.yuri.yuriplayer.ui.ArtistRowSkeleton import capital.yuri.yuriplayer.ui.LoadingEstimates import capital.yuri.yuriplayer.ui.SongListSkeleton -import capital.yuri.yuriplayer.ui.TestTags import capital.yuri.yuriplayer.ui.formatTrackCount import coil3.compose.AsyncImage import coil3.request.ImageRequest @@ -300,18 +298,8 @@ private fun CatalogHub( ) { LazyColumn( modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(bottom = 24.dp) + contentPadding = PaddingValues(top = 8.dp, bottom = 24.dp) ) { - item { - Text( - "Catalog", - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.Bold, - modifier = Modifier - .padding(horizontal = 16.dp, vertical = 12.dp) - .testTag(TestTags.CATALOG_TITLE) - ) - } item { LibraryChipsRow( sources = sources, diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt index 00b64385..0a772c7c 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt @@ -90,15 +90,10 @@ fun MyStuffPinsHost( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically + .padding(horizontal = 16.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End ) { - Text( - text = "My Stuff", - style = MaterialTheme.typography.headlineMedium, - fontWeight = FontWeight.Bold, - modifier = Modifier.weight(1f) - ) IconButton( onClick = onPlayAll, modifier = Modifier diff --git a/app/src/main/java/capital/yuri/yuriplayer/ui/TestTags.kt b/app/src/main/java/capital/yuri/yuriplayer/ui/TestTags.kt index 9308cc9d..c165a546 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/ui/TestTags.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/ui/TestTags.kt @@ -8,7 +8,8 @@ object TestTags { const val TAB_HOME = "tab_home" const val TAB_MY_STUFF = "tab_my_stuff" const val TAB_EXPLORE = "tab_explore" - const val TAB_SEARCH = "tab_search" + /** Alias — Explore used to be labeled Search. */ + const val TAB_SEARCH = TAB_EXPLORE const val SETTINGS = "settings" const val MYSTUFF_PINS = "mystuff_pins" From daa1ccf4dafcf1f1e7978530f9ec5a11bdf9ec09 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:20:57 +0000 Subject: [PATCH 13/69] Put Explore search in the top bar with library filters The Explore tab uses a pill search field instead of a title. A Filters row under it opens a sheet to pick local and remote libraries. --- .../yuriplayer/activities/MainActivity.kt | 49 ++- .../yuriplayer/activities/ui/ExploreScreen.kt | 313 ++++++++++++++++-- .../yuriplayer/data/ExploreSearchService.kt | 19 +- .../yuri/yuriplayer/data/LibrarySettings.kt | 12 + 4 files changed, 342 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt index 98334eed..205639ea 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt @@ -63,6 +63,7 @@ import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -81,6 +82,7 @@ import capital.yuri.yuriplayer.activities.ui.EditAlbumMetadataScreen import capital.yuri.yuriplayer.activities.ui.EditSongMetadataScreen import capital.yuri.yuriplayer.activities.ui.ExploreScanMenu import capital.yuri.yuriplayer.activities.ui.ExploreScreen +import capital.yuri.yuriplayer.activities.ui.ExploreSearchTopBar import capital.yuri.yuriplayer.activities.ui.HomeFeedScreen import capital.yuri.yuriplayer.activities.ui.LocalAlbumNav import capital.yuri.yuriplayer.activities.ui.LocalArtistNav @@ -401,6 +403,7 @@ fun YuriApp( } var topTab by remember { mutableStateOf(TopTab.Home) } + var exploreQuery by rememberSaveable { mutableStateOf("") } var playerExpanded by remember { mutableStateOf(false) } var detailStack by remember { mutableStateOf>(emptyList()) } var npPlaylistSong by remember { mutableStateOf(null) } @@ -667,21 +670,12 @@ fun YuriApp( }, topBar = { if (detail == null) { - TopAppBar( - title = { - Text( - topTab.label, - modifier = if (topTab == TopTab.MyStuff) { - Modifier.testTag(TestTags.CATALOG_TITLE) - } else { - Modifier - } - ) - }, - actions = { - if (topTab == TopTab.Explore) { - ExploreScanMenu() - } + if (topTab == TopTab.Explore) { + ExploreSearchTopBar( + query = exploreQuery, + onQueryChange = { exploreQuery = it } + ) { + ExploreScanMenu() IconButton( onClick = { pushDetail(DetailRoute.Settings) }, modifier = Modifier.testTag(TestTags.SETTINGS) @@ -689,7 +683,28 @@ fun YuriApp( Icon(Icons.Default.Settings, contentDescription = "Settings") } } - ) + } else { + TopAppBar( + title = { + Text( + topTab.label, + modifier = if (topTab == TopTab.MyStuff) { + Modifier.testTag(TestTags.CATALOG_TITLE) + } else { + Modifier + } + ) + }, + actions = { + IconButton( + onClick = { pushDetail(DetailRoute.Settings) }, + modifier = Modifier.testTag(TestTags.SETTINGS) + ) { + Icon(Icons.Default.Settings, contentDescription = "Settings") + } + } + ) + } } }, bottomBar = { @@ -969,6 +984,8 @@ fun YuriApp( onOpenSongAlbum = { openAlbumForSong(it) } ) TopTab.Explore -> ExploreScreen( + query = exploreQuery, + onQueryChange = { exploreQuery = it }, nowPlaying = currentSong, isPlaybackActive = playing, onPlay = { songs, index -> player.playSource(songs, index) }, diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ExploreScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ExploreScreen.kt index 1dd2e758..6c881ec0 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ExploreScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ExploreScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -17,19 +18,28 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Cloud +import androidx.compose.material.icons.filled.FilterList import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -41,6 +51,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -52,11 +63,16 @@ import capital.yuri.yuriplayer.data.AlbumItem import capital.yuri.yuriplayer.data.ArtistItem import capital.yuri.yuriplayer.data.CatalogRepository import capital.yuri.yuriplayer.data.ExploreSearchService +import capital.yuri.yuriplayer.data.LibrarySettings import capital.yuri.yuriplayer.data.Playlist import capital.yuri.yuriplayer.data.PlaylistRepository import capital.yuri.yuriplayer.data.Song +import capital.yuri.yuriplayer.data.albumKey +import capital.yuri.yuriplayer.data.db.CatalogSources +import capital.yuri.yuriplayer.data.db.SourceInstanceEntity import capital.yuri.yuriplayer.data.source.RemotePlaylist import capital.yuri.yuriplayer.data.source.RemotePlaylistService +import capital.yuri.yuriplayer.data.source.SourceInstanceRepository import capital.yuri.yuriplayer.data.source.SourceOffering import capital.yuri.yuriplayer.ui.LoadingEstimates import capital.yuri.yuriplayer.ui.SongListSkeleton @@ -68,6 +84,8 @@ import org.koin.compose.koinInject @Composable fun ExploreScreen( + query: String, + onQueryChange: (String) -> Unit, nowPlaying: Song? = null, isPlaybackActive: Boolean = false, onPlay: (List, Int) -> Unit, @@ -80,13 +98,16 @@ fun ExploreScreen( val catalog: CatalogRepository = koinInject() val playlistsRepo: PlaylistRepository = koinInject() val remotePls: RemotePlaylistService = koinInject() + val sourcesRepo: SourceInstanceRepository = koinInject() + val settings: LibrarySettings = koinInject() val localPlaylists by playlistsRepo.observePlaylists().collectAsState(initial = emptyList()) + val instances by sourcesRepo.observeAll().collectAsState(initial = emptyList()) + val remotes = remember(instances) { instances.filter { it.enabled } } val context = LocalContext.current - val focusManager = LocalFocusManager.current - val keyboard = LocalSoftwareKeyboardController.current val scope = rememberCoroutineScope() - var query by remember { mutableStateOf("") } + var selectedKeys by remember { mutableStateOf(settings.getExploreLibraryKeys()) } + var showFilters by remember { mutableStateOf(false) } val scanning by explore.isScanning.collectAsState() val scanProgress by explore.scanProgress.collectAsState() @@ -106,7 +127,7 @@ fun ExploreScreen( withContext(Dispatchers.IO) { remotePls.syncOwnedToMyStuff() } } - LaunchedEffect(query) { + LaunchedEffect(query, selectedKeys, remotes) { val q = query.trim() if (q.isEmpty()) { hits = emptyList() @@ -121,29 +142,51 @@ fun ExploreScreen( delay(SEARCH_DEBOUNCE_MS) if (query.trim() != q) return@LaunchedEffect + val filters = exploreSourceFilters(selectedKeys, remotes) val songHits = withContext(Dispatchers.Default) { - explore.searchLive(q).take(SONG_HIT_LIMIT) + explore.searchLive(q, SONG_HIT_LIMIT, filters).take(SONG_HIT_LIMIT) } if (query.trim() != q) return@LaunchedEffect val albums = withContext(Dispatchers.IO) { - catalog.searchAlbumsAsItems(q, ALBUM_HIT_LIMIT) + val found = catalog.searchAlbumsAsItems(q, ALBUM_HIT_LIMIT) + if (filters == null) found + else { + val keys = songHits.map { + albumKey(it.song.album, it.song.effectiveAlbumArtist ?: it.song.artist) + }.toHashSet() + found.filter { albumKey(it.name, it.artist) in keys } + } } if (query.trim() != q) return@LaunchedEffect val artists = withContext(Dispatchers.IO) { - catalog.searchArtistsAsItems(q, ARTIST_HIT_LIMIT) + val found = catalog.searchArtistsAsItems(q, ARTIST_HIT_LIMIT) + if (filters == null) found + else { + val names = songHits.map { it.song.displayArtist.lowercase() }.toHashSet() + found.filter { it.displayName.lowercase() in names } + } } if (query.trim() != q) return@LaunchedEffect + val includeLocal = filters == null || selectedKeys.isEmpty() || EXPLORE_LOCAL_KEY in selectedKeys + val remoteIds = selectedKeys.mapNotNull { it.removePrefix("i:").toLongOrNull() }.toHashSet() + val includeAllRemotes = filters == null || selectedKeys.isEmpty() + val localIds = localPlaylists.map { it.id }.toHashSet() val remotePl = withContext(Dispatchers.IO) { remotePls.search(q) } + .filter { includeAllRemotes || it.sourceInstanceId in remoteIds } .filter { it.ownedByUser || it.stableId !in localIds } .take(12) - val remoteIds = remotePl.map { it.stableId }.toHashSet() - val localPl = localPlaylists - .filter { it.name.contains(q, true) && it.id !in remoteIds } - .take(12) + val remotePlIds = remotePl.map { it.stableId }.toHashSet() + val localPl = if (includeLocal) { + localPlaylists + .filter { it.name.contains(q, true) && it.id !in remotePlIds } + .take(12) + } else { + emptyList() + } if (query.trim() != q) return@LaunchedEffect hits = songHits @@ -155,30 +198,43 @@ fun ExploreScreen( } Column(modifier = Modifier.fillMaxSize()) { - OutlinedTextField( - value = query, - onValueChange = { query = it }, + val allSelected = selectedKeys.isEmpty() || + (EXPLORE_LOCAL_KEY in selectedKeys && remotes.all { exploreInstanceKey(it.id) in selectedKeys }) + val filterSummary = when { + allSelected -> "All libraries" + selectedKeys.size == 1 && EXPLORE_LOCAL_KEY in selectedKeys -> "On this device" + selectedKeys.size == 1 -> remotes.firstOrNull { + exploreInstanceKey(it.id) in selectedKeys + }?.name ?: "1 library" + else -> "${selectedKeys.size} libraries" + } + Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 8.dp), - singleLine = true, - leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, - trailingIcon = { - if (query.isNotEmpty()) { - IconButton(onClick = { query = "" }) { - Icon(Icons.Default.Clear, contentDescription = "Clear search") - } - } - }, - placeholder = { Text("Search all libraries…") }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), - keyboardActions = KeyboardActions( - onSearch = { - focusManager.clearFocus() - keyboard?.hide() - } + .clickable { showFilters = true } + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "Filters", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.weight(1f) ) - ) + Text( + filterSummary, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + Icon( + Icons.Default.FilterList, + contentDescription = "Filter libraries", + modifier = Modifier + .padding(start = 8.dp) + .size(18.dp), + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } val status = when { err != null && !scanning -> err!! @@ -359,12 +415,203 @@ fun ExploreScreen( } } } + + if (showFilters) { + ExploreFilterSheet( + remotes = remotes, + selectedKeys = selectedKeys, + onDismiss = { showFilters = false }, + onChange = { next -> + selectedKeys = next + settings.setExploreLibraryKeys(next) + } + ) + } } private const val SEARCH_DEBOUNCE_MS = 280L private const val SONG_HIT_LIMIT = 80 private const val ALBUM_HIT_LIMIT = 12 private const val ARTIST_HIT_LIMIT = 12 +private const val EXPLORE_LOCAL_KEY = "local" + +private fun exploreInstanceKey(id: Long) = "i:$id" + +/** Null means every library (no filter). */ +private fun exploreSourceFilters( + selectedKeys: Set, + remotes: List +): List>? { + if (selectedKeys.isEmpty()) return null + val allKeys = buildSet { + add(EXPLORE_LOCAL_KEY) + remotes.forEach { add(exploreInstanceKey(it.id)) } + } + if (selectedKeys.containsAll(allKeys)) return null + return buildList { + if (EXPLORE_LOCAL_KEY in selectedKeys) add(CatalogSources.LOCAL to null) + remotes.forEach { row -> + if (exploreInstanceKey(row.id) in selectedKeys) add(row.type to row.id) + } + }.ifEmpty { null } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ExploreSearchTopBar( + query: String, + onQueryChange: (String) -> Unit, + actions: @Composable RowScope.() -> Unit +) { + TopAppBar( + title = { ExploreSearchField(query = query, onQueryChange = onQueryChange) }, + actions = actions + ) +} + +@Composable +private fun ExploreSearchField( + query: String, + onQueryChange: (String) -> Unit +) { + val focusManager = LocalFocusManager.current + val keyboard = LocalSoftwareKeyboardController.current + val scheme = MaterialTheme.colorScheme + TextField( + value = query, + onValueChange = onQueryChange, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + placeholder = { Text("Songs, albums, artists") }, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, + trailingIcon = { + if (query.isNotEmpty()) { + IconButton(onClick = { onQueryChange("") }) { + Icon(Icons.Default.Clear, contentDescription = "Clear search") + } + } + }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions( + onSearch = { + focusManager.clearFocus() + keyboard?.hide() + } + ), + shape = CircleShape, + colors = TextFieldDefaults.colors( + focusedContainerColor = scheme.surfaceContainerHighest, + unfocusedContainerColor = scheme.surfaceContainerHigh, + disabledContainerColor = scheme.surfaceContainerHigh, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent + ) + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ExploreFilterSheet( + remotes: List, + selectedKeys: Set, + onDismiss: () -> Unit, + onChange: (Set) -> Unit +) { + val allKeys = remember(remotes) { + buildSet { + add(EXPLORE_LOCAL_KEY) + remotes.forEach { add(exploreInstanceKey(it.id)) } + } + } + val effective = if (selectedKeys.isEmpty()) allKeys else selectedKeys + ModalBottomSheet( + onDismissRequest = onDismiss, + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + shape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + .padding(bottom = 24.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "Filters", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f) + ) + TextButton(onClick = { onChange(emptySet()) }) { + Text("All") + } + } + Text( + "Libraries", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + FilterLibraryRow( + title = "On this device", + checked = EXPLORE_LOCAL_KEY in effective, + onToggle = { + onChange(toggleExploreKey(effective, allKeys, EXPLORE_LOCAL_KEY)) + } + ) + remotes.forEach { row -> + val key = exploreInstanceKey(row.id) + FilterLibraryRow( + title = row.name.ifBlank { row.type }, + subtitle = row.type.lowercase().replaceFirstChar { it.titlecase() }, + checked = key in effective, + onToggle = { onChange(toggleExploreKey(effective, allKeys, key)) } + ) + } + } + } +} + +@Composable +private fun FilterLibraryRow( + title: String, + checked: Boolean, + onToggle: () -> Unit, + subtitle: String? = null +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onToggle) + .padding(horizontal = 8.dp, vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox(checked = checked, onCheckedChange = { onToggle() }) + Column(Modifier.weight(1f).padding(end = 8.dp)) { + Text(title, style = MaterialTheme.typography.bodyLarge) + if (subtitle != null) { + Text( + subtitle, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + ) + } + } + } +} + +private fun toggleExploreKey(current: Set, allKeys: Set, key: String): Set { + val next = current.toMutableSet() + if (key in next) next.remove(key) else next.add(key) + if (next.isEmpty() || next.containsAll(allKeys)) return emptySet() + return next +} @Composable private fun SectionHeader(title: String) { diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/ExploreSearchService.kt b/app/src/main/java/capital/yuri/yuriplayer/data/ExploreSearchService.kt index 7eb10288..e15c3c71 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/ExploreSearchService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/ExploreSearchService.kt @@ -204,15 +204,30 @@ class ExploreSearchService( suspend fun searchWithPrefer(query: String, forceRescan: Boolean = false): List = search(query, forceRescan) - suspend fun searchLive(query: String, limit: Int = 80): List = + suspend fun searchLive( + query: String, + limit: Int = 80, + sourceFilters: List>? = null + ): List = withContext(Dispatchers.IO) { val q = query.trim() if (q.isEmpty()) return@withContext emptyList() - buildHitsFromDb(q, limit = limit) + val songs = if (sourceFilters.isNullOrEmpty()) { + catalog.searchSongs(q, limit = limit * 3) + } else { + sourceFilters.flatMap { (type, instanceId) -> + catalog.searchSongsForSource(type, instanceId, q, limit) + }.distinctBy { it.songKey } + } + hitsFromSongs(songs, limit) } private suspend fun buildHitsFromDb(needle: String, limit: Int): List { val songs = catalog.searchSongs(needle, limit = limit * 3) + return hitsFromSongs(songs, limit) + } + + private suspend fun hitsFromSongs(songs: List, limit: Int): List { if (songs.isEmpty()) return emptyList() val grouped = songs.groupBy { TrackIdentity.of(it) } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt b/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt index e9bc381a..b14821d9 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySettings.kt @@ -283,6 +283,17 @@ class LibrarySettings(context: Context) { bumpColorPrefs() } + /** + * Explore library filter keys. Empty means every library. + * `local` = on this device; `i:{id}` = a [SourceInstanceEntity] id. + */ + fun getExploreLibraryKeys(): Set = + prefs.getStringSet(KEY_EXPLORE_LIBS, emptySet())?.toSet().orEmpty() + + fun setExploreLibraryKeys(keys: Set) { + prefs.edit().putStringSet(KEY_EXPLORE_LIBS, keys).apply() + } + private fun bumpColorPrefs() { _colorPrefsRevision.value = System.currentTimeMillis() } @@ -330,6 +341,7 @@ class LibrarySettings(context: Context) { private const val KEY_THEME_MODE = "theme_mode" private const val KEY_ACCENT = "accent_id" private const val KEY_HOME_ROWS = "home_rows" + private const val KEY_EXPLORE_LIBS = "explore_library_keys" val DEFAULT_ROOTS = listOf( "Music", From 1d08ab6920e9436788fc205f86c9e469e529977e Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:21:41 +0000 Subject: [PATCH 14/69] Remove the Home play-all button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pins are just pins — no radio control sitting above the grid. --- .../activities/ui/HomeFeedScreen.kt | 15 +--------- .../activities/ui/MyStuffPinsHost.kt | 28 +------------------ .../activities/ui/MyStuffPinsTab.kt | 6 ++-- .../activities/ui/MyStuffPinsTabDelegate.kt | 6 ++-- 4 files changed, 6 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt index 5d85d623..f4915b52 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt @@ -1,6 +1,5 @@ package capital.yuri.yuriplayer.activities.ui -import android.widget.Toast import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement @@ -49,7 +48,6 @@ import capital.yuri.yuriplayer.data.PlaylistRepository import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.data.StuffPinKind import capital.yuri.yuriplayer.player.PlaybackHistoryStore -import capital.yuri.yuriplayer.player.PlayerController import capital.yuri.yuriplayer.ui.TestTags import kotlinx.coroutines.launch import org.koin.compose.koinInject @@ -66,7 +64,6 @@ fun HomeFeedScreen( val pinStore: MyStuffPinStore = koinInject() val playlistsRepo: PlaylistRepository = koinInject() val catalog: CatalogRepository = koinInject() - val player: PlayerController = koinInject() val history: PlaybackHistoryStore = koinInject() val settings: LibrarySettings = koinInject() val pins by pinStore.pins.collectAsState() @@ -114,17 +111,7 @@ fun HomeFeedScreen( } }, onUnpin = { pinStore.unpin(it) }, - onAddPinSlot = { showAddPin = true }, - onPlayAll = { - scope.launch { - val songs = resolveCollectionSongs(entries, library, playlistsRepo) - if (songs.isEmpty()) { - Toast.makeText(context, "Nothing in My Stuff yet", Toast.LENGTH_SHORT).show() - } else { - player.startPlaylistRadio(songs, "My Stuff") - } - } - } + onAddPinSlot = { showAddPin = true } ) } if (HomeRowId.RECENTS in rows && historyEntries.isNotEmpty()) { diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt index 0a772c7c..b073940e 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsHost.kt @@ -18,13 +18,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -64,8 +61,7 @@ fun MyStuffPinsHost( allSongs: List, onOpenPin: (StuffPin) -> Unit, onUnpin: (StuffPin) -> Unit, - onAddPinSlot: () -> Unit, - onPlayAll: () -> Unit + onAddPinSlot: () -> Unit ) { var reorderMode by remember { mutableStateOf(false) } var pinForSheet by remember { mutableStateOf(null) } @@ -87,28 +83,6 @@ fun MyStuffPinsHost( } Column(modifier = Modifier.fillMaxWidth()) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End - ) { - IconButton( - onClick = onPlayAll, - modifier = Modifier - .size(48.dp) - .clip(CircleShape) - .border(1.5.dp, MaterialTheme.colorScheme.onSurface.copy(alpha = 0.35f), CircleShape) - ) { - Icon( - Icons.Default.PlayArrow, - contentDescription = "Start radio from My Stuff", - modifier = Modifier.size(28.dp) - ) - } - } - Column( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsTab.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsTab.kt index f2559092..2b5e7db3 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsTab.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsTab.kt @@ -21,8 +21,7 @@ fun MyStuffPinsTabWired( allSongs: List, onOpenPin: (StuffPin) -> Unit, onUnpin: (StuffPin) -> Unit, - onAddPinSlot: () -> Unit, - onPlayAll: () -> Unit + onAddPinSlot: () -> Unit ) = MyStuffPinsHost( pins = pins, library = library, @@ -30,6 +29,5 @@ fun MyStuffPinsTabWired( allSongs = allSongs, onOpenPin = onOpenPin, onUnpin = onUnpin, - onAddPinSlot = onAddPinSlot, - onPlayAll = onPlayAll + onAddPinSlot = onAddPinSlot ) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsTabDelegate.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsTabDelegate.kt index e490969f..89f6b5c9 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsTabDelegate.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffPinsTabDelegate.kt @@ -19,8 +19,7 @@ fun MyStuffPinsTabPublic( allSongs: List, onOpenPin: (StuffPin) -> Unit, onUnpin: (StuffPin) -> Unit, - onAddPinSlot: () -> Unit, - onPlayAll: () -> Unit + onAddPinSlot: () -> Unit ) { MyStuffPinsHost( pins = pins, @@ -29,7 +28,6 @@ fun MyStuffPinsTabPublic( allSongs = allSongs, onOpenPin = onOpenPin, onUnpin = onUnpin, - onAddPinSlot = onAddPinSlot, - onPlayAll = onPlayAll + onAddPinSlot = onAddPinSlot ) } From 5d1dbafb64fb557074af266b511791be3046b770 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:31:54 +0000 Subject: [PATCH 15/69] Playlists, covers, crop, and metadata on desktop Home playlist tiles use real playlist art (seed tracks from the catalog). Desktop gets playlists with secret/public covers, pan-zoom crop, queue drag-reorder, and song/album tag editing. --- .../activities/ui/HomeFeedScreen.kt | 2 +- .../yuriplayer/data/PlaylistRepository.kt | 20 +- .../yuriplayer/components/album/AlbumPage.kt | 43 ++- .../components/layout/DesktopBentoLayout.kt | 5 + .../yuriplayer/components/list/TrackRow.kt | 9 +- .../components/player/NowPlayingSidebar.kt | 6 +- .../components/player/QueuePanel.kt | 97 +++-- .../yuriplayer/core/player/PlayerSession.kt | 36 ++ desktop/build.gradle.kts | 1 + .../yuriplayer/desktop/DesktopCollection.kt | 2 +- .../desktop/DesktopPlaylistStore.kt | 218 +++++++++++ .../yuri/yuriplayer/desktop/DesktopSession.kt | 7 + .../yuri/yuriplayer/desktop/TagWriter.kt | 46 +++ .../yuriplayer/desktop/ui/DesktopFiles.kt | 30 ++ .../yuriplayer/desktop/ui/ImageCropDialog.kt | 178 +++++++++ .../desktop/ui/MetadataEditDialog.kt | 226 +++++++++++ .../yuriplayer/desktop/ui/PlaylistPage.kt | 359 ++++++++++++++++++ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 114 +++++- 18 files changed, 1347 insertions(+), 52 deletions(-) create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/TagWriter.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt index f4915b52..00edcc75 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/HomeFeedScreen.kt @@ -164,7 +164,7 @@ fun HomeFeedScreen( title = pl.name, subtitle = "Playlist", onClick = { onOpenPlaylist(pl) }, - art = { AlbumArt(song = null, size = 112.dp, corner = 8.dp) } + art = { PlaylistCoverArt(pl, size = 112.dp) } ) } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/PlaylistRepository.kt b/app/src/main/java/capital/yuri/yuriplayer/data/PlaylistRepository.kt index da357c94..f90b8c43 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/PlaylistRepository.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/PlaylistRepository.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.withContext import java.util.UUID @@ -72,7 +73,24 @@ class PlaylistRepository( entities.map { e -> e.toPlaylist(countMap[e.id] ?: 0, emptyList()) } }.flowOn(Dispatchers.Default) - fun observePlaylistsResolved(): Flow> = observePlaylists() + fun observePlaylistsResolved(): Flow> = + combine(dao.observeAll(), dao.observeTrackCounts(), dao.observeAllTracks()) { entities, counts, allTracks -> + Triple(entities, counts, allTracks) + }.mapLatest { (entities, counts, allTracks) -> + val countMap = counts.associate { it.playlistId to it.trackCount } + val byPl = allTracks.groupBy { it.playlistId } + val seedKeys = byPl.values.flatMap { tracks -> + tracks.sortedBy { it.position }.take(4).map { it.songKey } + }.distinct() + val songs = catalog.getSongsByKeys(seedKeys).associateBy { it.songKey } + entities.map { e -> + val seed = byPl[e.id].orEmpty() + .sortedBy { it.position } + .take(4) + .mapNotNull { songs[it.songKey] } + e.toPlaylist(countMap[e.id] ?: 0, seed) + } + }.flowOn(Dispatchers.IO) fun observePlaylist(id: String): Flow = combine(dao.observe(id), dao.observeTracks(id)) { entity, tracks -> diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt index dad006bb..b3ed784e 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.Icon @@ -44,6 +45,8 @@ fun AlbumPage( onBack: () -> Unit, onPlay: () -> Unit, onTrack: (Int) -> Unit, + onEdit: () -> Unit = {}, + onEditTrack: (Int) -> Unit = {}, modifier: Modifier = Modifier ) { val coverColors by rememberCoverColors( @@ -61,11 +64,11 @@ fun AlbumPage( val widthClass = windowWidthClass(maxWidth) when (widthClass) { WindowWidthClass.Compact -> AlbumPageCompact( - album, playing, onBack, onPlay, onTrack + album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack ) WindowWidthClass.Medium, WindowWidthClass.Expanded -> AlbumPageWide( - album, playing, onBack, onPlay, onTrack + album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack ) } } @@ -78,7 +81,9 @@ private fun AlbumPageCompact( playing: Boolean, onBack: () -> Unit, onPlay: () -> Unit, - onTrack: (Int) -> Unit + onTrack: (Int) -> Unit, + onEdit: () -> Unit, + onEditTrack: (Int) -> Unit ) { Column(Modifier.fillMaxSize()) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -90,6 +95,10 @@ private fun AlbumPageCompact( ) } Text("Album", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground) + Spacer(Modifier.weight(1f)) + IconButton(onClick = onEdit) { + Icon(Icons.Default.Edit, contentDescription = "Edit album") + } } CoverArt( model = album.artworkUri, @@ -100,7 +109,7 @@ private fun AlbumPageCompact( corner = 12.dp ) AlbumMeta(album, playing, onPlay, Modifier.padding(20.dp)) - TrackList(album, onTrack) + TrackList(album, onTrack, onEditTrack) } } @@ -110,7 +119,9 @@ private fun AlbumPageWide( playing: Boolean, onBack: () -> Unit, onPlay: () -> Unit, - onTrack: (Int) -> Unit + onTrack: (Int) -> Unit, + onEdit: () -> Unit, + onEditTrack: (Int) -> Unit ) { Row(Modifier.fillMaxSize().padding(24.dp)) { Column( @@ -118,12 +129,18 @@ private fun AlbumPageWide( .widthIn(min = 240.dp, max = 360.dp) .padding(end = 28.dp) ) { - IconButton(onClick = onBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back", - tint = MaterialTheme.colorScheme.onBackground - ) + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = onBack) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back", + tint = MaterialTheme.colorScheme.onBackground + ) + } + Spacer(Modifier.weight(1f)) + IconButton(onClick = onEdit) { + Icon(Icons.Default.Edit, contentDescription = "Edit album") + } } CoverArt( model = album.artworkUri, @@ -133,7 +150,7 @@ private fun AlbumPageWide( Spacer(Modifier.height(16.dp)) AlbumMeta(album, playing, onPlay) } - TrackList(album, onTrack, modifier = Modifier.weight(1f)) + TrackList(album, onTrack, onEditTrack, modifier = Modifier.weight(1f)) } } @@ -180,6 +197,7 @@ private fun AlbumMeta( private fun TrackList( album: AlbumPageModel, onTrack: (Int) -> Unit, + onEditTrack: (Int) -> Unit = {}, modifier: Modifier = Modifier ) { val discs = album.tracks.groupBy { it.discNumber ?: 1 } @@ -201,6 +219,7 @@ private fun TrackList( TrackRow( track = track, onClick = { onTrack(global.coerceAtLeast(0)) }, + onLongClick = { onEditTrack(global.coerceAtLeast(0)) }, showCover = false, showAlbum = false ) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt index ad57b404..328a1cbe 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt @@ -280,6 +280,11 @@ private fun LibraryRail( onClick = { onFilter(LibraryFilter.Recents) }, label = { Text("Recents") } ) + FilterChip( + selected = filter == LibraryFilter.Playlists, + onClick = { onFilter(LibraryFilter.Playlists) }, + label = { Text("Playlists") } + ) FilterChip( selected = filter == LibraryFilter.Albums, onClick = { onFilter(LibraryFilter.Albums) }, diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt index 52c2b450..1be03912 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt @@ -1,7 +1,8 @@ package capital.yuri.yuriplayer.components.list +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -19,20 +20,22 @@ import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.model.TrackRowModel +@OptIn(ExperimentalFoundationApi::class) @Composable fun TrackRow( track: TrackRowModel, onClick: () -> Unit, modifier: Modifier = Modifier, showCover: Boolean = true, - showAlbum: Boolean = true + showAlbum: Boolean = true, + onLongClick: (() -> Unit)? = null ) { val highlight = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) Row( modifier = modifier .fillMaxWidth() .background(if (track.highlighted) highlight else MaterialTheme.colorScheme.surface.copy(alpha = 0f)) - .clickable(onClick = onClick) + .combinedClickable(onClick = onClick, onLongClick = onLongClick) .padding(horizontal = 16.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index ed4c9afb..fcaec61f 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -30,6 +30,8 @@ fun NowPlayingSidebar( onQueueTrack: (TrackRowModel) -> Unit, onHistoryTrack: (TrackRowModel) -> Unit, onClearQueue: () -> Unit = {}, + onClearHistory: () -> Unit = {}, + onMoveUpcoming: ((from: Int, to: Int) -> Unit)? = null, modifier: Modifier = Modifier ) { Column( @@ -83,13 +85,15 @@ fun NowPlayingSidebar( ) QueuePanel( nowPlaying = track, - upcoming = queue.filter { !it.highlighted }, + upcoming = queue.dropWhile { it.id != track?.id }.drop(1), history = history, onPlay = { row -> if (history.any { it.id == row.id } && queue.none { it.id == row.id }) onHistoryTrack(row) else onQueueTrack(row) }, onClearQueue = onClearQueue, + onClearHistory = onClearHistory, + onMove = onMoveUpcoming, modifier = Modifier.weight(1f) ) } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt index ecec896c..2b981139 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt @@ -1,37 +1,43 @@ package capital.yuri.yuriplayer.components.player +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.FilterChip import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.CoverRef import capital.yuri.yuriplayer.components.model.TrackRowModel +import kotlin.math.roundToInt private enum class QueueTab { Queue, History } /** * Shared queue chrome used by the mobile overlay and the desktop right rail. - * Upcoming list + recently played — same tabs as Android [capital.yuri.yuriplayer.activities.ui.QueuePanel]. + * Long-press a row and drag to reorder when [onMove] is set. */ @Composable fun QueuePanel( @@ -41,6 +47,7 @@ fun QueuePanel( onPlay: (TrackRowModel) -> Unit, onClearQueue: () -> Unit = {}, onClearHistory: () -> Unit = {}, + onMove: ((from: Int, to: Int) -> Unit)? = null, modifier: Modifier = Modifier ) { var tab by remember { mutableStateOf(QueueTab.Queue) } @@ -80,16 +87,54 @@ fun QueuePanel( color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) ) } else { - LazyColumn(Modifier.weight(1f)) { - items(upcoming, key = { it.id }) { row -> - TrackRow( - track = row, - onClick = { onPlay(row) }, - showCover = true, - showAlbum = false - ) + val density = LocalDensity.current + val rowPx = with(density) { 64.dp.toPx() } + Box(Modifier.weight(1f)) { + LazyColumn( + modifier = if (onMove == null) Modifier.fillMaxSize() + else Modifier + .fillMaxSize() + .pointerInput(upcoming, onMove) { + var from: Int? = null + var dy = 0f + detectDragGesturesAfterLongPress( + onDragStart = { offset -> + from = (offset.y / rowPx).toInt().coerceIn(0, upcoming.lastIndex) + dy = 0f + }, + onDrag = { change, drag -> + change.consume() + dy += drag.y + }, + onDragEnd = { + val start = from ?: return@detectDragGesturesAfterLongPress + val to = (start + (dy / rowPx).roundToInt()) + .coerceIn(0, upcoming.lastIndex) + if (start != to) onMove(start, to) + from = null + }, + onDragCancel = { from = null } + ) + } + ) { + itemsIndexed(upcoming, key = { _, row -> row.id }) { _, row -> + TrackRow( + track = row, + onClick = { onPlay(row) }, + showCover = true, + showAlbum = false + ) + } } } + if (onMove != null) { + Text( + "Long-press and drag to reorder", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) + ) + } } } QueueTab.History -> { @@ -101,7 +146,7 @@ fun QueuePanel( ) } else { LazyColumn(Modifier.weight(1f)) { - items(history, key = { it.id }) { row -> + itemsIndexed(history, key = { _, row -> row.id }) { _, row -> TrackRow( track = row, onClick = { onPlay(row) }, @@ -118,29 +163,31 @@ fun QueuePanel( @Composable private fun NowPlayingQueueCard(track: CoverRef) { - Surface( - modifier = Modifier + Row( + Modifier .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 4.dp), - shape = MaterialTheme.shapes.large, - color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.45f) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically ) { - Column(Modifier.padding(12.dp)) { + capital.yuri.yuriplayer.components.art.CoverArt( + model = track.artworkUri, + size = 48.dp, + corner = 8.dp + ) + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f)) { Text( "Now playing", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.primary ) - Text( - track.title, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onSurface - ) + Text(track.title, fontWeight = FontWeight.SemiBold, maxLines = 1) Text( track.subtitle, style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + maxLines = 1 ) } } -} +} \ No newline at end of file diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt index 10804f9d..3574aed6 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -154,6 +154,42 @@ class PlayerSession( play(if (context.isNotEmpty()) context else listOf(track), i) } + fun addNext(track: Track) { + val q = _queue.value.toMutableList() + val i = (_index.value + 1).coerceIn(0, q.size) + q.add(i, track) + _queue.value = q + warmSuccessor() + } + + fun moveQueueItem(from: Int, to: Int) { + val q = _queue.value.toMutableList() + if (from !in q.indices || to !in q.indices || from == to) return + val item = q.removeAt(from) + q.add(to, item) + val currentId = _current.value?.id + _queue.value = q + if (currentId != null) { + _index.value = q.indexOfFirst { it.id == currentId }.coerceAtLeast(0) + } + warmSuccessor() + } + + fun clearQueueKeepCurrent() { + val cur = _current.value ?: run { + _queue.value = emptyList() + _index.value = 0 + return + } + _queue.value = listOf(cur) + _index.value = 0 + warmSuccessor() + } + + fun clearHistory() { + _history.value = emptyList() + } + fun positionMs(): Long = engine.getPositionMs() fun durationMs(): Long = engine.getDurationMs() diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 89f9ddba..0c2613e7 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(libs.coil.compose) implementation(libs.coil.network.ktor3) implementation(libs.ktor.client.cio) + implementation(libs.jaudiotagger) } val libvlcRoot = layout.buildDirectory.dir("libvlc") diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt index aacc1543..b1f7876d 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt @@ -20,7 +20,7 @@ class DesktopCollection(configDir: String) { data class Pin(val kind: Kind, val id: String, val title: String, val subtitle: String) - enum class Kind { ALBUM, ARTIST, SONG } + enum class Kind { ALBUM, ARTIST, SONG, PLAYLIST } init { load() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt new file mode 100644 index 00000000..7b704ee1 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -0,0 +1,218 @@ +package capital.yuri.yuriplayer.desktop + +import capital.yuri.yuriplayer.core.library.Track +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File +import java.util.UUID +import javax.imageio.ImageIO + +/** + * Desktop playlists: JSON + cover files on disk. + * Secret covers stay in the file but are not the active slot after a cold start. + */ +class DesktopPlaylistStore(configDir: String) { + private val root = File(configDir, "playlists") + private val jsonFile = File(root, "playlists.json") + private val coversDir = File(root, "covers") + private val json = Json { ignoreUnknownKeys = true; prettyPrint = true } + + private val _playlists = MutableStateFlow>(emptyList()) + val playlists: StateFlow> = _playlists.asStateFlow() + + init { + root.mkdirs() + coversDir.mkdirs() + load() + resetSecretActiveCoversToPublic() + } + + fun get(id: String): DesktopPlaylist? = _playlists.value.firstOrNull { it.id == id } + + fun create(name: String, description: String? = null, trackIds: List = emptyList()): DesktopPlaylist { + val now = System.currentTimeMillis() + val pl = DesktopPlaylist( + id = UUID.randomUUID().toString(), + name = name.trim().ifEmpty { "New playlist" }, + description = description?.trim()?.takeIf { it.isNotEmpty() }, + trackIds = trackIds, + covers = emptyList(), + activeCoverId = null, + createdAtMs = now, + updatedAtMs = now + ) + _playlists.value = listOf(pl) + _playlists.value + persist() + return pl + } + + fun rename(id: String, name: String, description: String?) { + update(id) { + it.copy( + name = name.trim().ifEmpty { it.name }, + description = description?.trim()?.takeIf { it.isNotEmpty() } + ) + } + } + + fun setTracks(id: String, trackIds: List) { + update(id) { it.copy(trackIds = trackIds.distinct()) } + } + + fun addTracks(id: String, trackIds: List) { + update(id) { it.copy(trackIds = (it.trackIds + trackIds).distinct()) } + } + + fun removeTracks(id: String, trackIds: List) { + val drop = trackIds.toHashSet() + update(id) { it.copy(trackIds = it.trackIds.filterNot { id -> id in drop }) } + } + + fun moveTrack(id: String, from: Int, to: Int) { + update(id) { + val t = it.trackIds.toMutableList() + if (from !in t.indices || to !in t.indices) return@update it + val item = t.removeAt(from) + t.add(to, item) + it.copy(trackIds = t) + } + } + + fun addCover(playlistId: String, source: File, isSecret: Boolean, makeActive: Boolean = !isSecret): DesktopCover? { + val pl = get(playlistId) ?: return null + if (!source.isFile) return null + val coverId = UUID.randomUUID().toString() + val destDir = File(coversDir, playlistId).also { it.mkdirs() } + val dest = File(destDir, "$coverId.jpg") + copyAsJpeg(source, dest) + val cover = DesktopCover( + id = coverId, + path = dest.absolutePath, + isSecret = isSecret, + sortOrder = (pl.covers.maxOfOrNull { it.sortOrder } ?: -1) + 1 + ) + update(playlistId) { + val next = it.copy(covers = it.covers + cover) + if (makeActive) next.copy(activeCoverId = coverId) else next + } + return cover + } + + fun setActiveCover(playlistId: String, coverId: String) { + update(playlistId) { pl -> + if (pl.covers.none { it.id == coverId }) pl + else pl.copy(activeCoverId = coverId) + } + } + + fun setCoverSecret(playlistId: String, coverId: String, secret: Boolean) { + update(playlistId) { pl -> + pl.copy(covers = pl.covers.map { if (it.id == coverId) it.copy(isSecret = secret) else it }) + } + } + + fun removeCover(playlistId: String, coverId: String) { + val pl = get(playlistId) ?: return + val cover = pl.covers.firstOrNull { it.id == coverId } ?: return + File(cover.path).delete() + update(playlistId) { + val remaining = it.covers.filterNot { c -> c.id == coverId } + val active = if (it.activeCoverId == coverId) { + remaining.firstOrNull { c -> !c.isSecret }?.id ?: remaining.firstOrNull()?.id + } else it.activeCoverId + it.copy(covers = remaining, activeCoverId = active) + } + } + + fun delete(id: String) { + File(coversDir, id).deleteRecursively() + _playlists.value = _playlists.value.filterNot { it.id == id } + persist() + } + + fun resetSecretActiveCoversToPublic() { + var changed = false + val next = _playlists.value.map { pl -> + val active = pl.covers.firstOrNull { it.id == pl.activeCoverId } + if (active?.isSecret != true) return@map pl + changed = true + val pub = pl.covers.firstOrNull { !it.isSecret } + pl.copy(activeCoverId = pub?.id) + } + if (changed) { + _playlists.value = next + persist() + } + } + + private fun update(id: String, transform: (DesktopPlaylist) -> DesktopPlaylist) { + _playlists.value = _playlists.value.map { + if (it.id != id) it + else transform(it).copy(updatedAtMs = System.currentTimeMillis()) + } + persist() + } + + private fun load() { + if (!jsonFile.exists()) return + runCatching { + val file = json.decodeFromString(jsonFile.readText()) + _playlists.value = file.playlists + } + } + + private fun persist() { + runCatching { + root.mkdirs() + jsonFile.writeText(json.encodeToString(PlaylistFile(_playlists.value))) + } + } + + private fun copyAsJpeg(source: File, dest: File) { + dest.parentFile.mkdirs() + val img = ImageIO.read(source) + if (img != null) { + ImageIO.write(img, "jpg", dest) + } else { + source.copyTo(dest, overwrite = true) + } + } + + @Serializable + private data class PlaylistFile(val playlists: List = emptyList()) +} + +@Serializable +data class DesktopPlaylist( + val id: String, + val name: String, + val description: String? = null, + val trackIds: List = emptyList(), + val covers: List = emptyList(), + val activeCoverId: String? = null, + val createdAtMs: Long = 0L, + val updatedAtMs: Long = 0L +) { + fun artworkUri(library: List): String? { + val active = covers.firstOrNull { it.id == activeCoverId } ?: covers.firstOrNull { !it.isSecret } + if (active != null) return File(active.path).toURI().toString() + return trackIds.firstNotNullOfOrNull { id -> library.firstOrNull { it.id == id }?.artworkUri } + } + + fun tracks(library: List): List { + val byId = library.associateBy { it.id } + return trackIds.mapNotNull { byId[it] } + } +} + +@Serializable +data class DesktopCover( + val id: String, + val path: String, + val isSecret: Boolean = false, + val sortOrder: Int = 0 +) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 247a825f..32c5c8e3 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -58,6 +58,7 @@ class DesktopSession { val theme = DesktopThemeStore(dirs.configDir) val layout = DesktopLayoutStore(dirs.configDir) val collection = DesktopCollection(dirs.configDir) + val playlists = DesktopPlaylistStore(dirs.configDir) val sources = LibrarySourceStore(dirs.configDir) val jellyfin = JellyfinCatalog(http, sources.deviceId) val subsonic = SubsonicCatalog(http) @@ -109,6 +110,12 @@ class DesktopSession { player.play(if (list.isNotEmpty()) list else listOf(track), i) } + fun replaceTracks(updated: List) { + if (updated.isEmpty()) return + val byId = updated.associateBy { it.id } + _tracks.value = _tracks.value.map { byId[it.id] ?: it } + } + fun addFolder(path: String) { sources.addFolder(path) rescan() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/TagWriter.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/TagWriter.kt new file mode 100644 index 00000000..e24e0357 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/TagWriter.kt @@ -0,0 +1,46 @@ +package capital.yuri.yuriplayer.desktop + +import org.jaudiotagger.audio.AudioFileIO +import org.jaudiotagger.tag.FieldKey +import org.jaudiotagger.tag.images.ArtworkFactory +import java.io.File + +/** Writes tags + embedded cover into a local audio file. */ +object TagWriter { + data class SongEdit( + val title: String? = null, + val artist: String? = null, + val album: String? = null, + val albumArtist: String? = null, + val year: Int? = null, + val genre: String? = null, + val trackNumber: Int? = null + ) + + fun write(file: File, edit: SongEdit, coverJpeg: ByteArray? = null): Result = runCatching { + val audio = AudioFileIO.read(file) + val tag = audio.tagOrCreateAndSetDefault + fun set(key: FieldKey, value: String?) { + if (value == null) return + if (value.isBlank()) tag.deleteField(key) else tag.setField(key, value) + } + set(FieldKey.TITLE, edit.title) + set(FieldKey.ARTIST, edit.artist) + set(FieldKey.ALBUM, edit.album) + set(FieldKey.ALBUM_ARTIST, edit.albumArtist) + set(FieldKey.YEAR, edit.year?.toString()) + set(FieldKey.GENRE, edit.genre) + set(FieldKey.TRACK, edit.trackNumber?.toString()) + if (coverJpeg != null && coverJpeg.isNotEmpty()) { + runCatching { + val tmp = File.createTempFile("yuri-cover-", ".jpg") + tmp.writeBytes(coverJpeg) + tmp.deleteOnExit() + val art = ArtworkFactory.createArtworkFromFile(tmp) + tag.deleteArtworkField() + tag.setField(art) + } + } + audio.commit() + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt new file mode 100644 index 00000000..11cc4edc --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt @@ -0,0 +1,30 @@ +package capital.yuri.yuriplayer.desktop.ui + +import java.awt.FileDialog +import java.awt.Frame +import java.io.File +import javax.swing.SwingUtilities + +object DesktopFiles { + fun pickImage(title: String = "Choose image"): File? { + val files = pickImages(title, multiple = false) + return files.firstOrNull() + } + + fun pickImages(title: String = "Choose images", multiple: Boolean = true): List { + var result: Array? = null + val task = Runnable { + val dialog = FileDialog(null as Frame?, title, FileDialog.LOAD) + dialog.isMultipleMode = multiple + dialog.setFilenameFilter { _, name -> + val ext = name.substringAfterLast('.', "").lowercase() + ext in setOf("jpg", "jpeg", "png", "webp", "gif", "bmp") + } + dialog.isVisible = true + result = dialog.files + } + if (SwingUtilities.isEventDispatchThread()) task.run() + else SwingUtilities.invokeAndWait(task) + return result?.toList().orEmpty() + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt new file mode 100644 index 00000000..0e481b26 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt @@ -0,0 +1,178 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.toComposeImageBitmap +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogWindow +import java.awt.image.BufferedImage +import java.io.File +import javax.imageio.ImageIO +import kotlin.math.max +import kotlin.math.min + +/** + * Pan/zoom square crop. Source is scaled to cover the frame; wheel zooms, drag pans. + */ +@Composable +fun ImageCropDialog( + source: File, + title: String = "Crop cover", + onCancel: () -> Unit, + onCropped: (File) -> Unit +) { + val image = remember(source) { runCatching { ImageIO.read(source) }.getOrNull() } + if (image == null) { + onCancel() + return + } + val bitmap = remember(image) { image.toComposeImageBitmap() } + var zoom by remember { mutableFloatStateOf(1f) } + var pan by remember { mutableStateOf(Offset.Zero) } + + DialogWindow(onCloseRequest = onCancel, title = title) { + Surface(Modifier.size(560.dp, 640.dp), color = MaterialTheme.colorScheme.surface) { + Column(Modifier.fillMaxSize().padding(16.dp)) { + Text(title, style = MaterialTheme.typography.titleLarge) + Text( + "Scroll to zoom · drag to pan", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(bottom = 12.dp) + ) + Box( + Modifier + .fillMaxWidth() + .weight(1f) + .background(Color.Black) + .pointerInput(image) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + if (event.type == PointerEventType.Scroll) { + val dy = event.changes.firstOrNull()?.scrollDelta?.y ?: 0f + zoom = (zoom * (1f - dy * 0.08f)).coerceIn(1f, 8f) + } + } + } + } + .pointerInput(image) { + detectDragGestures { change, drag -> + change.consume() + pan += drag + } + }, + contentAlignment = Alignment.Center + ) { + Canvas(Modifier.fillMaxSize()) { + val frame = min(size.width, size.height) * 0.82f + val frameLeft = (size.width - frame) / 2f + val frameTop = (size.height - frame) / 2f + val imgW = image.width.toFloat() + val imgH = image.height.toFloat() + val cover = max(frame / imgW, frame / imgH) * zoom + val drawW = imgW * cover + val drawH = imgH * cover + val maxPanX = max(0f, (drawW - frame) / 2f) + val maxPanY = max(0f, (drawH - frame) / 2f) + val px = pan.x.coerceIn(-maxPanX, maxPanX) + val py = pan.y.coerceIn(-maxPanY, maxPanY) + val left = frameLeft + (frame - drawW) / 2f + px + val top = frameTop + (frame - drawH) / 2f + py + drawImage( + image = bitmap, + dstOffset = androidx.compose.ui.unit.IntOffset(left.toInt(), top.toInt()), + dstSize = androidx.compose.ui.unit.IntSize(drawW.toInt(), drawH.toInt()) + ) + drawRect(Color.Black.copy(alpha = 0.45f), size = Size(size.width, frameTop)) + drawRect( + Color.Black.copy(alpha = 0.45f), + topLeft = Offset(0f, frameTop + frame), + size = Size(size.width, size.height - frameTop - frame) + ) + drawRect( + Color.Black.copy(alpha = 0.45f), + topLeft = Offset(0f, frameTop), + size = Size(frameLeft, frame) + ) + drawRect( + Color.Black.copy(alpha = 0.45f), + topLeft = Offset(frameLeft + frame, frameTop), + size = Size(size.width - frameLeft - frame, frame) + ) + drawRect( + Color.White, + topLeft = Offset(frameLeft, frameTop), + size = Size(frame, frame), + style = Stroke(width = 2.dp.toPx()) + ) + } + } + Row(Modifier.fillMaxWidth().padding(top = 12.dp), verticalAlignment = Alignment.CenterVertically) { + TextButton(onClick = onCancel) { Text("Cancel") } + androidx.compose.foundation.layout.Spacer(Modifier.weight(1f)) + TextButton( + onClick = { + val out = cropToSquare(image, zoom, pan) + if (out != null) onCropped(out) else onCancel() + } + ) { Text("Use photo") } + } + } + } + } +} + +private fun cropToSquare(src: BufferedImage, zoom: Float, pan: Offset): File? { + val imgW = src.width.toFloat() + val imgH = src.height.toFloat() + val frame = 1024f + val cover = max(frame / imgW, frame / imgH) * zoom.coerceAtLeast(1f) + val drawW = imgW * cover + val drawH = imgH * cover + val maxPanX = max(0f, (drawW - frame) / 2f) + val maxPanY = max(0f, (drawH - frame) / 2f) + val px = pan.x.coerceIn(-maxPanX, maxPanX) + val py = pan.y.coerceIn(-maxPanY, maxPanY) + val left = (drawW - frame) / 2f - px + val top = (drawH - frame) / 2f - py + val srcX = (left / cover).toInt().coerceIn(0, src.width - 1) + val srcY = (top / cover).toInt().coerceIn(0, src.height - 1) + val srcSide = (frame / cover).toInt().coerceAtLeast(1) + val w = min(srcSide, src.width - srcX) + val h = min(srcSide, src.height - srcY) + val cropped = src.getSubimage(srcX, srcY, w, h) + val square = BufferedImage(1024, 1024, BufferedImage.TYPE_INT_RGB) + val g = square.createGraphics() + g.drawImage(cropped, 0, 0, 1024, 1024, null) + g.dispose() + val out = File.createTempFile("yuri-crop-", ".jpg") + ImageIO.write(square, "jpg", out) + return out +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt new file mode 100644 index 00000000..9b276a34 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt @@ -0,0 +1,226 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogWindow +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.model.AlbumPageModel +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.desktop.TagWriter +import java.io.File + +@Composable +fun EditSongDialog( + track: Track, + onDismiss: () -> Unit, + onSaved: (Track) -> Unit +) { + val file = track.path?.let { File(it) } + val writable = file != null && file.isFile && file.canWrite() + var title by remember(track.id) { mutableStateOf(track.title.orEmpty()) } + var artist by remember(track.id) { mutableStateOf(track.artist.orEmpty()) } + var genre by remember(track.id) { mutableStateOf(track.genre.orEmpty()) } + var coverFile by remember { mutableStateOf(null) } + var cropSource by remember { mutableStateOf(null) } + var error by remember { mutableStateOf(null) } + + cropSource?.let { src -> + ImageCropDialog( + source = src, + title = "Crop cover", + onCancel = { cropSource = null }, + onCropped = { + coverFile = it + cropSource = null + } + ) + } + + DialogWindow(onCloseRequest = onDismiss, title = "Edit song") { + Surface(Modifier.size(480.dp, 520.dp)) { + Column(Modifier.padding(20.dp)) { + Text("Edit song", style = MaterialTheme.typography.titleLarge) + if (!writable) { + Text( + "This file isn’t writable (remote or read-only).", + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(top = 8.dp) + ) + } + CoverArt( + model = coverFile?.toURI()?.toString() ?: track.artworkUri, + size = 96.dp, + corner = 8.dp, + modifier = Modifier.padding(vertical = 12.dp) + ) + TextButton( + onClick = { cropSource = DesktopFiles.pickImage() }, + enabled = writable + ) { Text("Change cover") } + OutlinedTextField(title, { title = it }, label = { Text("Title") }, modifier = Modifier.fillMaxWidth()) + Spacer(Modifier.height(8.dp)) + OutlinedTextField(artist, { artist = it }, label = { Text("Artist") }, modifier = Modifier.fillMaxWidth()) + Spacer(Modifier.height(8.dp)) + OutlinedTextField(genre, { genre = it }, label = { Text("Genre") }, modifier = Modifier.fillMaxWidth()) + error?.let { + Text(it, color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(top = 8.dp)) + } + Spacer(Modifier.weight(1f)) + Row { + TextButton(onClick = onDismiss) { Text("Cancel") } + Spacer(Modifier.weight(1f)) + TextButton( + enabled = writable, + onClick = { + val f = file ?: return@TextButton + val result = TagWriter.write( + f, + TagWriter.SongEdit( + title = title.ifBlank { null }, + artist = artist.ifBlank { null }, + genre = genre.ifBlank { null } + ), + coverJpeg = coverFile?.readBytes() + ) + result.fold( + onSuccess = { + onSaved( + track.copy( + title = title.ifBlank { track.title }, + artist = artist.ifBlank { track.artist }, + genre = genre.ifBlank { track.genre }, + artworkUri = coverFile?.toURI()?.toString() ?: track.artworkUri + ) + ) + onDismiss() + }, + onFailure = { error = it.message ?: "Couldn’t save tags" } + ) + } + ) { Text("Save") } + } + } + } + } +} + +@Composable +fun EditAlbumDialog( + album: AlbumPageModel, + tracks: List, + onDismiss: () -> Unit, + onSaved: (List) -> Unit +) { + val writable = tracks.mapNotNull { it.path?.let { p -> File(p) } }.any { it.isFile && it.canWrite() } + var title by remember(album.id) { mutableStateOf(album.title) } + var artist by remember(album.id) { mutableStateOf(album.artist) } + var year by remember(album.id) { mutableStateOf(album.year?.toString().orEmpty()) } + var genre by remember(album.id) { mutableStateOf(tracks.firstOrNull()?.genre.orEmpty()) } + var coverFile by remember { mutableStateOf(null) } + var cropSource by remember { mutableStateOf(null) } + var error by remember { mutableStateOf(null) } + + cropSource?.let { src -> + ImageCropDialog( + source = src, + onCancel = { cropSource = null }, + onCropped = { + coverFile = it + cropSource = null + } + ) + } + + DialogWindow(onCloseRequest = onDismiss, title = "Edit album") { + Surface(Modifier.size(480.dp, 560.dp)) { + Column(Modifier.padding(20.dp)) { + Text("Edit album", style = MaterialTheme.typography.titleLarge) + CoverArt( + model = coverFile?.toURI()?.toString() ?: album.artworkUri, + size = 96.dp, + corner = 8.dp, + modifier = Modifier.padding(vertical = 12.dp) + ) + TextButton( + onClick = { cropSource = DesktopFiles.pickImage() }, + enabled = writable + ) { Text("Change cover") } + OutlinedTextField(title, { title = it }, label = { Text("Album") }, modifier = Modifier.fillMaxWidth()) + Spacer(Modifier.height(8.dp)) + OutlinedTextField(artist, { artist = it }, label = { Text("Album artist") }, modifier = Modifier.fillMaxWidth()) + Spacer(Modifier.height(8.dp)) + OutlinedTextField(year, { year = it }, label = { Text("Year") }, modifier = Modifier.fillMaxWidth()) + Spacer(Modifier.height(8.dp)) + OutlinedTextField(genre, { genre = it }, label = { Text("Genre") }, modifier = Modifier.fillMaxWidth()) + error?.let { + Text(it, color = MaterialTheme.colorScheme.error, modifier = Modifier.padding(top = 8.dp)) + } + Spacer(Modifier.weight(1f)) + Row { + TextButton(onClick = onDismiss) { Text("Cancel") } + Spacer(Modifier.width(8.dp)) + TextButton( + enabled = writable, + onClick = { + val coverBytes = coverFile?.readBytes() + val yearInt = year.toIntOrNull() + val updated = ArrayList() + var failed = 0 + tracks.forEach { t -> + val f = t.path?.let { File(it) } + if (f == null || !f.canWrite()) { + failed++ + updated += t + return@forEach + } + val result = TagWriter.write( + f, + TagWriter.SongEdit( + album = title.ifBlank { null }, + albumArtist = artist.ifBlank { null }, + year = yearInt, + genre = genre.ifBlank { null } + ), + coverJpeg = coverBytes + ) + if (result.isFailure) failed++ + updated += t.copy( + album = title.ifBlank { t.album }, + albumArtist = artist.ifBlank { t.albumArtist }, + year = yearInt ?: t.year, + genre = genre.ifBlank { t.genre }, + artworkUri = coverFile?.toURI()?.toString() ?: t.artworkUri + ) + } + if (failed == tracks.size) { + error = "Couldn’t write tags on these files" + } else { + onSaved(updated) + onDismiss() + } + } + ) { Text("Save") } + } + } + } + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt new file mode 100644 index 00000000..4690b89b --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -0,0 +1,359 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material3.Checkbox +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogWindow +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.model.toRow +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.desktop.DesktopCover +import capital.yuri.yuriplayer.desktop.DesktopPlaylist +import capital.yuri.yuriplayer.desktop.DesktopPlaylistStore +import java.io.File + +@Composable +fun PlaylistPage( + playlist: DesktopPlaylist, + tracks: List, + library: List, + currentId: String?, + store: DesktopPlaylistStore, + onBack: () -> Unit, + onPlay: (List, Int) -> Unit, + onEditTrack: (Track) -> Unit +) { + var name by remember(playlist.id, playlist.updatedAtMs) { mutableStateOf(playlist.name) } + var description by remember(playlist.id, playlist.updatedAtMs) { + mutableStateOf(playlist.description.orEmpty()) + } + var editing by remember { mutableStateOf(false) } + var showCovers by remember { mutableStateOf(false) } + var showAdd by remember { mutableStateOf(false) } + var cropSource by remember { mutableStateOf(null) } + var cropSecret by remember { mutableStateOf(false) } + val art = playlist.artworkUri(tracks) + + cropSource?.let { src -> + ImageCropDialog( + source = src, + title = if (cropSecret) "Crop secret cover" else "Crop cover", + onCancel = { cropSource = null }, + onCropped = { cropped -> + store.addCover(playlist.id, cropped, isSecret = cropSecret, makeActive = !cropSecret) + cropSource = null + } + ) + } + + Column(Modifier.fillMaxSize().padding(24.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + Spacer(Modifier.weight(1f)) + IconButton(onClick = { editing = !editing }) { + Icon(Icons.Default.Edit, contentDescription = "Edit playlist") + } + } + Row(Modifier.fillMaxWidth().padding(top = 8.dp)) { + Box( + Modifier + .size(220.dp) + .clip(RoundedCornerShape(12.dp)) + .clickable { showCovers = true } + ) { + CoverArt(model = art, modifier = Modifier.fillMaxSize(), corner = 12.dp) + } + Column(Modifier.weight(1f).padding(start = 24.dp)) { + if (editing) { + OutlinedTextField( + value = name, + onValueChange = { name = it }, + label = { Text("Name") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = description, + onValueChange = { description = it }, + label = { Text("Description") }, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) + ) + Row(Modifier.padding(top = 8.dp)) { + TextButton(onClick = { + store.rename(playlist.id, name, description) + editing = false + }) { Text("Save") } + TextButton(onClick = { + name = playlist.name + description = playlist.description.orEmpty() + editing = false + }) { Text("Cancel") } + } + } else { + Text(playlist.name, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold) + if (!playlist.description.isNullOrBlank()) { + Text( + playlist.description, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + modifier = Modifier.padding(top = 6.dp) + ) + } + Text( + "${tracks.size} songs", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + modifier = Modifier.padding(top = 8.dp) + ) + } + FilledIconButton( + onClick = { if (tracks.isNotEmpty()) onPlay(tracks, 0) }, + modifier = Modifier.padding(top = 16.dp) + ) { + Icon(Icons.Default.PlayArrow, contentDescription = "Play") + } + TextButton(onClick = { showAdd = true }) { Text("Add songs") } + } + } + Spacer(Modifier.height(16.dp)) + LazyColumn(Modifier.weight(1f)) { + itemsIndexed(tracks, key = { _, t -> t.id }) { index, track -> + TrackRow( + track = track.toRow(highlighted = track.id == currentId), + onClick = { onPlay(tracks, index) }, + onLongClick = { onEditTrack(track) }, + showCover = true, + showAlbum = true + ) + Row(Modifier.fillMaxWidth().padding(horizontal = 8.dp), horizontalArrangement = Arrangement.End) { + TextButton(onClick = { + if (index > 0) store.moveTrack(playlist.id, index, index - 1) + }, enabled = index > 0) { Text("Up") } + TextButton(onClick = { + if (index < tracks.lastIndex) store.moveTrack(playlist.id, index, index + 1) + }, enabled = index < tracks.lastIndex) { Text("Down") } + IconButton(onClick = { store.removeTracks(playlist.id, listOf(track.id)) }) { + Icon(Icons.Default.Delete, contentDescription = "Remove") + } + } + } + } + } + + if (showAdd) { + AddSongsDialog( + library = library, + existing = playlist.trackIds.toSet(), + onDismiss = { showAdd = false }, + onAdd = { ids -> + store.addTracks(playlist.id, ids) + showAdd = false + } + ) + } + + if (showCovers) { + CoverManagerDialog( + playlist = playlist, + store = store, + onDismiss = { showCovers = false }, + onPick = { secret -> + val file = DesktopFiles.pickImage(if (secret) "Secret cover" else "Playlist cover") + if (file != null) { + cropSecret = secret + cropSource = file + showCovers = false + } + } + ) + } +} + +@Composable +private fun AddSongsDialog( + library: List, + existing: Set, + onDismiss: () -> Unit, + onAdd: (List) -> Unit +) { + var query by remember { mutableStateOf("") } + val hits = remember(query, library) { + val q = query.trim() + if (q.isEmpty()) library.take(40) + else library.filter { + it.displayTitle.contains(q, true) || it.displayArtist.contains(q, true) + }.take(40) + } + DialogWindow(onCloseRequest = onDismiss, title = "Add songs") { + Surface(Modifier.size(520.dp, 560.dp)) { + Column(Modifier.padding(16.dp)) { + OutlinedTextField( + value = query, + onValueChange = { query = it }, + label = { Text("Search library") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + LazyColumn(Modifier.weight(1f).padding(top = 8.dp)) { + itemsIndexed(hits, key = { _, t -> t.id }) { _, track -> + val inPl = track.id in existing + TrackRow( + track = track.toRow(), + onClick = { if (!inPl) onAdd(listOf(track.id)) }, + showCover = true, + showAlbum = true + ) + } + } + TextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.End)) { Text("Done") } + } + } + } +} + +@Composable +private fun CoverManagerDialog( + playlist: DesktopPlaylist, + store: DesktopPlaylistStore, + onDismiss: () -> Unit, + onPick: (secret: Boolean) -> Unit +) { + DialogWindow(onCloseRequest = onDismiss, title = "Playlist covers") { + Surface(Modifier.size(560.dp, 420.dp)) { + Column(Modifier.fillMaxSize().padding(20.dp)) { + Text("Covers", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text( + "Secret covers stay off the public slot after you quit.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(bottom = 12.dp) + ) + LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + itemsIndexed(playlist.covers) { _, cover -> + CoverSlotCard( + cover = cover, + active = cover.id == playlist.activeCoverId, + onActivate = { store.setActiveCover(playlist.id, cover.id) }, + onSecret = { store.setCoverSecret(playlist.id, cover.id, !cover.isSecret) }, + onDelete = { store.removeCover(playlist.id, cover.id) } + ) + } + item { + AddCoverChip("Public") { onPick(false) } + } + item { + AddCoverChip("Secret") { onPick(true) } + } + } + Spacer(Modifier.weight(1f)) + TextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.End)) { Text("Done") } + } + } + } +} + +@Composable +private fun CoverSlotCard( + cover: DesktopCover, + active: Boolean, + onActivate: () -> Unit, + onSecret: () -> Unit, + onDelete: () -> Unit +) { + Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.width(120.dp)) { + Box( + Modifier + .size(112.dp) + .clip(RoundedCornerShape(10.dp)) + .clickable(onClick = onActivate) + ) { + CoverArt( + model = File(cover.path).toURI().toString(), + modifier = Modifier.fillMaxSize(), + corner = 10.dp + ) + if (cover.isSecret) { + Icon( + Icons.Default.Lock, + contentDescription = "Secret", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(6.dp) + .size(18.dp) + ) + } + if (active) { + Box( + Modifier + .matchParentSize() + .background(MaterialTheme.colorScheme.primary.copy(alpha = 0.18f)) + ) + } + } + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox(checked = cover.isSecret, onCheckedChange = { onSecret() }) + Text("Secret", style = MaterialTheme.typography.labelSmall) + } + TextButton(onClick = onDelete) { Text("Remove") } + } +} + +@Composable +private fun AddCoverChip(label: String, onClick: () -> Unit) { + Column( + Modifier + .size(112.dp) + .clip(RoundedCornerShape(10.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant) + .clickable(onClick = onClick), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon(Icons.Default.Add, contentDescription = null) + Text(label, style = MaterialTheme.typography.labelMedium) + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index b0080b4b..80f40e18 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -62,6 +62,7 @@ private sealed class Route { data object Home : Route() data object Search : Route() data class Album(val album: AlbumPageModel) : Route() + data class Playlist(val id: String) : Route() } @Composable @@ -86,6 +87,7 @@ fun YuriDesktopApp(session: DesktopSession) { val volume by session.player.volume.collectAsState() val liked by session.collection.liked.collectAsState() val pins by session.collection.pinned.collectAsState() + val playlists by session.playlists.playlists.collectAsState() val leftFrac by session.layout.leftFraction.collectAsState() val rightFrac by session.layout.rightFraction.collectAsState() val albums = remember(tracks) { tracks.albums() } @@ -95,6 +97,8 @@ fun YuriDesktopApp(session: DesktopSession) { var libraryFilter by remember { mutableStateOf(LibraryFilter.Recents) } var showSettings by remember { mutableStateOf(false) } var showSidebar by remember { mutableStateOf(true) } + var editSong by remember { mutableStateOf(null) } + var editAlbum by remember { mutableStateOf(null) } val route = stack.last() val nav = if (route is Route.Search) DesktopNav.Search else DesktopNav.Home @@ -124,7 +128,7 @@ fun YuriDesktopApp(session: DesktopSession) { fun albumRecency(album: AlbumPageModel): Int = album.tracks.minOfOrNull { recency[it.id] ?: Int.MAX_VALUE } ?: Int.MAX_VALUE - val libraryItems = remember(albums, tracks, libraryFilter, pins, history, liked) { + val libraryItems = remember(albums, tracks, libraryFilter, pins, history, liked, playlists) { val pinItems = pins.map { pin -> when (pin.kind) { DesktopCollection.Kind.ALBUM -> { @@ -146,6 +150,13 @@ fun YuriDesktopApp(session: DesktopSession) { t?.artworkUri, pinned = true ) } + DesktopCollection.Kind.PLAYLIST -> { + val pl = playlists.firstOrNull { it.id == pin.id } + LibraryRailItem( + "playlist:${pin.id}", pin.title, pin.subtitle, + pl?.artworkUri(tracks), pinned = true + ) + } } } val rest = when (libraryFilter) { @@ -165,12 +176,22 @@ fun YuriDesktopApp(session: DesktopSession) { LibraryFilter.Albums -> albums .sortedBy { albumRecency(it) } .map { LibraryRailItem(it.id, it.title, it.artist, it.artworkUri) } - LibraryFilter.Playlists, LibraryFilter.Recents -> albums + LibraryFilter.Playlists -> listOf( + LibraryRailItem("new-playlist", "New playlist", "Create", null) + ) + playlists.sortedByDescending { it.updatedAtMs }.map { pl -> + LibraryRailItem( + "playlist:${pl.id}", + pl.name, + "${pl.trackIds.size} songs", + pl.artworkUri(tracks) + ) + } + LibraryFilter.Recents -> albums .sortedBy { albumRecency(it) } .map { LibraryRailItem( it.id, it.title, - if (albumRecency(it) < Int.MAX_VALUE) "Album · ${it.artist}" else "Album · ${it.artist}", + "Album · ${it.artist}", it.artworkUri ) } @@ -204,11 +225,19 @@ fun YuriDesktopApp(session: DesktopSession) { libraryItems = libraryItems, selectedLibraryId = selectedLibraryId, onLibraryItem = { item -> - if (item.id.startsWith("artist:")) { - query = item.title - push(Route.Search) - } else { - albums.firstOrNull { it.id == item.id }?.let { openAlbum(it) } + when { + item.id == "new-playlist" -> { + val created = session.playlists.create("New playlist") + push(Route.Playlist(created.id)) + } + item.id.startsWith("playlist:") -> { + push(Route.Playlist(item.id.removePrefix("playlist:"))) + } + item.id.startsWith("artist:") -> { + query = item.title + push(Route.Search) + } + else -> albums.firstOrNull { it.id == item.id }?.let { openAlbum(it) } } }, showSidebar = showSidebar, @@ -268,6 +297,14 @@ fun YuriDesktopApp(session: DesktopSession) { onHistoryTrack = { row -> val t = history.firstOrNull { it.id == row.id } ?: return@NowPlayingSidebar session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) + }, + onClearQueue = session.player::clearQueueKeepCurrent, + onClearHistory = session.player::clearHistory, + onMoveUpcoming = { from, to -> + val cur = current ?: return@NowPlayingSidebar + val base = queue.indexOfFirst { it.id == cur.id } + if (base < 0) return@NowPlayingSidebar + session.player.moveQueueItem(base + 1 + from, base + 1 + to) } ) } @@ -276,9 +313,12 @@ fun YuriDesktopApp(session: DesktopSession) { when (val r = route) { Route.Home -> HomeFeed( albums = albums, + playlists = playlists, + tracks = tracks, recents = history, status = engineMessage ?: status, onOpenAlbum = ::openAlbum, + onOpenPlaylist = { push(Route.Playlist(it.id)) }, onPlayRecent = { t -> session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) } ) Route.Search -> LibraryGrid( @@ -305,9 +345,30 @@ fun YuriDesktopApp(session: DesktopSession) { }, onTrack = { i -> if (i in albumTracks.indices) session.player.play(albumTracks, i) + }, + onEdit = { editAlbum = live }, + onEditTrack = { i -> + albumTracks.getOrNull(i)?.let { editSong = it } } ) } + is Route.Playlist -> { + val pl = playlists.firstOrNull { it.id == r.id } + if (pl == null) { + Text("Playlist gone.", modifier = Modifier.padding(24.dp)) + } else { + PlaylistPage( + playlist = pl, + tracks = pl.tracks(tracks), + library = tracks, + currentId = current?.id, + store = session.playlists, + onBack = ::goBack, + onPlay = { list, i -> session.player.play(list, i) }, + onEditTrack = { editSong = it } + ) + } + } } } if (showSettings) { @@ -316,15 +377,34 @@ fun YuriDesktopApp(session: DesktopSession) { onDismiss = { showSettings = false } ) } + editSong?.let { song -> + EditSongDialog( + track = song, + onDismiss = { editSong = null }, + onSaved = { session.replaceTracks(listOf(it)) } + ) + } + editAlbum?.let { album -> + val albumTracks = tracks.filter { t -> album.tracks.any { it.id == t.id } } + EditAlbumDialog( + album = album, + tracks = albumTracks, + onDismiss = { editAlbum = null }, + onSaved = { session.replaceTracks(it) } + ) + } } } @Composable private fun HomeFeed( albums: List, + playlists: List, + tracks: List, recents: List, status: String, onOpenAlbum: (AlbumPageModel) -> Unit, + onOpenPlaylist: (capital.yuri.yuriplayer.desktop.DesktopPlaylist) -> Unit, onPlayRecent: (Track) -> Unit ) { val recentAlbums = remember(recents, albums) { @@ -398,6 +478,24 @@ private fun HomeFeed( } Spacer(Modifier.height(24.dp)) } + if (playlists.isNotEmpty()) { + SectionHeader("Playlists") + LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + items(playlists.take(12), key = { it.id }) { pl -> + AlbumCard( + album = AlbumPageModel( + id = pl.id, + title = pl.name, + artist = pl.description ?: "Playlist", + artworkUri = pl.artworkUri(tracks), + tracks = emptyList() + ), + onClick = { onOpenPlaylist(pl) } + ) + } + } + Spacer(Modifier.height(24.dp)) + } if (albums.isNotEmpty()) { SectionHeader("Albums") LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { From 6c2852bbb571f4952b74d8fb78a8ababb6f79fff Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:40:37 +0000 Subject: [PATCH 16/69] Keep the mobile tab bar on album, artist, and playlist pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Route those screens on a per-tab back stack so back (and a re-tap of the current tab) returns to Home, My Stuff, or Explore — whichever opened them. Enable Android predictive back so the overlay scales off the launching tab instead of replacing it. --- app/src/main/AndroidManifest.xml | 2 + .../yuriplayer/activities/MainActivity.kt | 349 +++++++++++------- .../activities/ui/BrowseAllLibraryScreen.kt | 2 +- .../activities/ui/MyStuffCatalogTab.kt | 4 +- .../activities/ui/SettingsScreen.kt | 2 +- .../yuri/yuriplayer/activities/ui/TabBack.kt | 10 + 6 files changed, 227 insertions(+), 142 deletions(-) create mode 100644 app/src/main/java/capital/yuri/yuriplayer/activities/ui/TabBack.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 028bbd02..7a88a714 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -71,6 +71,7 @@ android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:enableOnBackInvokedCallback="true" android:theme="@style/Theme.YuriPlayer"> diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt index 205639ea..c461c8f3 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt @@ -11,8 +11,9 @@ import android.os.Environment import android.provider.Settings import android.view.WindowManager import android.widget.Toast +import androidx.activity.BackEventCompat import androidx.activity.ComponentActivity -import androidx.activity.compose.BackHandler +import androidx.activity.compose.PredictiveBackHandler import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts @@ -31,7 +32,6 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars @@ -51,6 +51,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar @@ -59,6 +60,8 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -67,6 +70,8 @@ import androidx.compose.runtime.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp @@ -89,6 +94,7 @@ import capital.yuri.yuriplayer.activities.ui.LocalArtistNav import capital.yuri.yuriplayer.activities.ui.LocalPlaylistNav import capital.yuri.yuriplayer.activities.ui.LocalSongNav import capital.yuri.yuriplayer.activities.ui.LocalStatusBarStack +import capital.yuri.yuriplayer.activities.ui.LocalTabBackEnabled import capital.yuri.yuriplayer.activities.ui.MiniPlayerBar import capital.yuri.yuriplayer.activities.ui.MyStuffScreen import capital.yuri.yuriplayer.activities.ui.NowPlayingScreen @@ -405,18 +411,43 @@ fun YuriApp( var topTab by remember { mutableStateOf(TopTab.Home) } var exploreQuery by rememberSaveable { mutableStateOf("") } var playerExpanded by remember { mutableStateOf(false) } - var detailStack by remember { mutableStateOf>(emptyList()) } + var tabStacks by remember { + mutableStateOf( + mapOf( + TopTab.Home to emptyList(), + TopTab.MyStuff to emptyList(), + TopTab.Explore to emptyList() + ) + ) + } var npPlaylistSong by remember { mutableStateOf(null) } + fun stackOf(tab: TopTab) = tabStacks[tab].orEmpty() + fun pushDetail(route: DetailRoute) { - detailStack = detailStack + route + val tab = topTab + tabStacks = tabStacks + (tab to stackOf(tab) + route) } fun popDetail() { - if (detailStack.isNotEmpty()) detailStack = detailStack.dropLast(1) + val tab = topTab + val stack = stackOf(tab) + if (stack.isNotEmpty()) tabStacks = tabStacks + (tab to stack.dropLast(1)) + } + + fun selectTab(tab: TopTab) { + if (topTab == tab) { + if (stackOf(tab).isNotEmpty()) { + tabStacks = tabStacks + (tab to emptyList()) + } + } else { + topTab = tab + } } - val detail = detailStack.lastOrNull() + val currentStack = stackOf(topTab) + val detail = currentStack.lastOrNull() + val previousDetail = currentStack.getOrNull(currentStack.lastIndex - 1) val edgeToEdgeDetail = detail is DetailRoute.Album || detail is DetailRoute.Artist || detail is DetailRoute.Playlist @@ -473,8 +504,24 @@ fun YuriApp( themeStore.updateNeighbors(context, peekNext, peekPrev, baseScheme) } - BackHandler(enabled = playerExpanded) { playerExpanded = false } - BackHandler(enabled = !playerExpanded && detailStack.isNotEmpty()) { popDetail() } + PredictiveBackHandler(enabled = playerExpanded) { events -> + events.collect { } + playerExpanded = false + } + + var backProgress by remember { mutableFloatStateOf(0f) } + var backSwipeEdge by remember { mutableIntStateOf(BackEventCompat.EDGE_LEFT) } + PredictiveBackHandler(enabled = !playerExpanded && currentStack.isNotEmpty()) { events -> + try { + events.collect { event -> + backProgress = event.progress + backSwipeEdge = event.swipeEdge + } + popDetail() + } finally { + backProgress = 0f + } + } fun playAlbumFrom(album: AlbumItem, songs: List, index: Int) { val key = albumKey(album.name, album.artist) @@ -661,58 +708,9 @@ fun YuriApp( modifier = Modifier.fillMaxSize(), containerColor = if (edgeToEdgeDetail) Color.Transparent else MaterialTheme.colorScheme.background, - contentWindowInsets = if (edgeToEdgeDetail) { - WindowInsets(0, 0, 0, 0) - } else { - WindowInsets.systemBars.only( - WindowInsetsSides.Horizontal + WindowInsetsSides.Top - ) - }, - topBar = { - if (detail == null) { - if (topTab == TopTab.Explore) { - ExploreSearchTopBar( - query = exploreQuery, - onQueryChange = { exploreQuery = it } - ) { - ExploreScanMenu() - IconButton( - onClick = { pushDetail(DetailRoute.Settings) }, - modifier = Modifier.testTag(TestTags.SETTINGS) - ) { - Icon(Icons.Default.Settings, contentDescription = "Settings") - } - } - } else { - TopAppBar( - title = { - Text( - topTab.label, - modifier = if (topTab == TopTab.MyStuff) { - Modifier.testTag(TestTags.CATALOG_TITLE) - } else { - Modifier - } - ) - }, - actions = { - IconButton( - onClick = { pushDetail(DetailRoute.Settings) }, - modifier = Modifier.testTag(TestTags.SETTINGS) - ) { - Icon(Icons.Default.Settings, contentDescription = "Settings") - } - } - ) - } - } - }, + contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal), bottomBar = { - Column( - Modifier - .fillMaxWidth() - .then(if (detail != null) Modifier.navigationBarsPadding() else Modifier) - ) { + Column(Modifier.fillMaxWidth()) { MiniPlayerBar( song = currentSong, playing = playing, @@ -721,56 +719,135 @@ fun YuriApp( onToggle = { player.togglePlayPause() }, onExpand = { playerExpanded = true } ) - if (detail == null) { - NavigationBar { - NavigationBarItem( - selected = topTab == TopTab.Home, - onClick = { topTab = TopTab.Home }, - modifier = Modifier.testTag(TestTags.TAB_HOME), - icon = { - Icon( - if (topTab == TopTab.Home) Icons.Filled.Home else Icons.Outlined.Home, - contentDescription = "Home" - ) - }, - label = { Text("Home") } - ) - NavigationBarItem( - selected = topTab == TopTab.MyStuff, - onClick = { topTab = TopTab.MyStuff }, - modifier = Modifier.testTag(TestTags.TAB_MY_STUFF), - icon = { - Icon( - if (topTab == TopTab.MyStuff) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, - contentDescription = "My Stuff" - ) - }, - label = { Text("My Stuff") } - ) - NavigationBarItem( - selected = topTab == TopTab.Explore, - onClick = { topTab = TopTab.Explore }, - modifier = Modifier.testTag(TestTags.TAB_EXPLORE), - icon = { - Icon( - if (topTab == TopTab.Explore) Icons.Filled.Search else Icons.Outlined.Search, - contentDescription = "Explore" + NavigationBar { + NavigationBarItem( + selected = topTab == TopTab.Home, + onClick = { selectTab(TopTab.Home) }, + modifier = Modifier.testTag(TestTags.TAB_HOME), + icon = { + Icon( + if (topTab == TopTab.Home) Icons.Filled.Home else Icons.Outlined.Home, + contentDescription = "Home" + ) + }, + label = { Text("Home") } + ) + NavigationBarItem( + selected = topTab == TopTab.MyStuff, + onClick = { selectTab(TopTab.MyStuff) }, + modifier = Modifier.testTag(TestTags.TAB_MY_STUFF), + icon = { + Icon( + if (topTab == TopTab.MyStuff) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = "My Stuff" + ) + }, + label = { Text("My Stuff") } + ) + NavigationBarItem( + selected = topTab == TopTab.Explore, + onClick = { selectTab(TopTab.Explore) }, + modifier = Modifier.testTag(TestTags.TAB_EXPLORE), + icon = { + Icon( + if (topTab == TopTab.Explore) Icons.Filled.Search else Icons.Outlined.Search, + contentDescription = "Explore" + ) + }, + label = { Text("Explore") } + ) + } + } + } + ) { innerPadding -> + val contentPadding = PaddingValues( + bottom = innerPadding.calculateBottomPadding() + ) + Box(modifier = Modifier.fillMaxSize().padding(contentPadding)) { + CompositionLocalProvider( + LocalTabBackEnabled provides currentStack.isEmpty() + ) { + Column(Modifier.fillMaxSize()) { + if (topTab == TopTab.Explore) { + ExploreSearchTopBar( + query = exploreQuery, + onQueryChange = { exploreQuery = it } + ) { + ExploreScanMenu() + IconButton( + onClick = { pushDetail(DetailRoute.Settings) }, + modifier = Modifier.testTag(TestTags.SETTINGS) + ) { + Icon(Icons.Default.Settings, contentDescription = "Settings") + } + } + } else { + TopAppBar( + title = { + Text( + topTab.label, + modifier = if (topTab == TopTab.MyStuff) { + Modifier.testTag(TestTags.CATALOG_TITLE) + } else { + Modifier + } ) }, - label = { Text("Explore") } + actions = { + IconButton( + onClick = { pushDetail(DetailRoute.Settings) }, + modifier = Modifier.testTag(TestTags.SETTINGS) + ) { + Icon(Icons.Default.Settings, contentDescription = "Settings") + } + } ) } + Box(Modifier.weight(1f).fillMaxWidth()) { + when (topTab) { + TopTab.Home -> HomeFeedScreen( + library = library, + onPlay = { songs, index -> player.playSource(songs, index) }, + onOpenAlbum = { openAlbumResolved(it) }, + onOpenArtist = { openArtistResolved(it) }, + onOpenPlaylist = { pl: Playlist -> + pushDetail(DetailRoute.Playlist(pl.id)) + }, + onOpenSongAlbum = { openAlbumForSong(it) } + ) + TopTab.MyStuff -> MyStuffScreen( + library = library, + nowPlaying = currentSong, + isPlaybackActive = playing, + onPlay = { songs, index -> player.playSource(songs, index) }, + onAddToQueue = { player.addToHotQueue(it) }, + onOpenAlbum = { openAlbumResolved(it) }, + onOpenArtist = { openArtistResolved(it) }, + onOpenPlaylist = { pl: Playlist -> + pushDetail(DetailRoute.Playlist(pl.id)) + }, + onOpenSongAlbum = { openAlbumForSong(it) } + ) + TopTab.Explore -> ExploreScreen( + query = exploreQuery, + onQueryChange = { exploreQuery = it }, + nowPlaying = currentSong, + isPlaybackActive = playing, + onPlay = { songs, index -> player.playSource(songs, index) }, + onAddToQueue = { player.addToHotQueue(it) }, + onOpenAlbum = { album -> openAlbumResolved(album) }, + onOpenArtist = { artist -> openArtistResolved(artist) }, + onOpenPlaylist = { pl: Playlist -> + pushDetail(DetailRoute.Playlist(pl.id)) + } + ) + } + } } } - } - ) { innerPadding -> - val contentPadding = if (edgeToEdgeDetail) { - PaddingValues(bottom = innerPadding.calculateBottomPadding()) - } else { - innerPadding - } - Box(modifier = Modifier.fillMaxSize().padding(contentPadding)) { - when (val d = detail) { + + val mediaDetailHost = @Composable { d: DetailRoute -> + when (d) { is DetailRoute.Album -> { val key = albumKey(d.album.name, d.album.artist) var resolvedAlbum by remember(key) { mutableStateOf(d.album) } @@ -959,43 +1036,37 @@ fun YuriApp( ) } is DetailRoute.Settings -> SettingsScreen(onBack = { popDetail() }) - null -> when (topTab) { - TopTab.Home -> HomeFeedScreen( - library = library, - onPlay = { songs, index -> player.playSource(songs, index) }, - onOpenAlbum = { openAlbumResolved(it) }, - onOpenArtist = { openArtistResolved(it) }, - onOpenPlaylist = { pl: Playlist -> - pushDetail(DetailRoute.Playlist(pl.id)) - }, - onOpenSongAlbum = { openAlbumForSong(it) } - ) - TopTab.MyStuff -> MyStuffScreen( - library = library, - nowPlaying = currentSong, - isPlaybackActive = playing, - onPlay = { songs, index -> player.playSource(songs, index) }, - onAddToQueue = { player.addToHotQueue(it) }, - onOpenAlbum = { openAlbumResolved(it) }, - onOpenArtist = { openArtistResolved(it) }, - onOpenPlaylist = { pl: Playlist -> - pushDetail(DetailRoute.Playlist(pl.id)) + } + } + + if (previousDetail != null) { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + mediaDetailHost(previousDetail) + } + } + if (detail != null) { + val progress = backProgress + val edge = backSwipeEdge + Surface( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + val dir = if (edge == BackEventCompat.EDGE_RIGHT) -1f else 1f + translationX = dir * progress * 48.dp.toPx() + val s = 1f - (0.06f * progress) + scaleX = s + scaleY = s + alpha = 1f - (0.12f * progress) + clip = true + shadowElevation = 16f * (1f - progress) + transformOrigin = TransformOrigin.Center }, - onOpenSongAlbum = { openAlbumForSong(it) } - ) - TopTab.Explore -> ExploreScreen( - query = exploreQuery, - onQueryChange = { exploreQuery = it }, - nowPlaying = currentSong, - isPlaybackActive = playing, - onPlay = { songs, index -> player.playSource(songs, index) }, - onAddToQueue = { player.addToHotQueue(it) }, - onOpenAlbum = { album -> openAlbumResolved(album) }, - onOpenArtist = { artist -> openArtistResolved(artist) }, - onOpenPlaylist = { pl: Playlist -> - pushDetail(DetailRoute.Playlist(pl.id)) - } - ) + color = MaterialTheme.colorScheme.background + ) { + mediaDetailHost(detail) } } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/BrowseAllLibraryScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/BrowseAllLibraryScreen.kt index 058e9256..0779dee9 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/BrowseAllLibraryScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/BrowseAllLibraryScreen.kt @@ -30,7 +30,7 @@ fun BrowseAllLibraryScreen( onOpenPlaylist: (Playlist) -> Unit, onOpenSongAlbum: (Song) -> Unit ) { - BackHandler(onBack = onBack) + BackHandler(enabled = LocalTabBackEnabled.current, onBack = onBack) val playlistsRepo: PlaylistRepository = koinInject() val songs by library.songs.collectAsState() val playlists by playlistsRepo.observePlaylistsResolved().collectAsState(initial = emptyList()) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffCatalogTab.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffCatalogTab.kt index 62383bf0..d429d2ae 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffCatalogTab.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/MyStuffCatalogTab.kt @@ -147,7 +147,9 @@ fun MyStuffCatalogTab( withContext(Dispatchers.IO) { remotePlaylists.syncOwnedToMyStuff() } } - BackHandler(enabled = page !is CatalogPage.Hub) { page = CatalogPage.Hub } + BackHandler(enabled = LocalTabBackEnabled.current && page !is CatalogPage.Hub) { + page = CatalogPage.Hub + } when (val p = page) { CatalogPage.Hub -> CatalogHub( diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt index bcef31d8..88bc695f 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SettingsScreen.kt @@ -96,7 +96,7 @@ fun SettingsScreen(onBack: () -> Unit) { if (stack.size > 1) stack = stack.dropLast(1) else onBack() } - BackHandler { pop() } + BackHandler(enabled = stack.size > 1) { pop() } when (val p = page) { SettingsPage.Hub -> SettingsHubScreen( diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/TabBack.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/TabBack.kt new file mode 100644 index 00000000..ba2db288 --- /dev/null +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/TabBack.kt @@ -0,0 +1,10 @@ +package capital.yuri.yuriplayer.activities.ui + +import androidx.compose.runtime.staticCompositionLocalOf + +/** + * Nested tab screens (catalog pages, browse-all) register [androidx.activity.compose.BackHandler] + * callbacks. Those must disable while a per-tab album/artist/playlist overlay is showing so + * predictive back pops the overlay instead of the tab's inner page. + */ +val LocalTabBackEnabled = staticCompositionLocalOf { true } From 91524f39fd452793660c6afc4e87e590f7c7501e Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:45:41 +0000 Subject: [PATCH 17/69] Unstick desktop sidebar resize Drag was adding each pointer delta to a stale fraction and then rescaling both panes, so the handles fought the cursor. Accumulate the gesture in pixels, start immediately (no touch slop), and only persist on release. Wider hit target and a visible grip. --- .../components/layout/DesktopBentoLayout.kt | 142 ++++++++++++++---- .../yuriplayer/desktop/DesktopLayoutStore.kt | 8 +- 2 files changed, 116 insertions(+), 34 deletions(-) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt index 328a1cbe..7f11da79 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt @@ -2,7 +2,12 @@ package capital.yuri.yuriplayer.components.layout import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -40,10 +45,18 @@ import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.PointerIcon +import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -107,18 +120,46 @@ fun DesktopBentoLayout( .fillMaxWidth() .padding(horizontal = 8.dp) ) { + val density = LocalDensity.current val totalPx = constraints.maxWidth.toFloat().coerceAtLeast(1f) - val gap = 8.dp - val handle = 6.dp - var leftF = leftFraction.coerceIn(0.12f, 0.4f) - var rightF = if (showSidebar) rightFraction.coerceIn(0.14f, 0.45f) else 0f - if (showSidebar && leftF + rightF > 0.72f) { - val scale = 0.72f / (leftF + rightF) - leftF *= scale - rightF *= scale + val handleW = 10.dp + val handlePx = with(density) { handleW.toPx() } + val minLeftPx = with(density) { 180.dp.toPx() } + val minRightPx = with(density) { 200.dp.toPx() } + val minCenterPx = with(density) { 280.dp.toPx() } + val handlesTotalPx = handlePx * if (showSidebar) 2 else 1 + + var leftDragPx by remember { mutableFloatStateOf(0f) } + var rightDragPx by remember { mutableFloatStateOf(0f) } + var draggingLeft by remember { mutableStateOf(false) } + var draggingRight by remember { mutableStateOf(false) } + + val rightBasePx = if (showSidebar) { + (rightFraction * totalPx).coerceAtLeast(minRightPx) + } else { + 0f } - val leftW = maxWidth * leftF - val rightW = maxWidth * rightF + val leftBasePx = (leftFraction * totalPx).coerceAtLeast(minLeftPx) + + fun maxLeftPx(rightPx: Float) = + (totalPx - rightPx - minCenterPx - handlesTotalPx).coerceAtLeast(minLeftPx) + + fun maxRightPx(leftPx: Float) = + (totalPx - leftPx - minCenterPx - handlesTotalPx).coerceAtLeast(minRightPx) + + val leftPx = if (draggingLeft) { + (leftBasePx + leftDragPx).coerceIn(minLeftPx, maxLeftPx(rightBasePx)) + } else { + leftBasePx.coerceIn(minLeftPx, maxLeftPx(rightBasePx)) + } + val rightPx = if (!showSidebar) { + 0f + } else if (draggingRight) { + (rightBasePx - rightDragPx).coerceIn(minRightPx, maxRightPx(leftPx)) + } else { + rightBasePx.coerceIn(minRightPx, maxRightPx(leftPx)) + } + Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) { LibraryRail( filter = libraryFilter, @@ -126,13 +167,23 @@ fun DesktopBentoLayout( items = libraryItems, selectedId = selectedLibraryId, onItem = onLibraryItem, - modifier = Modifier.width(leftW.coerceAtLeast(180.dp)).fillMaxHeight() + modifier = Modifier.width(with(density) { leftPx.toDp() }).fillMaxHeight() ) ResizeHandle( - onDragPx = { dx -> - onLeftFraction((leftFraction + dx / totalPx).coerceIn(0.14f, 0.38f), false) + onDelta = { leftDragPx += it }, + onStart = { + leftDragPx = 0f + draggingLeft = true }, - onDragEnd = { onLeftFraction(leftFraction, true) } + onStop = { + val px = (leftBasePx + leftDragPx).coerceIn( + minLeftPx, + maxLeftPx(rightBasePx) + ) + onLeftFraction(px / totalPx, true) + draggingLeft = false + leftDragPx = 0f + } ) Surface( modifier = Modifier.weight(1f).fillMaxHeight(), @@ -143,13 +194,23 @@ fun DesktopBentoLayout( } if (showSidebar) { ResizeHandle( - onDragPx = { dx -> - onRightFraction((rightFraction - dx / totalPx).coerceIn(0.16f, 0.42f), false) + onDelta = { rightDragPx += it }, + onStart = { + rightDragPx = 0f + draggingRight = true }, - onDragEnd = { onRightFraction(rightFraction, true) } + onStop = { + val px = (rightBasePx - rightDragPx).coerceIn( + minRightPx, + maxRightPx(leftPx) + ) + onRightFraction(px / totalPx, true) + draggingRight = false + rightDragPx = 0f + } ) Surface( - modifier = Modifier.width(rightW.coerceAtLeast(200.dp)).fillMaxHeight(), + modifier = Modifier.width(with(density) { rightPx.toDp() }).fillMaxHeight(), shape = MaterialTheme.shapes.large, color = MaterialTheme.colorScheme.surface ) { @@ -164,21 +225,42 @@ fun DesktopBentoLayout( @Composable private fun ResizeHandle( - onDragPx: (Float) -> Unit, - onDragEnd: () -> Unit + onDelta: (Float) -> Unit, + onStart: () -> Unit, + onStop: () -> Unit ) { + val interaction = remember { MutableInteractionSource() } + val hovered by interaction.collectIsHoveredAsState() + val onDeltaState = rememberUpdatedState(onDelta) + val onStartState = rememberUpdatedState(onStart) + val onStopState = rememberUpdatedState(onStop) + val dragState = rememberDraggableState { delta -> onDeltaState.value(delta) } Box( Modifier - .width(6.dp) + .width(10.dp) .fillMaxHeight() - .pointerInput(Unit) { - detectHorizontalDragGestures( - onDragEnd = { onDragEnd() }, - onDragCancel = { onDragEnd() }, - onHorizontalDrag = { _, dragAmount -> onDragPx(dragAmount) } + .hoverable(interaction) + .pointerHoverIcon(PointerIcon.Hand) + .draggable( + state = dragState, + orientation = Orientation.Horizontal, + startDragImmediately = true, + onDragStarted = { onStartState.value() }, + onDragStopped = { onStopState.value() } + ), + contentAlignment = Alignment.Center + ) { + Box( + Modifier + .width(if (hovered) 4.dp else 2.dp) + .height(48.dp) + .clip(RoundedCornerShape(2.dp)) + .background( + if (hovered) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.outline.copy(alpha = 0.45f) ) - } - ) + ) + } } @Composable diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopLayoutStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopLayoutStore.kt index 55f6690e..45be43b6 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopLayoutStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopLayoutStore.kt @@ -52,10 +52,10 @@ class DesktopLayoutStore(configDir: String) { companion object { const val DEFAULT_LEFT = 0.22f const val DEFAULT_RIGHT = 0.26f - const val MIN_LEFT = 0.14f - const val MAX_LEFT = 0.38f - const val MIN_RIGHT = 0.16f - const val MAX_RIGHT = 0.42f + const val MIN_LEFT = 0.10f + const val MAX_LEFT = 0.55f + const val MIN_RIGHT = 0.12f + const val MAX_RIGHT = 0.55f const val MIN_CENTER = 0.28f } } From 388f00f0f6d022d246cfcb02e2eeb7ca3df3ffad Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 18:58:41 +0000 Subject: [PATCH 18/69] Match Android provider settings on desktop Providers is its own settings category: list, add Jellyfin or Subsonic, then the same identity / connection / enabled / save / delete editor. Test connection is a real button on each editor (and on each saved server in the list). --- .../activities/ui/ProvidersSettingsScreen.kt | 58 +-- .../components/settings/SettingsRows.kt | 223 ++++++++++ .../components/settings/SettingsShell.kt | 2 + .../yuri/yuriplayer/desktop/DesktopSession.kt | 111 +++-- .../yuriplayer/desktop/ui/DesktopProviders.kt | 398 ++++++++++++++++++ .../yuriplayer/desktop/ui/DesktopSettings.kt | 116 +---- 6 files changed, 748 insertions(+), 160 deletions(-) create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsRows.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ProvidersSettingsScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ProvidersSettingsScreen.kt index 63b4a18a..f954ebce 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ProvidersSettingsScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ProvidersSettingsScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -221,6 +222,31 @@ fun ProviderEditorScreen( val title = if (existingId == null) "Add provider" else "Edit provider" + fun runTest() { + if (testing) return + scope.launch { + testing = true + status = null + val result = withContext(Dispatchers.IO) { + when (type) { + SourceType.JELLYFIN -> + jellyfin.authenticate(url, username, password) + .map { "Connected as user ${it.userId.take(8)}…" } + else -> { + val session = SubsonicClient.Session( + baseUrl = SourceInstanceRepository.normalizeBaseUrl(url), + username = username, + password = password + ) + subsonic.ping(session).map { "Ping ok" } + } + } + } + status = result.getOrElse { e -> "Failed: ${e.message ?: e}" } + testing = false + } + } + Column( modifier = Modifier .fillMaxSize() @@ -305,6 +331,13 @@ fun ProviderEditorScreen( }, modifier = Modifier.fillMaxWidth() ) + Spacer(modifier = Modifier.height(12.dp)) + OutlinedButton( + onClick = { runTest() }, + enabled = !testing && url.isNotBlank() && username.isNotBlank() + ) { + Text(if (testing) "Testing…" else "Test connection") + } } } @@ -354,30 +387,7 @@ fun ProviderEditorScreen( SettingsNavRow( title = if (testing) "Testing…" else "Test connection", subtitle = "Authenticate against the server", - onClick = { - if (testing) return@SettingsNavRow - scope.launch { - testing = true - status = null - val result = withContext(Dispatchers.IO) { - when (type) { - SourceType.JELLYFIN -> - jellyfin.authenticate(url, username, password) - .map { "Connected as user ${it.userId.take(8)}…" } - else -> { - val session = SubsonicClient.Session( - baseUrl = SourceInstanceRepository.normalizeBaseUrl(url), - username = username, - password = password - ) - subsonic.ping(session).map { "Ping ok" } - } - } - } - status = result.getOrElse { e -> "Failed: ${e.message ?: e}" } - testing = false - } - } + onClick = { runTest() } ) SettingsNavRow( title = "Save", diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsRows.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsRows.kt new file mode 100644 index 00000000..71da4d89 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsRows.kt @@ -0,0 +1,223 @@ +package capital.yuri.yuriplayer.components.settings + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +@Composable +fun SettingsSectionTitle(title: String) { + Text( + text = title, + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(vertical = 10.dp) + ) +} + +@Composable +fun SettingsGroup( + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit +) { + Surface( + modifier = modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f), + tonalElevation = 0.dp + ) { + Column(content = content) + } + Spacer(modifier = Modifier.height(8.dp)) +} + +@Composable +fun SettingsNavRow( + title: String, + subtitle: String? = null, + icon: ImageVector? = null, + trailing: String? = null, + onClick: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 16.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (icon != null) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(22.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + } + Column(modifier = Modifier.weight(1f)) { + Text(title, style = MaterialTheme.typography.bodyLarge) + if (!subtitle.isNullOrBlank()) { + Text( + subtitle, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + } + if (!trailing.isNullOrBlank()) { + Text( + trailing, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.45f) + ) + } + } +} + +@Composable +fun SettingsSwitchRow( + title: String, + subtitle: String? = null, + icon: ImageVector? = null, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onCheckedChange(!checked) } + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (icon != null) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(22.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + } + Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) { + Text(title, style = MaterialTheme.typography.bodyLarge) + if (!subtitle.isNullOrBlank()) { + Text( + subtitle, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + } + Switch(checked = checked, onCheckedChange = onCheckedChange) + } +} + +@Composable +fun SettingsNote(text: String) { + Text( + text = text, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(vertical = 4.dp) + ) +} + +@Composable +fun SettingsChoiceRow( + title: String, + subtitle: String? = null, + trailing: String, + options: List>, + onSelect: (String) -> Unit +) { + var open by remember { mutableStateOf(false) } + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { open = true } + .padding(horizontal = 16.dp, vertical = 14.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) { + Text(title, style = MaterialTheme.typography.bodyLarge) + if (!subtitle.isNullOrBlank()) { + Text( + subtitle, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + } + Text( + trailing, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.45f) + ) + DropdownMenu(expanded = open, onDismissRequest = { open = false }) { + options.forEach { (id, label) -> + DropdownMenuItem( + text = { Text(label) }, + onClick = { + open = false + onSelect(id) + } + ) + } + } + } +} + +@Composable +fun SettingsTopBar( + title: String, + onBack: () -> Unit, + actions: @Composable () -> Unit = {} +) { + Row( + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + Text( + text = title, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold + ) + } + actions() + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsShell.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsShell.kt index d245e6b0..8cd0f8e6 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsShell.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/settings/SettingsShell.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Cloud import androidx.compose.material.icons.filled.ColorLens import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.LibraryMusic @@ -34,6 +35,7 @@ enum class SettingsCategory(val label: String, val icon: ImageVector) { Appearance("Appearance", Icons.Default.ColorLens), Playback("Playback", Icons.Default.PlayArrow), Library("Library", Icons.Default.LibraryMusic), + Providers("Providers", Icons.Default.Cloud), About("About", Icons.Default.Info) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 32c5c8e3..27fc6f68 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.swing.Swing +import kotlinx.coroutines.withContext import java.io.File import java.util.UUID @@ -127,45 +128,103 @@ class DesktopSession { } fun addJellyfin(name: String, baseUrl: String, username: String, password: String) { - scope.launch(Dispatchers.IO) { - _scanMessage.value = "Signing in to Jellyfin…" - val seed = RemoteAccount( - id = UUID.randomUUID().toString(), - kind = SourceKind.JELLYFIN, - name = name.ifBlank { "Jellyfin" }, - baseUrl = baseUrl, - username = username, - secret = password - ) - jellyfin.authenticate(seed) - .onSuccess { - sources.upsertRemote(it) - rescan() - } - .onFailure { _scanMessage.value = "Jellyfin: ${it.message}" } - } + saveRemote( + existingId = null, + kind = SourceKind.JELLYFIN, + name = name, + baseUrl = baseUrl, + username = username, + password = password, + enabled = true + ) } fun addSubsonic(name: String, baseUrl: String, username: String, password: String) { + saveRemote( + existingId = null, + kind = SourceKind.SUBSONIC, + name = name, + baseUrl = baseUrl, + username = username, + password = password, + enabled = true + ) + } + + fun saveRemote( + existingId: String?, + kind: SourceKind, + name: String, + baseUrl: String, + username: String, + password: String, + enabled: Boolean, + onDone: (ok: Boolean, message: String) -> Unit = { ok, msg -> + if (!ok) _scanMessage.value = msg + } + ) { scope.launch(Dispatchers.IO) { - _scanMessage.value = "Signing in to Subsonic…" + val existing = existingId?.let { id -> sources.remotes.value.firstOrNull { it.id == id } } val seed = RemoteAccount( - id = UUID.randomUUID().toString(), - kind = SourceKind.SUBSONIC, - name = name.ifBlank { "Subsonic" }, + id = existing?.id ?: UUID.randomUUID().toString(), + kind = kind, + name = name.ifBlank { + if (kind == SourceKind.JELLYFIN) "Jellyfin" else "Subsonic" + }, baseUrl = baseUrl, username = username, - secret = password + secret = password.ifBlank { existing?.secret.orEmpty() }, + enabled = enabled, + accessToken = existing?.accessToken, + userId = existing?.userId ) - subsonic.ping(seed) - .onSuccess { - sources.upsertRemote(it) + val result = when (kind) { + SourceKind.JELLYFIN -> jellyfin.authenticate(seed) + SourceKind.SUBSONIC -> subsonic.ping(seed) + SourceKind.LOCAL -> Result.failure(IllegalArgumentException("Not a remote provider")) + } + result + .onSuccess { signed -> + sources.upsertRemote(signed.copy(enabled = enabled, name = seed.name)) rescan() + withContext(Dispatchers.Swing) { onDone(true, "Saved") } } - .onFailure { _scanMessage.value = "Subsonic: ${it.message}" } + .onFailure { + withContext(Dispatchers.Swing) { + onDone(false, it.message ?: "Could not reach server") + } + } + } + } + + suspend fun testConnection( + kind: SourceKind, + url: String, + username: String, + password: String + ): Result { + val seed = RemoteAccount( + id = "test", + kind = kind, + name = "", + baseUrl = url, + username = username, + secret = password + ) + return when (kind) { + SourceKind.JELLYFIN -> jellyfin.authenticate(seed).map { signed -> + val who = signed.userId?.take(8)?.plus("…") ?: signed.username + "Connected as $who" + } + SourceKind.SUBSONIC -> subsonic.ping(seed).map { "Ping ok" } + SourceKind.LOCAL -> Result.failure(IllegalArgumentException("Not a remote provider")) } } + fun setRemoteEnabled(id: String, enabled: Boolean) { + sources.setRemoteEnabled(id, enabled) + } + fun removeRemote(id: String) { sources.removeRemote(id) rescan() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt new file mode 100644 index 00000000..e2b92c40 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt @@ -0,0 +1,398 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Cloud +import androidx.compose.material.icons.filled.Dns +import androidx.compose.material.icons.filled.Folder +import androidx.compose.material.icons.filled.Storage +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.settings.SettingsGroup +import capital.yuri.yuriplayer.components.settings.SettingsNavRow +import capital.yuri.yuriplayer.components.settings.SettingsNote +import capital.yuri.yuriplayer.components.settings.SettingsSectionTitle +import capital.yuri.yuriplayer.components.settings.SettingsSwitchRow +import capital.yuri.yuriplayer.components.settings.SettingsTopBar +import capital.yuri.yuriplayer.core.source.RemoteAccount +import capital.yuri.yuriplayer.core.source.SourceKind +import capital.yuri.yuriplayer.desktop.DesktopSession +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +private sealed class ProviderPage { + data object List : ProviderPage() + data class Editor(val existing: RemoteAccount?, val kind: SourceKind) : ProviderPage() +} + +@Composable +fun DesktopProvidersPane( + session: DesktopSession, + onOpenLocal: () -> Unit +) { + var page by remember { mutableStateOf(ProviderPage.List) } + when (val p = page) { + ProviderPage.List -> ProvidersList( + session = session, + onOpenLocal = onOpenLocal, + onOpen = { page = ProviderPage.Editor(it, it.kind) }, + onAdd = { page = ProviderPage.Editor(null, it) } + ) + is ProviderPage.Editor -> ProviderEditor( + session = session, + existing = p.existing, + kind = p.kind, + onBack = { page = ProviderPage.List } + ) + } +} + +@Composable +private fun ProvidersList( + session: DesktopSession, + onOpenLocal: () -> Unit, + onOpen: (RemoteAccount) -> Unit, + onAdd: (SourceKind) -> Unit +) { + val remotes by session.sources.remotes.collectAsState() + val scope = rememberCoroutineScope() + var listStatus by remember { mutableStateOf(null) } + var testingId by remember { mutableStateOf(null) } + + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + SettingsNote("Local library and remote servers YuriPlayer can play from.") + Spacer(Modifier.height(8.dp)) + + SettingsSectionTitle("On this device") + SettingsGroup { + SettingsNavRow( + title = "Local files", + subtitle = "Default music folders plus any you add", + icon = Icons.Default.Folder, + trailing = "Configure", + onClick = onOpenLocal + ) + } + + SettingsSectionTitle("Servers") + if (remotes.isEmpty()) { + SettingsGroup { + SettingsNavRow( + title = "No servers yet", + subtitle = "Add Jellyfin or Subsonic / OpenSubsonic", + icon = Icons.Default.Storage, + onClick = { onAdd(SourceKind.JELLYFIN) } + ) + } + } else { + SettingsGroup { + remotes.forEach { row -> + Column { + SettingsNavRow( + title = row.name.ifBlank { kindLabel(row.kind) }, + subtitle = buildString { + append(kindLabel(row.kind)) + if (!row.enabled) append(" · off") + append(" · ") + append(row.baseUrl.removePrefix("https://").removePrefix("http://")) + }, + icon = iconFor(row.kind), + trailing = if (row.enabled) "On" else "Off", + onClick = { onOpen(row) } + ) + Row( + Modifier.fillMaxWidth().padding(start = 54.dp, end = 16.dp, bottom = 8.dp), + horizontalArrangement = Arrangement.End + ) { + TextButton( + enabled = testingId != row.id, + onClick = { + testingId = row.id + listStatus = null + scope.launch { + val result = withContext(Dispatchers.IO) { + session.testConnection( + row.kind, + row.baseUrl, + row.username, + row.secret + ) + } + listStatus = result.getOrElse { "Failed: ${it.message ?: it}" } + testingId = null + } + } + ) { + Text(if (testingId == row.id) "Testing…" else "Test connection") + } + } + } + } + } + } + + if (listStatus != null) { + Text( + listStatus!!, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(vertical = 8.dp) + ) + } + + SettingsSectionTitle("Add") + SettingsGroup { + SettingsNavRow( + title = "Jellyfin", + subtitle = "Self-hosted media server", + icon = Icons.Default.Dns, + onClick = { onAdd(SourceKind.JELLYFIN) } + ) + SettingsNavRow( + title = "Subsonic / OpenSubsonic", + subtitle = "Navidrome, Gonic, Official, …", + icon = Icons.Default.Cloud, + onClick = { onAdd(SourceKind.SUBSONIC) } + ) + } + Spacer(Modifier.height(24.dp)) + } +} + +@Composable +private fun ProviderEditor( + session: DesktopSession, + existing: RemoteAccount?, + kind: SourceKind, + onBack: () -> Unit +) { + val scope = rememberCoroutineScope() + var label by remember { mutableStateOf(existing?.name.orEmpty().ifBlank { + if (kind == SourceKind.JELLYFIN) "Jellyfin" else "Subsonic" + }) } + var url by remember { mutableStateOf(existing?.baseUrl.orEmpty()) } + var username by remember { mutableStateOf(existing?.username.orEmpty()) } + var password by remember { mutableStateOf(existing?.secret.orEmpty()) } + var showPassword by remember { mutableStateOf(false) } + var enabled by remember { mutableStateOf(existing?.enabled ?: true) } + var status by remember { mutableStateOf(null) } + var testing by remember { mutableStateOf(false) } + var saving by remember { mutableStateOf(false) } + var confirmDelete by remember { mutableStateOf(false) } + + fun runTest() { + if (testing) return + scope.launch { + testing = true + status = null + val result = withContext(Dispatchers.IO) { + session.testConnection(kind, url, username, password) + } + status = result.getOrElse { "Failed: ${it.message ?: it}" } + testing = false + } + } + + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + SettingsTopBar( + title = if (existing == null) "Add provider" else "Edit provider", + onBack = onBack + ) + Text( + kindLabel(kind), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.primary + ) + Spacer(Modifier.height(8.dp)) + + SettingsSectionTitle("Identity") + SettingsGroup { + Column(Modifier.padding(16.dp)) { + OutlinedTextField( + value = label, + onValueChange = { label = it }, + label = { Text("Label") }, + placeholder = { Text("Home server") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(4.dp)) + Text( + "Shown in the providers list and library filters.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) + ) + } + } + + SettingsSectionTitle("Connection") + SettingsGroup { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + OutlinedTextField( + value = url, + onValueChange = { url = it }, + label = { Text("Server URL") }, + placeholder = { Text("https://music.example.com") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = username, + onValueChange = { username = it }, + label = { Text("Username") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + OutlinedTextField( + value = password, + onValueChange = { password = it }, + label = { Text("Password") }, + singleLine = true, + visualTransformation = if (showPassword) { + VisualTransformation.None + } else { + PasswordVisualTransformation() + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + trailingIcon = { + IconButton(onClick = { showPassword = !showPassword }) { + Icon( + if (showPassword) Icons.Default.VisibilityOff else Icons.Default.Visibility, + contentDescription = if (showPassword) "Hide password" else "Show password" + ) + } + }, + modifier = Modifier.fillMaxWidth() + ) + OutlinedButton( + onClick = { runTest() }, + enabled = !testing && url.isNotBlank() && username.isNotBlank() + ) { + Text(if (testing) "Testing…" else "Test connection") + } + } + } + + SettingsSectionTitle("Options") + SettingsGroup { + SettingsSwitchRow( + title = "Enabled", + subtitle = "Include this provider when browsing and playing", + checked = enabled, + onCheckedChange = { enabled = it } + ) + } + + if (status != null) { + Text( + status!!, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(vertical = 8.dp) + ) + } + + SettingsSectionTitle("Actions") + SettingsGroup { + SettingsNavRow( + title = if (testing) "Testing…" else "Test connection", + subtitle = "Authenticate against the server", + onClick = { runTest() } + ) + SettingsNavRow( + title = if (saving) "Saving…" else "Save", + subtitle = "Write label and credentials", + onClick = { + if (saving) return@SettingsNavRow + saving = true + status = null + session.saveRemote( + existingId = existing?.id, + kind = kind, + name = label, + baseUrl = url, + username = username, + password = password, + enabled = enabled + ) { ok, message -> + saving = false + if (ok) onBack() else status = message + } + } + ) + if (existing != null) { + SettingsNavRow( + title = "Delete provider", + subtitle = "Remove this server from YuriPlayer", + onClick = { confirmDelete = true } + ) + } + } + Spacer(Modifier.height(24.dp)) + } + + if (confirmDelete && existing != null) { + AlertDialog( + onDismissRequest = { confirmDelete = false }, + title = { Text("Delete provider?") }, + text = { + Text("“${label.ifBlank { kindLabel(kind) }}” will be removed. Local files are unaffected.") + }, + confirmButton = { + TextButton( + onClick = { + session.removeRemote(existing.id) + confirmDelete = false + onBack() + } + ) { Text("Delete") } + }, + dismissButton = { + TextButton(onClick = { confirmDelete = false }) { Text("Cancel") } + } + ) + } +} + +private fun kindLabel(kind: SourceKind): String = when (kind) { + SourceKind.JELLYFIN -> "Jellyfin" + SourceKind.SUBSONIC -> "Subsonic / OpenSubsonic" + SourceKind.LOCAL -> "Local" +} + +private fun iconFor(kind: SourceKind) = when (kind) { + SourceKind.JELLYFIN -> Icons.Default.Dns + SourceKind.SUBSONIC -> Icons.Default.Cloud + SourceKind.LOCAL -> Icons.Default.Folder +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt index fbcde9d0..1e5cedc4 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt @@ -1,6 +1,5 @@ package capital.yuri.yuriplayer.desktop.ui -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -11,18 +10,12 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Folder -import androidx.compose.material.icons.filled.Visibility -import androidx.compose.material.icons.filled.VisibilityOff -import androidx.compose.material3.Button import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -33,9 +26,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties @@ -44,7 +34,6 @@ import capital.yuri.yuriplayer.components.settings.SettingsShell import capital.yuri.yuriplayer.components.theme.AccentPicker import capital.yuri.yuriplayer.components.theme.ThemeModePicker import capital.yuri.yuriplayer.core.platform.appDirectories -import capital.yuri.yuriplayer.core.source.SourceKind import capital.yuri.yuriplayer.desktop.DesktopSession import javax.swing.JFileChooser @@ -71,6 +60,7 @@ fun DesktopSettingsDialog( SettingsCategory.Appearance, SettingsCategory.Playback, SettingsCategory.Library, + SettingsCategory.Providers, SettingsCategory.About ) ) { @@ -78,6 +68,10 @@ fun DesktopSettingsDialog( SettingsCategory.Appearance -> AppearancePane(session) SettingsCategory.Playback -> PlaybackPane() SettingsCategory.Library -> LibraryPane(session) + SettingsCategory.Providers -> DesktopProvidersPane( + session = session, + onOpenLocal = { category = SettingsCategory.Library } + ) SettingsCategory.About -> AboutPane() } } @@ -127,23 +121,14 @@ private fun PlaybackPane() { @Composable private fun LibraryPane(session: DesktopSession) { val folders by session.sources.extraFolders.collectAsState() - val remotes by session.sources.remotes.collectAsState() val status by session.scanMessage.collectAsState() val defaults = remember { appDirectories().defaultMusicRoots } - var jfName by remember { mutableStateOf("") } - var jfUrl by remember { mutableStateOf("") } - var jfUser by remember { mutableStateOf("") } - var jfPass by remember { mutableStateOf("") } - var ssName by remember { mutableStateOf("") } - var ssUrl by remember { mutableStateOf("") } - var ssUser by remember { mutableStateOf("") } - var ssPass by remember { mutableStateOf("") } Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { Text(status, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.primary) Spacer(Modifier.height(16.dp)) Text("Local folders", style = MaterialTheme.typography.titleMedium) Text( - "Default music folders are scanned automatically.", + "Default music folders are scanned automatically. Jellyfin and Subsonic live under Providers.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) @@ -163,100 +148,11 @@ private fun LibraryPane(session: DesktopSession) { Icon(Icons.Default.Folder, contentDescription = null) Text(" Add folder") } - Spacer(Modifier.height(24.dp)) - Text("Servers", style = MaterialTheme.typography.titleMedium) - Text( - "Jellyfin and Subsonic / Navidrome / OpenSubsonic — same as on Android.", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) - ) - remotes.forEach { remote -> - Row(Modifier.fillMaxWidth().padding(top = 8.dp), verticalAlignment = Alignment.CenterVertically) { - Column(Modifier.weight(1f)) { - Text(remote.name.ifBlank { remote.kind.name }) - Text( - "${remote.kind.name.lowercase()} · ${remote.baseUrl}", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) - ) - } - TextButton(onClick = { session.removeRemote(remote.id) }) { Text("Remove") } - } - } - Spacer(Modifier.height(20.dp)) - Text("Add Jellyfin", style = MaterialTheme.typography.titleSmall) - ServerForm( - name = jfName, onName = { jfName = it }, - url = jfUrl, onUrl = { jfUrl = it }, - user = jfUser, onUser = { jfUser = it }, - password = jfPass, onPassword = { jfPass = it }, - urlHint = "https://jellyfin.example.com" - ) - Button( - onClick = { - session.addJellyfin(jfName, jfUrl, jfUser, jfPass) - jfPass = "" - }, - enabled = jfUrl.isNotBlank() && jfUser.isNotBlank() && jfPass.isNotBlank() - ) { Text("Connect Jellyfin") } - Spacer(Modifier.height(20.dp)) - Text("Add Subsonic / Navidrome", style = MaterialTheme.typography.titleSmall) - ServerForm( - name = ssName, onName = { ssName = it }, - url = ssUrl, onUrl = { ssUrl = it }, - user = ssUser, onUser = { ssUser = it }, - password = ssPass, onPassword = { ssPass = it }, - urlHint = "https://navidrome.example.com" - ) - Button( - onClick = { - session.addSubsonic(ssName, ssUrl, ssUser, ssPass) - ssPass = "" - }, - enabled = ssUrl.isNotBlank() && ssUser.isNotBlank() && ssPass.isNotBlank() - ) { Text("Connect Subsonic") } Spacer(Modifier.height(16.dp)) OutlinedButton(onClick = { session.rescan() }) { Text("Rescan libraries") } } } -@Composable -private fun ServerForm( - name: String, - onName: (String) -> Unit, - url: String, - onUrl: (String) -> Unit, - user: String, - onUser: (String) -> Unit, - password: String, - onPassword: (String) -> Unit, - urlHint: String -) { - var showPass by remember { mutableStateOf(false) } - Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(vertical = 8.dp)) { - OutlinedTextField(name, onName, label = { Text("Label") }, singleLine = true, modifier = Modifier.fillMaxWidth()) - OutlinedTextField(url, onUrl, label = { Text("Server URL") }, placeholder = { Text(urlHint) }, singleLine = true, modifier = Modifier.fillMaxWidth()) - OutlinedTextField(user, onUser, label = { Text("Username") }, singleLine = true, modifier = Modifier.fillMaxWidth()) - OutlinedTextField( - password, - onPassword, - label = { Text("Password") }, - singleLine = true, - modifier = Modifier.fillMaxWidth(), - visualTransformation = if (showPass) VisualTransformation.None else PasswordVisualTransformation(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), - trailingIcon = { - IconButton(onClick = { showPass = !showPass }) { - Icon( - if (showPass) Icons.Default.VisibilityOff else Icons.Default.Visibility, - contentDescription = if (showPass) "Hide password" else "Show password" - ) - } - } - ) - } -} - private fun pickDirectory(): String? { val chooser = JFileChooser() chooser.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY From f17cd489d24232c38185facf546f6d0d5dffbeee Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:00:52 +0000 Subject: [PATCH 19/69] Index a source as soon as it is added Saving a Jellyfin or Subsonic provider now kicks off a forced first scan of that server (queued if another sync is already running). Desktop does the same for a new remote or extra folder instead of waiting on a full library rescan. --- .../activities/ui/ProvidersSettingsScreen.kt | 5 +- .../yuriplayer/data/LibraryScanService.kt | 25 ++++++++- .../yuri/yuriplayer/desktop/DesktopSession.kt | 55 +++++++++++++++++-- .../yuriplayer/desktop/ui/DesktopProviders.kt | 9 +++ 4 files changed, 86 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ProvidersSettingsScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ProvidersSettingsScreen.kt index f954ebce..5a0a9efc 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ProvidersSettingsScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/ProvidersSettingsScreen.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.data.ExploreSearchService import capital.yuri.yuriplayer.data.db.SourceInstanceEntity import capital.yuri.yuriplayer.data.SyncInterval import capital.yuri.yuriplayer.data.source.JellyfinClient @@ -181,6 +182,7 @@ fun ProviderEditorScreen( val sourcesRepo: SourceInstanceRepository = koinInject() val jellyfin: JellyfinClient = koinInject() val subsonic: SubsonicClient = koinInject() + val explore: ExploreSearchService = koinInject() val scope = rememberCoroutineScope() var loaded by remember { mutableStateOf(null) } @@ -403,12 +405,13 @@ fun ProviderEditorScreen( } } if (existingId == null) { - when (type) { + val newId = when (type) { SourceType.JELLYFIN -> sourcesRepo.addJellyfin(display, normalized, username, password) else -> sourcesRepo.addSubsonic(display, normalized, username, password) } + explore.requestRemoteScan(force = true, sourceId = newId) } else { val base = loaded ?: sourcesRepo.get(existingId) ?: return@withContext val interval = if (partialOverride == "default") { diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanService.kt b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanService.kt index dacf9d8d..7a83b311 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanService.kt @@ -33,6 +33,8 @@ class LibraryScanService : Service() { private val scanDispatcher = Dispatchers.IO.limitedParallelism(1) private val scope = CoroutineScope(SupervisorJob() + scanDispatcher) private var work: Job? = null + @Volatile private var queuedForce = false + @Volatile private var queuedSourceId: Long? = null override fun onBind(intent: Intent?): IBinder? = null @@ -58,8 +60,16 @@ class LibraryScanService : Service() { } // Already scanning — do not cancel unless this is an explicit full force-rescan. + // A newly added source always queues so its first index still runs. if (action == ACTION_REMOTE && work?.isActive == true) { - if (!force || sourceId != null) { + if (sourceId != null) { + queuedForce = true + queuedSourceId = sourceId + Log.i(TAG, "queue initial index sourceId=$sourceId") + startAsForeground("Syncing libraries", explore.scanProgress.value ?: "Working…") + return START_NOT_STICKY + } + if (!force) { Log.i(TAG, "remote scan already active — ignore duplicate start") startAsForeground("Syncing libraries", explore.scanProgress.value ?: "Working…") return START_NOT_STICKY @@ -98,8 +108,17 @@ class LibraryScanService : Service() { Log.e(TAG, "scan service failed", e) notifier.finish("Scan failed", e.message ?: "Unknown error") } finally { - stopForegroundCompat() - stopSelf(startId) + val nextId = queuedSourceId + val nextForce = queuedForce + queuedSourceId = null + queuedForce = false + if (nextId != null) { + Log.i(TAG, "start queued index sourceId=$nextId") + startRemote(applicationContext, nextForce, nextId) + } else { + stopForegroundCompat() + stopSelf(startId) + } } } return START_NOT_STICKY diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 27fc6f68..cc78fb23 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -119,7 +119,7 @@ class DesktopSession { fun addFolder(path: String) { sources.addFolder(path) - rescan() + indexFolder(path) } fun removeFolder(path: String) { @@ -185,9 +185,10 @@ class DesktopSession { } result .onSuccess { signed -> - sources.upsertRemote(signed.copy(enabled = enabled, name = seed.name)) - rescan() - withContext(Dispatchers.Swing) { onDone(true, "Saved") } + val saved = signed.copy(enabled = enabled, name = seed.name) + sources.upsertRemote(saved) + if (enabled) indexRemote(saved) + withContext(Dispatchers.Swing) { onDone(true, "Saved — indexing ${saved.name}") } } .onFailure { withContext(Dispatchers.Swing) { @@ -223,6 +224,9 @@ class DesktopSession { fun setRemoteEnabled(id: String, enabled: Boolean) { sources.setRemoteEnabled(id, enabled) + if (enabled) { + sources.remotes.value.firstOrNull { it.id == id }?.let { indexRemote(it) } + } } fun removeRemote(id: String) { @@ -230,6 +234,49 @@ class DesktopSession { rescan() } + fun indexFolder(path: String) { + val root = File(path) + if (!root.isDirectory) return + scope.launch(Dispatchers.IO) { + _scanMessage.value = "Indexing ${root.name}…" + val added = LocalLibraryScanner.scan(listOf(root)) + mergeTracks(added) + _scanMessage.value = "${root.name}: ${added.size} tracks · ${_tracks.value.size} total" + } + } + + fun indexRemote(account: RemoteAccount) { + if (!account.enabled || account.kind == SourceKind.LOCAL) return + scope.launch(Dispatchers.IO) { + _scanMessage.value = "Indexing ${account.name}…" + val result = when (account.kind) { + SourceKind.JELLYFIN -> { + val authed = if (account.accessToken.isNullOrBlank()) { + jellyfin.authenticate(account).getOrElse { + _scanMessage.value = "${account.name}: ${it.message}" + return@launch + }.also { sources.upsertRemote(it) } + } else account + jellyfin.listTracks(authed) + } + SourceKind.SUBSONIC -> subsonic.listTracks(account) + SourceKind.LOCAL -> return@launch + } + result + .onSuccess { incoming -> + mergeTracks(incoming) + _scanMessage.value = + "${account.name}: ${incoming.size} tracks · ${_tracks.value.size} total" + } + .onFailure { _scanMessage.value = "${account.name}: ${it.message}" } + } + } + + private fun mergeTracks(incoming: List) { + if (incoming.isEmpty()) return + _tracks.value = (_tracks.value + incoming).distinctBy { it.id } + } + fun rescan() { scope.launch(Dispatchers.IO) { val defaultRoots = LocalLibraryScanner.defaultRoots() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt index e2b92c40..29788ca0 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt @@ -86,12 +86,21 @@ private fun ProvidersList( onAdd: (SourceKind) -> Unit ) { val remotes by session.sources.remotes.collectAsState() + val scanMessage by session.scanMessage.collectAsState() val scope = rememberCoroutineScope() var listStatus by remember { mutableStateOf(null) } var testingId by remember { mutableStateOf(null) } Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { SettingsNote("Local library and remote servers YuriPlayer can play from.") + if (scanMessage.isNotBlank()) { + Text( + scanMessage, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(vertical = 4.dp) + ) + } Spacer(Modifier.height(8.dp)) SettingsSectionTitle("On this device") From bd79d75716380fcb993193496352004f35e28490 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:06:14 +0000 Subject: [PATCH 20/69] Scroll library chips and keep covers 1:1 Filter chips in the desktop rail scroll horizontally so Artists no longer wraps. Album art frames stay square; non-square images keep their ratio and sit centered in that square. --- .../yuri/yuriplayer/activities/ui/AlbumArt.kt | 10 +++- .../activities/ui/SwipeableAlbumArt.kt | 3 +- .../yuriplayer/components/art/CoverArt.kt | 19 +++++-- .../components/layout/DesktopBentoLayout.kt | 57 +++++++++++-------- .../components/player/NowPlayingSidebar.kt | 3 +- 5 files changed, 59 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumArt.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumArt.kt index 62fa4530..899187fe 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumArt.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumArt.kt @@ -3,6 +3,7 @@ package capital.yuri.yuriplayer.activities.ui import android.graphics.Bitmap import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size @@ -89,7 +90,11 @@ fun AlbumArt( } val shape = RoundedCornerShape(corner) - val boxMod = if (size != null) modifier.size(size).clip(shape) else modifier.clip(shape) + val boxMod = if (size != null) { + modifier.size(size).clip(shape) + } else { + modifier.aspectRatio(1f).clip(shape) + } Box( modifier = boxMod.background(MaterialTheme.colorScheme.primaryContainer), @@ -100,7 +105,8 @@ fun AlbumArt( Image( bitmap = bmp.asImageBitmap(), contentDescription = song?.displayAlbum, - contentScale = ContentScale.Crop, + contentScale = ContentScale.Fit, + alignment = Alignment.Center, filterQuality = if (decodeSize >= AlbumArtCache.HERO_DECODE_SIZE) { FilterQuality.High } else { diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SwipeableAlbumArt.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SwipeableAlbumArt.kt index 3431edc5..57bd03c9 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SwipeableAlbumArt.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/SwipeableAlbumArt.kt @@ -384,7 +384,8 @@ private fun ArtCard(bitmap: Bitmap?, modifier: Modifier = Modifier) { Image( bitmap = bitmap.asImageBitmap(), contentDescription = null, - contentScale = ContentScale.Crop, + contentScale = ContentScale.Fit, + alignment = Alignment.Center, filterQuality = FilterQuality.High, modifier = Modifier.fillMaxSize() ) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt index f8f1e25a..1313881f 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt @@ -2,6 +2,7 @@ package capital.yuri.yuriplayer.components.art import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape @@ -19,8 +20,8 @@ import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage /** - * Shared cover. Coil 3 owns memory + disk cache (configured per platform - * against [capital.yuri.yuriplayer.core.platform.AppDirectories.cacheDir]). + * Shared cover. The frame is always 1:1. Non-square art keeps its ratio and + * is centered in that square (contain), sitting on [surfaceVariant]. */ @Composable fun CoverArt( @@ -28,12 +29,17 @@ fun CoverArt( modifier: Modifier = Modifier, size: Dp? = null, corner: Dp = 8.dp, - contentDescription: String? = null + contentDescription: String? = null, + contentScale: ContentScale = ContentScale.Fit ) { val shape = RoundedCornerShape(corner) - val sized = if (size != null) modifier.size(size) else modifier + val frame = if (size != null) { + modifier.size(size) + } else { + modifier.aspectRatio(1f) + } Box( - modifier = sized + modifier = frame .clip(shape) .background(MaterialTheme.colorScheme.surfaceVariant), contentAlignment = Alignment.Center @@ -49,7 +55,8 @@ fun CoverArt( model = model, contentDescription = contentDescription, modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop + contentScale = contentScale, + alignment = Alignment.Center ) } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt index 7f11da79..dc0358f0 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -56,6 +57,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -353,30 +355,38 @@ private fun LibraryRail( fontWeight = FontWeight.Bold ) } - Row( - Modifier.padding(top = 12.dp, bottom = 8.dp), + LazyRow( + modifier = Modifier.fillMaxWidth().padding(top = 12.dp, bottom = 8.dp), horizontalArrangement = Arrangement.spacedBy(6.dp) ) { - FilterChip( - selected = filter == LibraryFilter.Recents, - onClick = { onFilter(LibraryFilter.Recents) }, - label = { Text("Recents") } - ) - FilterChip( - selected = filter == LibraryFilter.Playlists, - onClick = { onFilter(LibraryFilter.Playlists) }, - label = { Text("Playlists") } - ) - FilterChip( - selected = filter == LibraryFilter.Albums, - onClick = { onFilter(LibraryFilter.Albums) }, - label = { Text("Albums") } - ) - FilterChip( - selected = filter == LibraryFilter.Artists, - onClick = { onFilter(LibraryFilter.Artists) }, - label = { Text("Artists") } - ) + item { + FilterChip( + selected = filter == LibraryFilter.Recents, + onClick = { onFilter(LibraryFilter.Recents) }, + label = { Text("Recents", maxLines = 1) } + ) + } + item { + FilterChip( + selected = filter == LibraryFilter.Playlists, + onClick = { onFilter(LibraryFilter.Playlists) }, + label = { Text("Playlists", maxLines = 1) } + ) + } + item { + FilterChip( + selected = filter == LibraryFilter.Albums, + onClick = { onFilter(LibraryFilter.Albums) }, + label = { Text("Albums", maxLines = 1) } + ) + } + item { + FilterChip( + selected = filter == LibraryFilter.Artists, + onClick = { onFilter(LibraryFilter.Artists) }, + label = { Text("Artists", maxLines = 1) } + ) + } } LazyColumn(Modifier.weight(1f)) { items(items, key = { it.id }) { item -> @@ -396,7 +406,8 @@ private fun LibraryRail( CoverArt( model = item.artworkUri, size = 48.dp, - corner = if (item.circular) 24.dp else 8.dp + corner = if (item.circular) 24.dp else 8.dp, + contentScale = if (item.circular) ContentScale.Crop else ContentScale.Fit ) Spacer(Modifier.width(12.dp)) Column(Modifier.weight(1f)) { diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index fcaec61f..09bac2fe 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -80,7 +81,7 @@ fun NowPlayingSidebar( modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp) - .height(220.dp), + .aspectRatio(1f), corner = 16.dp ) QueuePanel( From 045c5af6967b1249eed6c624bd9ef5d38c7a20a9 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:14:25 +0000 Subject: [PATCH 21/69] Desktop scan popover, scrolling queue chips, muted settings Queue / Recently played chips scroll instead of wrapping. A TravelExplore control next to settings shows scan progress and per-source pause/stop/resume, matching mobile. Settings is a quiet icon instead of a filled accent button. --- .../components/layout/DesktopBentoLayout.kt | 21 +- .../components/player/QueuePanel.kt | 33 ++- .../yuri/yuriplayer/desktop/DesktopSession.kt | 274 ++++++++++++++---- .../yuriplayer/desktop/ui/DesktopScanMenu.kt | 172 +++++++++++ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 1 + 5 files changed, 419 insertions(+), 82 deletions(-) create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopScanMenu.kt diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt index dc0358f0..09ccdf8e 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt @@ -88,6 +88,7 @@ fun DesktopBentoLayout( canBack: Boolean, canForward: Boolean, onSettings: () -> Unit, + scanMenu: @Composable () -> Unit = {}, libraryFilter: LibraryFilter, onLibraryFilter: (LibraryFilter) -> Unit, libraryItems: List, @@ -114,7 +115,8 @@ fun DesktopBentoLayout( onForward = onForward, canBack = canBack, canForward = canForward, - onSettings = onSettings + onSettings = onSettings, + scanMenu = scanMenu ) BoxWithConstraints( Modifier @@ -275,7 +277,8 @@ private fun TopChrome( onForward: () -> Unit, canBack: Boolean, canForward: Boolean, - onSettings: () -> Unit + onSettings: () -> Unit, + scanMenu: @Composable () -> Unit = {} ) { Row( Modifier @@ -314,15 +317,13 @@ private fun TopChrome( ) ) Spacer(Modifier.weight(1f)) - FilledIconButton( - onClick = onSettings, - shape = CircleShape, - colors = IconButtonDefaults.filledIconButtonColors( - containerColor = MaterialTheme.colorScheme.primary, - contentColor = MaterialTheme.colorScheme.onPrimary + scanMenu() + IconButton(onClick = onSettings) { + Icon( + Icons.Default.Settings, + contentDescription = "Settings", + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) ) - ) { - Icon(Icons.Default.Settings, contentDescription = "Settings") } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt index 2b981139..6dc950fa 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.FilterChip import androidx.compose.material3.MaterialTheme @@ -56,20 +57,28 @@ fun QueuePanel( Modifier .fillMaxWidth() .padding(horizontal = 8.dp, vertical = 4.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically ) { - FilterChip( - selected = tab == QueueTab.Queue, - onClick = { tab = QueueTab.Queue }, - label = { Text("Queue") } - ) - FilterChip( - selected = tab == QueueTab.History, - onClick = { tab = QueueTab.History }, - label = { Text("Recently played") } - ) - Spacer(Modifier.weight(1f)) + LazyRow( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + item { + FilterChip( + selected = tab == QueueTab.Queue, + onClick = { tab = QueueTab.Queue }, + label = { Text("Queue", maxLines = 1) } + ) + } + item { + FilterChip( + selected = tab == QueueTab.History, + onClick = { tab = QueueTab.History }, + label = { Text("Recently played", maxLines = 1) } + ) + } + } if (tab == QueueTab.Queue && upcoming.isNotEmpty()) { TextButton(onClick = onClearQueue) { Text("Clear") } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index cc78fb23..3497775c 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -15,6 +15,7 @@ import capital.yuri.yuriplayer.desktop.os.createOsMediaControls import capital.yuri.yuriplayer.desktop.player.VlcjPlaybackEngine import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -26,9 +27,23 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.swing.Swing +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import java.io.File import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean + +enum class DesktopScanStatus { IDLE, RUNNING, PAUSED, STOPPED, DONE, ERROR } + +data class DesktopScanSource( + val id: String, + val name: String, + val status: DesktopScanStatus, + val detail: String = "", + val count: Int = 0 +) class DesktopSession { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Swing) @@ -46,6 +61,12 @@ class DesktopSession { private val _scanMessage = MutableStateFlow("Scanning…") val scanMessage: StateFlow = _scanMessage.asStateFlow() + private val _isScanning = MutableStateFlow(false) + val isScanning: StateFlow = _isScanning.asStateFlow() + + private val _scanSources = MutableStateFlow>(emptyList()) + val scanSources: StateFlow> = _scanSources.asStateFlow() + private val _positionMs = MutableStateFlow(0L) val positionMs: StateFlow = _positionMs.asStateFlow() @@ -65,6 +86,12 @@ class DesktopSession { val subsonic = SubsonicCatalog(http) private var ticker: Job? = null + private var scanJob: Job? = null + private val scanGate = Mutex() + private val stopAll = AtomicBoolean(false) + private val pauseAll = AtomicBoolean(false) + private val pausedIds = ConcurrentHashMap.newKeySet() + private val stoppedIds = ConcurrentHashMap.newKeySet() init { engine.addListener(object : capital.yuri.yuriplayer.core.player.PlaybackEngine.Listener { @@ -235,41 +262,12 @@ class DesktopSession { } fun indexFolder(path: String) { - val root = File(path) - if (!root.isDirectory) return - scope.launch(Dispatchers.IO) { - _scanMessage.value = "Indexing ${root.name}…" - val added = LocalLibraryScanner.scan(listOf(root)) - mergeTracks(added) - _scanMessage.value = "${root.name}: ${added.size} tracks · ${_tracks.value.size} total" - } + requestScan(force = false, sourceId = LOCAL_SCAN_ID) } fun indexRemote(account: RemoteAccount) { if (!account.enabled || account.kind == SourceKind.LOCAL) return - scope.launch(Dispatchers.IO) { - _scanMessage.value = "Indexing ${account.name}…" - val result = when (account.kind) { - SourceKind.JELLYFIN -> { - val authed = if (account.accessToken.isNullOrBlank()) { - jellyfin.authenticate(account).getOrElse { - _scanMessage.value = "${account.name}: ${it.message}" - return@launch - }.also { sources.upsertRemote(it) } - } else account - jellyfin.listTracks(authed) - } - SourceKind.SUBSONIC -> subsonic.listTracks(account) - SourceKind.LOCAL -> return@launch - } - result - .onSuccess { incoming -> - mergeTracks(incoming) - _scanMessage.value = - "${account.name}: ${incoming.size} tracks · ${_tracks.value.size} total" - } - .onFailure { _scanMessage.value = "${account.name}: ${it.message}" } - } + requestScan(force = true, sourceId = account.id) } private fun mergeTracks(incoming: List) { @@ -277,43 +275,195 @@ class DesktopSession { _tracks.value = (_tracks.value + incoming).distinctBy { it.id } } - fun rescan() { - scope.launch(Dispatchers.IO) { - val defaultRoots = LocalLibraryScanner.defaultRoots() - val extra = sources.extraFolders.value.map { File(it) }.filter { it.isDirectory } - val roots = (defaultRoots + extra).distinctBy { it.absolutePath } - _scanMessage.value = if (roots.isEmpty()) { - "No local folders yet" + fun rescan() = requestScan(force = true) + + fun requestScan(force: Boolean = false, sourceId: String? = null) { + if (force && sourceId == null) { + pausedIds.clear() + stoppedIds.clear() + scanJob?.cancel() + } + stopAll.set(false) + if (sourceId == null) pauseAll.set(false) + if (sourceId != null) { + pausedIds.remove(sourceId) + stoppedIds.remove(sourceId) + } + scanJob = scope.launch(Dispatchers.IO) { + scanGate.withLock { + runScan(force, sourceId) + } + } + } + + fun pauseScan(sourceId: String? = null) { + if (sourceId == null) { + pauseAll.set(true) + scanJob?.cancel() + markRunning(DesktopScanStatus.PAUSED, "Paused") + _isScanning.value = false + _scanMessage.value = "Paused" + } else { + pausedIds.add(sourceId) + patchScan(sourceId, DesktopScanStatus.PAUSED, "Paused") + } + } + + fun stopScan(sourceId: String? = null) { + if (sourceId == null) { + stopAll.set(true) + scanJob?.cancel() + markRunning(DesktopScanStatus.STOPPED, "Stopped") + _isScanning.value = false + _scanMessage.value = "Stopped" + } else { + stoppedIds.add(sourceId) + patchScan(sourceId, DesktopScanStatus.STOPPED, "Stopped") + } + } + + private suspend fun runScan(force: Boolean, sourceId: String?) { + _isScanning.value = true + refreshScanSources() + try { + val doLocal = sourceId == null || sourceId == LOCAL_SCAN_ID + val remotes = sources.remotes.value.filter { it.enabled } + .filter { sourceId == null || it.id == sourceId } + + val localTracks = if (doLocal && !stopAll.get() && !pauseAll.get()) { + scanLocalLibrary() } else { - "Scanning ${roots.joinToString { it.name }}…" + emptyList() } - val local = if (roots.isEmpty()) emptyList() else LocalLibraryScanner.scan(roots) + if (stopAll.get() || pauseAll.get()) return + val remoteTracks = mutableListOf() - for (remote in sources.remotes.value.filter { it.enabled }) { - _scanMessage.value = "Indexing ${remote.name}…" - val result = when (remote.kind) { - SourceKind.JELLYFIN -> { - val authed = if (remote.accessToken.isNullOrBlank()) { - jellyfin.authenticate(remote).getOrElse { - _scanMessage.value = "${remote.name}: ${it.message}" - continue - }.also { sources.upsertRemote(it) } - } else remote - jellyfin.listTracks(authed) - } - SourceKind.SUBSONIC -> subsonic.listTracks(remote) - SourceKind.LOCAL -> continue + for (remote in remotes) { + if (stopAll.get()) { + patchScan(remote.id, DesktopScanStatus.STOPPED, "Stopped") + continue } + if (pauseAll.get() || remote.id in pausedIds) { + patchScan(remote.id, DesktopScanStatus.PAUSED, "Paused") + continue + } + if (remote.id in stoppedIds) { + patchScan(remote.id, DesktopScanStatus.STOPPED, "Stopped") + continue + } + patchScan(remote.id, DesktopScanStatus.RUNNING, "Indexing…") + _scanMessage.value = "Indexing ${remote.name}…" + val result = fetchRemote(remote) result - .onSuccess { remoteTracks += it } - .onFailure { _scanMessage.value = "${remote.name}: ${it.message}" } + .onSuccess { incoming -> + remoteTracks += incoming + patchScan( + remote.id, + DesktopScanStatus.DONE, + "${incoming.size} tracks", + incoming.size + ) + } + .onFailure { + patchScan( + remote.id, + DesktopScanStatus.ERROR, + it.message ?: "Failed" + ) + _scanMessage.value = "${remote.name}: ${it.message}" + } + } + + if (sourceId == null) { + _tracks.value = (localTracks + remoteTracks).distinctBy { it.id } + } else if (sourceId == LOCAL_SCAN_ID) { + mergeTracks(localTracks) + } else { + mergeTracks(remoteTracks) } - val merged = (local + remoteTracks).distinctBy { it.id } - _tracks.value = merged _scanMessage.value = when { - merged.isEmpty() -> "No tracks yet — add a folder or a server in Settings → Library" - else -> "${merged.size} tracks" + _tracks.value.isEmpty() -> + "No tracks yet — add a folder or a server in Settings → Library" + else -> "${_tracks.value.size} tracks" + } + } catch (e: CancellationException) { + if (!pauseAll.get() && !stopAll.get()) { + markRunning(DesktopScanStatus.STOPPED, "Stopped") + _scanMessage.value = "Stopped" + } + throw e + } finally { + _isScanning.value = false + } + } + + private fun scanLocalLibrary(): List { + val defaultRoots = LocalLibraryScanner.defaultRoots() + val extra = sources.extraFolders.value.map { File(it) }.filter { it.isDirectory } + val roots = (defaultRoots + extra).distinctBy { it.absolutePath } + patchScan( + LOCAL_SCAN_ID, + DesktopScanStatus.RUNNING, + if (roots.isEmpty()) "No folders" else "Scanning ${roots.joinToString { it.name }}…" + ) + _scanMessage.value = if (roots.isEmpty()) { + "No local folders yet" + } else { + "Scanning ${roots.joinToString { it.name }}…" + } + val local = if (roots.isEmpty()) emptyList() else LocalLibraryScanner.scan(roots) + patchScan( + LOCAL_SCAN_ID, + DesktopScanStatus.DONE, + "${local.size} tracks", + local.size + ) + return local + } + + private suspend fun fetchRemote(remote: RemoteAccount): Result> = + when (remote.kind) { + SourceKind.JELLYFIN -> { + val authed = if (remote.accessToken.isNullOrBlank()) { + jellyfin.authenticate(remote).getOrElse { + return Result.failure(it) + }.also { sources.upsertRemote(it) } + } else remote + jellyfin.listTracks(authed) } + SourceKind.SUBSONIC -> subsonic.listTracks(remote) + SourceKind.LOCAL -> Result.success(emptyList()) + } + + private fun refreshScanSources() { + val local = _scanSources.value.firstOrNull { it.id == LOCAL_SCAN_ID } + ?: DesktopScanSource(LOCAL_SCAN_ID, "Local library", DesktopScanStatus.IDLE) + val remotes = sources.remotes.value.filter { it.enabled }.map { remote -> + _scanSources.value.firstOrNull { it.id == remote.id } + ?: DesktopScanSource(remote.id, remote.name, DesktopScanStatus.IDLE) + } + _scanSources.value = listOf(local.copy(name = "Local library")) + remotes.map { src -> + val name = sources.remotes.value.firstOrNull { it.id == src.id }?.name ?: src.name + src.copy(name = name) + } + } + + private fun patchScan( + id: String, + status: DesktopScanStatus, + detail: String, + count: Int? = null + ) { + _scanSources.value = _scanSources.value.map { + if (it.id != id) it + else it.copy(status = status, detail = detail, count = count ?: it.count) + } + } + + private fun markRunning(status: DesktopScanStatus, detail: String) { + _scanSources.value = _scanSources.value.map { + if (it.status == DesktopScanStatus.RUNNING) it.copy(status = status, detail = detail) + else it } } @@ -326,4 +476,8 @@ class DesktopSession { runCatching { http.close() } scope.coroutineContext[Job]?.cancel() } + + companion object { + const val LOCAL_SCAN_ID = "local" + } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopScanMenu.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopScanMenu.kt new file mode 100644 index 00000000..8d0e3828 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopScanMenu.kt @@ -0,0 +1,172 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.TravelExplore +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.desktop.DesktopScanStatus +import capital.yuri.yuriplayer.desktop.DesktopSession + +@Composable +fun DesktopScanMenu(session: DesktopSession) { + val scanning by session.isScanning.collectAsState() + val message by session.scanMessage.collectAsState() + val sources by session.scanSources.collectAsState() + var expanded by remember { mutableStateOf(false) } + val muted = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + + Box { + IconButton(onClick = { expanded = true }) { + if (scanning) { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + strokeWidth = 2.dp, + color = muted + ) + } else { + Icon( + Icons.Default.TravelExplore, + contentDescription = "Library scan", + tint = muted + ) + } + } + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { + Text( + message, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.primary + ) + }, + onClick = { }, + enabled = false + ) + HorizontalDivider() + DropdownMenuItem( + text = { Text(if (scanning) "Resume / continue scan" else "Scan libraries") }, + onClick = { + expanded = false + session.requestScan(force = false) + } + ) + DropdownMenuItem( + text = { Text("Force re-scan all") }, + onClick = { + expanded = false + session.requestScan(force = true) + } + ) + DropdownMenuItem( + text = { Text("Refresh local library only") }, + onClick = { + expanded = false + session.requestScan(force = true, sourceId = DesktopSession.LOCAL_SCAN_ID) + } + ) + if (scanning || sources.any { + it.status == DesktopScanStatus.RUNNING || + it.status == DesktopScanStatus.PAUSED + } + ) { + HorizontalDivider() + DropdownMenuItem( + text = { Text("Pause all") }, + onClick = { + expanded = false + session.pauseScan(null) + } + ) + DropdownMenuItem( + text = { Text("Stop all") }, + onClick = { + expanded = false + session.stopScan(null) + } + ) + } + if (sources.isNotEmpty()) { + HorizontalDivider() + sources.forEach { row -> + val statusLabel = when (row.status) { + DesktopScanStatus.RUNNING -> "running" + DesktopScanStatus.PAUSED -> "paused" + DesktopScanStatus.STOPPED -> "stopped" + DesktopScanStatus.DONE -> + if (row.count > 0) "done (${row.count})" else "done" + DesktopScanStatus.ERROR -> row.detail.ifBlank { "error" } + DesktopScanStatus.IDLE -> "idle" + } + DropdownMenuItem( + text = { Text("${row.name} · $statusLabel") }, + onClick = { }, + enabled = false + ) + when (row.status) { + DesktopScanStatus.RUNNING -> { + DropdownMenuItem( + text = { Text(" Pause ${row.name}") }, + onClick = { + expanded = false + session.pauseScan(row.id) + } + ) + DropdownMenuItem( + text = { Text(" Stop ${row.name}") }, + onClick = { + expanded = false + session.stopScan(row.id) + } + ) + } + DesktopScanStatus.PAUSED, DesktopScanStatus.STOPPED -> { + DropdownMenuItem( + text = { Text(" Resume ${row.name} only") }, + onClick = { + expanded = false + session.requestScan(force = false, sourceId = row.id) + } + ) + } + else -> { + DropdownMenuItem( + text = { Text(" Scan ${row.name} only") }, + onClick = { + expanded = false + session.requestScan(force = false, sourceId = row.id) + } + ) + DropdownMenuItem( + text = { Text(" Force re-scan ${row.name}") }, + onClick = { + expanded = false + session.requestScan(force = true, sourceId = row.id) + } + ) + } + } + } + } + } + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 80f40e18..462959c4 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -220,6 +220,7 @@ fun YuriDesktopApp(session: DesktopSession) { canBack = stack.size > 1, canForward = forward.isNotEmpty(), onSettings = { showSettings = true }, + scanMenu = { DesktopScanMenu(session) }, libraryFilter = libraryFilter, onLibraryFilter = { libraryFilter = it }, libraryItems = libraryItems, From 0c5818c0bd753ad47986b2a0eba94aa74efc8352 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:18:06 +0000 Subject: [PATCH 22/69] Make desktop partial/full/pause/stop scans actually work Partial scan skips already-indexed libraries and only pulls new Jellyfin/Subsonic tracks. Full scan walks everything. Pause and stop cancel the in-flight source at a page boundary and can target all libraries or a single one. --- .../yuriplayer/core/source/JellyfinCatalog.kt | 30 +- .../yuriplayer/core/source/SubsonicCatalog.kt | 27 +- .../yuri/yuriplayer/desktop/DesktopSession.kt | 266 ++++++++++++------ .../yuriplayer/desktop/ui/DesktopScanMenu.kt | 109 +++---- 4 files changed, 274 insertions(+), 158 deletions(-) diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt index 8d319271..af0b1a2e 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt @@ -12,6 +12,7 @@ import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType import io.ktor.http.content.TextContent import io.ktor.http.isSuccess +import kotlinx.coroutines.ensureActive import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -50,7 +51,30 @@ class JellyfinCatalog( ) } - suspend fun listTracks(account: RemoteAccount, maxItems: Int = 50_000): Result> = + suspend fun audioCount(account: RemoteAccount): Result = runCatching { + val token = account.accessToken ?: error("Not signed in") + val userId = account.userId ?: error("Not signed in") + val root = normalizeBaseUrl(account.baseUrl) + val requestUrl = url(root) { + path("Users", userId, "Items") + param("IncludeItemTypes", "Audio") + param("Recursive", "true") + param("Limit", 0) + } + val response = http.get(requestUrl) { + header("X-Emby-Authorization", authHeader(token)) + header("X-Emby-Token", token) + } + if (!response.status.isSuccess()) error("Items HTTP ${response.status.value}") + json.decodeFromString(response.bodyAsText()).total ?: 0 + } + + suspend fun listTracks( + account: RemoteAccount, + maxItems: Int = 50_000, + sortBy: String = "Album,SortName", + sortOrder: String = "Ascending" + ): Result> = runCatching { val token = account.accessToken ?: error("Not signed in") val userId = account.userId ?: error("Not signed in") @@ -59,6 +83,7 @@ class JellyfinCatalog( var start = 0 val pageSize = 400 while (out.size < maxItems) { + kotlinx.coroutines.currentCoroutineContext().ensureActive() val take = minOf(pageSize, maxItems - out.size) val requestUrl = url(root) { path("Users", userId, "Items") @@ -69,7 +94,8 @@ class JellyfinCatalog( param("ImageTypeLimit", "1") param("StartIndex", start) param("Limit", take) - param("SortBy", "Album,SortName") + param("SortBy", sortBy) + param("SortOrder", sortOrder) } val response = http.get(requestUrl) { header("X-Emby-Authorization", authHeader(token)) diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt index 573d88bf..0d54b68f 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt @@ -8,6 +8,7 @@ import io.ktor.client.HttpClient import io.ktor.client.request.get import io.ktor.client.statement.bodyAsText import io.ktor.http.isSuccess +import kotlinx.coroutines.ensureActive import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -45,9 +46,11 @@ class SubsonicCatalog( var offset = 0 val pageSize = 100 while (out.size < maxSongs) { - val page = listAlbums(session, offset, pageSize) + kotlinx.coroutines.currentCoroutineContext().ensureActive() + val page = listAlbums(session, offset, pageSize, type = "alphabeticalByName") if (page.isEmpty()) break for (album in page) { + kotlinx.coroutines.currentCoroutineContext().ensureActive() val id = album.id ?: continue out += songsForAlbum(session, id, album) if (out.size >= maxSongs) break @@ -58,9 +61,27 @@ class SubsonicCatalog( out } - private suspend fun listAlbums(session: Session, offset: Int, size: Int): List { + suspend fun listNewestTracks(account: RemoteAccount, maxAlbums: Int = 40): Result> = + runCatching { + val session = sessionOf(account) + val albums = listAlbums(session, offset = 0, size = maxAlbums, type = "newest") + val out = ArrayList(albums.size * 12) + for (album in albums) { + kotlinx.coroutines.currentCoroutineContext().ensureActive() + val id = album.id ?: continue + out += songsForAlbum(session, id, album) + } + out + } + + private suspend fun listAlbums( + session: Session, + offset: Int, + size: Int, + type: String = "alphabeticalByName" + ): List { val body = apiGet(session, "getAlbumList2") { - param("type", "alphabeticalByName") + param("type", type) param("size", size) param("offset", offset) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 3497775c..cc9c96c3 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -15,6 +15,8 @@ import capital.yuri.yuriplayer.desktop.os.createOsMediaControls import capital.yuri.yuriplayer.desktop.player.VlcjPlaybackEngine import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -34,6 +36,7 @@ import java.io.File import java.util.UUID import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference enum class DesktopScanStatus { IDLE, RUNNING, PAUSED, STOPPED, DONE, ERROR } @@ -87,11 +90,13 @@ class DesktopSession { private var ticker: Job? = null private var scanJob: Job? = null + private var sourceJob: Job? = null private val scanGate = Mutex() private val stopAll = AtomicBoolean(false) private val pauseAll = AtomicBoolean(false) private val pausedIds = ConcurrentHashMap.newKeySet() private val stoppedIds = ConcurrentHashMap.newKeySet() + private val currentSourceId = AtomicReference(null) init { engine.addListener(object : capital.yuri.yuriplayer.core.player.PlaybackEngine.Listener { @@ -129,6 +134,9 @@ class DesktopSession { _coverPixels.value = CoverPixels.argb(track?.artworkUri, track?.path) } } + scope.launch { + sources.remotes.collect { refreshScanSources() } + } rescan() } @@ -262,7 +270,7 @@ class DesktopSession { } fun indexFolder(path: String) { - requestScan(force = false, sourceId = LOCAL_SCAN_ID) + requestScan(force = true, sourceId = LOCAL_SCAN_ID) } fun indexRemote(account: RemoteAccount) { @@ -281,17 +289,21 @@ class DesktopSession { if (force && sourceId == null) { pausedIds.clear() stoppedIds.clear() + sourceJob?.cancel() scanJob?.cancel() } stopAll.set(false) - if (sourceId == null) pauseAll.set(false) + pauseAll.set(false) if (sourceId != null) { pausedIds.remove(sourceId) stoppedIds.remove(sourceId) + } else if (force) { + pausedIds.clear() + stoppedIds.clear() } scanJob = scope.launch(Dispatchers.IO) { scanGate.withLock { - runScan(force, sourceId) + runScan(force = force, onlySourceId = sourceId) } } } @@ -299,153 +311,227 @@ class DesktopSession { fun pauseScan(sourceId: String? = null) { if (sourceId == null) { pauseAll.set(true) - scanJob?.cancel() + sourceJob?.cancel() markRunning(DesktopScanStatus.PAUSED, "Paused") - _isScanning.value = false _scanMessage.value = "Paused" } else { pausedIds.add(sourceId) patchScan(sourceId, DesktopScanStatus.PAUSED, "Paused") + if (currentSourceId.get() == sourceId) sourceJob?.cancel() } } fun stopScan(sourceId: String? = null) { if (sourceId == null) { stopAll.set(true) - scanJob?.cancel() + sourceJob?.cancel() markRunning(DesktopScanStatus.STOPPED, "Stopped") _isScanning.value = false _scanMessage.value = "Stopped" } else { stoppedIds.add(sourceId) patchScan(sourceId, DesktopScanStatus.STOPPED, "Stopped") + if (currentSourceId.get() == sourceId) sourceJob?.cancel() } } - private suspend fun runScan(force: Boolean, sourceId: String?) { + private suspend fun runScan(force: Boolean, onlySourceId: String?) { _isScanning.value = true refreshScanSources() try { - val doLocal = sourceId == null || sourceId == LOCAL_SCAN_ID - val remotes = sources.remotes.value.filter { it.enabled } - .filter { sourceId == null || it.id == sourceId } - - val localTracks = if (doLocal && !stopAll.get() && !pauseAll.get()) { - scanLocalLibrary() - } else { - emptyList() - } - if (stopAll.get() || pauseAll.get()) return + coroutineScope { + val doLocal = onlySourceId == null || onlySourceId == LOCAL_SCAN_ID + val remotes = sources.remotes.value.filter { it.enabled } + .filter { onlySourceId == null || it.id == onlySourceId } + + val localTracks = if (doLocal) { + scanOneSource(LOCAL_SCAN_ID, "Local library", force, onlySourceId) { + scanLocalLibrary() + } + } else { + emptyList() + } + if (halted()) return@coroutineScope - val remoteTracks = mutableListOf() - for (remote in remotes) { - if (stopAll.get()) { - patchScan(remote.id, DesktopScanStatus.STOPPED, "Stopped") - continue + val remoteTracks = mutableListOf() + for (remote in remotes) { + if (halted()) break + val got = scanOneSource(remote.id, remote.name, force, onlySourceId) { + fetchRemote(remote, partial = !force) + } + remoteTracks += got } - if (pauseAll.get() || remote.id in pausedIds) { - patchScan(remote.id, DesktopScanStatus.PAUSED, "Paused") - continue + + if (stopAll.get() || pauseAll.get()) return@coroutineScope + + if (onlySourceId == null && force) { + _tracks.value = (localTracks + remoteTracks).distinctBy { it.id } + } else { + mergeTracks(localTracks + remoteTracks) } - if (remote.id in stoppedIds) { - patchScan(remote.id, DesktopScanStatus.STOPPED, "Stopped") - continue + _scanMessage.value = when { + _tracks.value.isEmpty() -> + "No tracks yet — add a folder or a server in Settings → Library" + else -> "${_tracks.value.size} tracks" } - patchScan(remote.id, DesktopScanStatus.RUNNING, "Indexing…") - _scanMessage.value = "Indexing ${remote.name}…" - val result = fetchRemote(remote) - result - .onSuccess { incoming -> - remoteTracks += incoming - patchScan( - remote.id, - DesktopScanStatus.DONE, - "${incoming.size} tracks", - incoming.size - ) - } - .onFailure { - patchScan( - remote.id, - DesktopScanStatus.ERROR, - it.message ?: "Failed" - ) - _scanMessage.value = "${remote.name}: ${it.message}" - } - } - - if (sourceId == null) { - _tracks.value = (localTracks + remoteTracks).distinctBy { it.id } - } else if (sourceId == LOCAL_SCAN_ID) { - mergeTracks(localTracks) - } else { - mergeTracks(remoteTracks) - } - _scanMessage.value = when { - _tracks.value.isEmpty() -> - "No tracks yet — add a folder or a server in Settings → Library" - else -> "${_tracks.value.size} tracks" } } catch (e: CancellationException) { - if (!pauseAll.get() && !stopAll.get()) { + if (pauseAll.get()) { + markRunning(DesktopScanStatus.PAUSED, "Paused") + _scanMessage.value = "Paused" + } else { markRunning(DesktopScanStatus.STOPPED, "Stopped") _scanMessage.value = "Stopped" } throw e } finally { + currentSourceId.set(null) + sourceJob = null + if (!pauseAll.get()) { + // keep isScanning true only while a source child is in flight + } _isScanning.value = false } } + private fun halted(): Boolean = stopAll.get() || pauseAll.get() + + private fun shouldScan(id: String, force: Boolean, onlySourceId: String?): Boolean { + if (id in stoppedIds && onlySourceId != id) return false + if (id in pausedIds && onlySourceId != id && !force) return false + val src = _scanSources.value.firstOrNull { it.id == id } + if (force || onlySourceId == id) return true + // Partial all: skip already-indexed sources. + if (src?.status == DesktopScanStatus.DONE && (src.count > 0)) return false + return true + } + + private suspend fun scanOneSource( + id: String, + name: String, + force: Boolean, + onlySourceId: String?, + block: suspend () -> List + ): List { + if (!shouldScan(id, force, onlySourceId)) { + val src = _scanSources.value.firstOrNull { it.id == id } + if (src?.status == DesktopScanStatus.DONE) { + _scanMessage.value = "$name: indexed (${src.count}) — skip" + } + return emptyList() + } + if (id in stoppedIds) { + patchScan(id, DesktopScanStatus.STOPPED, "Stopped") + return emptyList() + } + if (id in pausedIds || pauseAll.get()) { + patchScan(id, DesktopScanStatus.PAUSED, "Paused") + return emptyList() + } + if (stopAll.get()) { + patchScan(id, DesktopScanStatus.STOPPED, "Stopped") + return emptyList() + } + currentSourceId.set(id) + patchScan(id, DesktopScanStatus.RUNNING, if (force) "Full scan…" else "Checking…") + _scanMessage.value = if (force) "Full scan · $name" else "Partial scan · $name" + return coroutineScope { + val child = async { block() } + sourceJob = child + try { + val incoming = child.await() + val prev = _scanSources.value.firstOrNull { it.id == id }?.count ?: 0 + val nextCount = if (force) incoming.size else maxOf(prev, incoming.size) + patchScan( + id, + DesktopScanStatus.DONE, + when { + incoming.isEmpty() && !force -> "up to date" + incoming.isEmpty() -> "0 tracks" + else -> "${incoming.size} tracks" + }, + nextCount + ) + incoming + } catch (e: CancellationException) { + when { + stopAll.get() || id in stoppedIds -> + patchScan(id, DesktopScanStatus.STOPPED, "Stopped") + pauseAll.get() || id in pausedIds -> + patchScan(id, DesktopScanStatus.PAUSED, "Paused") + else -> patchScan(id, DesktopScanStatus.STOPPED, "Stopped") + } + if (halted()) throw e + emptyList() + } catch (e: Exception) { + patchScan(id, DesktopScanStatus.ERROR, e.message ?: "Failed") + _scanMessage.value = "$name: ${e.message}" + emptyList() + } finally { + if (currentSourceId.get() == id) currentSourceId.set(null) + if (sourceJob === child) sourceJob = null + } + } + } + private fun scanLocalLibrary(): List { val defaultRoots = LocalLibraryScanner.defaultRoots() val extra = sources.extraFolders.value.map { File(it) }.filter { it.isDirectory } val roots = (defaultRoots + extra).distinctBy { it.absolutePath } - patchScan( - LOCAL_SCAN_ID, - DesktopScanStatus.RUNNING, - if (roots.isEmpty()) "No folders" else "Scanning ${roots.joinToString { it.name }}…" - ) _scanMessage.value = if (roots.isEmpty()) { "No local folders yet" } else { "Scanning ${roots.joinToString { it.name }}…" } - val local = if (roots.isEmpty()) emptyList() else LocalLibraryScanner.scan(roots) - patchScan( - LOCAL_SCAN_ID, - DesktopScanStatus.DONE, - "${local.size} tracks", - local.size - ) - return local + return if (roots.isEmpty()) emptyList() else LocalLibraryScanner.scan(roots) } - private suspend fun fetchRemote(remote: RemoteAccount): Result> = - when (remote.kind) { + private suspend fun fetchRemote(remote: RemoteAccount, partial: Boolean): List { + val known = _scanSources.value.firstOrNull { it.id == remote.id }?.count ?: 0 + val result = when (remote.kind) { SourceKind.JELLYFIN -> { val authed = if (remote.accessToken.isNullOrBlank()) { - jellyfin.authenticate(remote).getOrElse { - return Result.failure(it) - }.also { sources.upsertRemote(it) } + jellyfin.authenticate(remote).getOrElse { throw it } + .also { sources.upsertRemote(it) } } else remote - jellyfin.listTracks(authed) + if (partial && known > 0) { + val total = jellyfin.audioCount(authed).getOrElse { -1 } + if (total in 0..known) return emptyList() + val cap = if (total > known) (total - known + 64).coerceIn(1, 2_000) else 256 + jellyfin.listTracks( + authed, + maxItems = cap, + sortBy = "DateCreated", + sortOrder = "Descending" + ) + } else { + jellyfin.listTracks(authed) + } + } + SourceKind.SUBSONIC -> { + if (partial && known > 0) subsonic.listNewestTracks(remote) + else subsonic.listTracks(remote) } - SourceKind.SUBSONIC -> subsonic.listTracks(remote) SourceKind.LOCAL -> Result.success(emptyList()) } + return result.getOrElse { throw it } + } private fun refreshScanSources() { - val local = _scanSources.value.firstOrNull { it.id == LOCAL_SCAN_ID } + val existing = _scanSources.value.associateBy { it.id } + val local = existing[LOCAL_SCAN_ID] ?: DesktopScanSource(LOCAL_SCAN_ID, "Local library", DesktopScanStatus.IDLE) val remotes = sources.remotes.value.filter { it.enabled }.map { remote -> - _scanSources.value.firstOrNull { it.id == remote.id } - ?: DesktopScanSource(remote.id, remote.name, DesktopScanStatus.IDLE) - } - _scanSources.value = listOf(local.copy(name = "Local library")) + remotes.map { src -> - val name = sources.remotes.value.firstOrNull { it.id == src.id }?.name ?: src.name - src.copy(name = name) + val prev = existing[remote.id] + DesktopScanSource( + id = remote.id, + name = remote.name, + status = prev?.status ?: DesktopScanStatus.IDLE, + detail = prev?.detail.orEmpty(), + count = prev?.count ?: 0 + ) } + _scanSources.value = listOf(local.copy(name = "Local library")) + remotes } private fun patchScan( diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopScanMenu.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopScanMenu.kt index 8d0e3828..032f03e7 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopScanMenu.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopScanMenu.kt @@ -64,47 +64,42 @@ fun DesktopScanMenu(session: DesktopSession) { ) HorizontalDivider() DropdownMenuItem( - text = { Text(if (scanning) "Resume / continue scan" else "Scan libraries") }, + text = { Text("Partial scan all") }, onClick = { expanded = false session.requestScan(force = false) } ) DropdownMenuItem( - text = { Text("Force re-scan all") }, + text = { Text("Full scan all") }, onClick = { expanded = false session.requestScan(force = true) } ) DropdownMenuItem( - text = { Text("Refresh local library only") }, + text = { Text("Full scan local only") }, onClick = { expanded = false session.requestScan(force = true, sourceId = DesktopSession.LOCAL_SCAN_ID) } ) - if (scanning || sources.any { - it.status == DesktopScanStatus.RUNNING || - it.status == DesktopScanStatus.PAUSED + DropdownMenuItem( + text = { Text("Pause all") }, + enabled = scanning, + onClick = { + expanded = false + session.pauseScan(null) } - ) { - HorizontalDivider() - DropdownMenuItem( - text = { Text("Pause all") }, - onClick = { - expanded = false - session.pauseScan(null) - } - ) - DropdownMenuItem( - text = { Text("Stop all") }, - onClick = { - expanded = false - session.stopScan(null) - } - ) - } + ) + DropdownMenuItem( + text = { Text("Stop all") }, + enabled = scanning, + onClick = { + expanded = false + session.stopScan(null) + } + ) if (sources.isNotEmpty()) { HorizontalDivider() sources.forEach { row -> @@ -117,54 +112,42 @@ fun DesktopScanMenu(session: DesktopSession) { DesktopScanStatus.ERROR -> row.detail.ifBlank { "error" } DesktopScanStatus.IDLE -> "idle" } + val busy = row.status == DesktopScanStatus.RUNNING || scanning DropdownMenuItem( text = { Text("${row.name} · $statusLabel") }, onClick = { }, enabled = false ) - when (row.status) { - DesktopScanStatus.RUNNING -> { - DropdownMenuItem( - text = { Text(" Pause ${row.name}") }, - onClick = { - expanded = false - session.pauseScan(row.id) - } - ) - DropdownMenuItem( - text = { Text(" Stop ${row.name}") }, - onClick = { - expanded = false - session.stopScan(row.id) - } - ) + DropdownMenuItem( + text = { Text(" Partial scan") }, + onClick = { + expanded = false + session.requestScan(force = false, sourceId = row.id) + } + ) + DropdownMenuItem( + text = { Text(" Full scan") }, + onClick = { + expanded = false + session.requestScan(force = true, sourceId = row.id) } - DesktopScanStatus.PAUSED, DesktopScanStatus.STOPPED -> { - DropdownMenuItem( - text = { Text(" Resume ${row.name} only") }, - onClick = { - expanded = false - session.requestScan(force = false, sourceId = row.id) - } - ) + ) + DropdownMenuItem( + text = { Text(" Pause") }, + enabled = busy, + onClick = { + expanded = false + session.pauseScan(row.id) } - else -> { - DropdownMenuItem( - text = { Text(" Scan ${row.name} only") }, - onClick = { - expanded = false - session.requestScan(force = false, sourceId = row.id) - } - ) - DropdownMenuItem( - text = { Text(" Force re-scan ${row.name}") }, - onClick = { - expanded = false - session.requestScan(force = true, sourceId = row.id) - } - ) + ) + DropdownMenuItem( + text = { Text(" Stop") }, + enabled = busy, + onClick = { + expanded = false + session.stopScan(row.id) } - } + ) } } } From d96f195d4a200b6a74246b4f32e7087cf723ddf7 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:24:15 +0000 Subject: [PATCH 23/69] Fix search pill clipping and Spotify-style home pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Search uses a 40dp centered pill so the query is no longer clipped by OutlinedTextField padding. Home opens with a greeting and up to eight My Stuff shortcuts in a 2×4 grid (Liked Songs, pins, then recents), followed by Jump back in / playlists / albums / artists. --- .../components/layout/DesktopBentoLayout.kt | 64 ++++- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 255 ++++++++++++++---- 2 files changed, 258 insertions(+), 61 deletions(-) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt index 09ccdf8e..45b1baea 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -41,8 +42,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -55,6 +54,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.layout.ContentScale @@ -303,18 +303,10 @@ private fun TopChrome( Icon(Icons.Default.Home, contentDescription = "Home") } Spacer(Modifier.weight(1f)) - OutlinedTextField( - value = searchQuery, - onValueChange = onSearchQuery, - modifier = Modifier.widthIn(min = 280.dp, max = 520.dp).weight(1.2f).height(48.dp), - placeholder = { Text("What do you want to play?") }, - leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, - singleLine = true, - shape = CircleShape, - colors = OutlinedTextFieldDefaults.colors( - focusedContainerColor = MaterialTheme.colorScheme.surface, - unfocusedContainerColor = MaterialTheme.colorScheme.surface - ) + SearchPill( + query = searchQuery, + onQuery = onSearchQuery, + modifier = Modifier.widthIn(min = 280.dp, max = 520.dp).weight(1.2f) ) Spacer(Modifier.weight(1f)) scanMenu() @@ -328,6 +320,50 @@ private fun TopChrome( } } +@Composable +private fun SearchPill( + query: String, + onQuery: (String) -> Unit, + modifier: Modifier = Modifier +) { + val onSurface = MaterialTheme.colorScheme.onSurface + val muted = onSurface.copy(alpha = 0.5f) + Row( + modifier = modifier + .height(40.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surface) + .padding(horizontal = 14.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + Icons.Default.Search, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = muted + ) + Spacer(Modifier.width(10.dp)) + Box(Modifier.weight(1f), contentAlignment = Alignment.CenterStart) { + if (query.isEmpty()) { + Text( + "What do you want to play?", + style = MaterialTheme.typography.bodyMedium, + color = muted, + maxLines = 1 + ) + } + BasicTextField( + value = query, + onValueChange = onQuery, + singleLine = true, + textStyle = MaterialTheme.typography.bodyMedium.copy(color = onSurface), + cursorBrush = SolidColor(onSurface), + modifier = Modifier.fillMaxWidth() + ) + } + } +} + @Composable private fun LibraryRail( filter: LibraryFilter, diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 462959c4..4e1fd115 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -317,10 +317,16 @@ fun YuriDesktopApp(session: DesktopSession) { playlists = playlists, tracks = tracks, recents = history, + pins = pins, + liked = liked, status = engineMessage ?: status, onOpenAlbum = ::openAlbum, onOpenPlaylist = { push(Route.Playlist(it.id)) }, - onPlayRecent = { t -> session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) } + onOpenArtist = { name -> + query = name + push(Route.Search) + }, + onPlayTracks = { list, i -> session.player.play(list, i) } ) Route.Search -> LibraryGrid( albums = visibleAlbums, @@ -403,66 +409,80 @@ private fun HomeFeed( playlists: List, tracks: List, recents: List, + pins: List, + liked: Set, status: String, onOpenAlbum: (AlbumPageModel) -> Unit, onOpenPlaylist: (capital.yuri.yuriplayer.desktop.DesktopPlaylist) -> Unit, - onPlayRecent: (Track) -> Unit + onOpenArtist: (String) -> Unit, + onPlayTracks: (List, Int) -> Unit ) { val recentAlbums = remember(recents, albums) { recents.mapNotNull { t -> albums.firstOrNull { a -> a.tracks.any { it.id == t.id } } }.distinctBy { it.id } } + val likedTracks = remember(liked, tracks) { tracks.filter { it.id in liked } } + val shortcuts = remember(pins, liked, recents, albums, playlists, tracks) { + buildHomeShortcuts( + pins, liked, likedTracks, recents, albums, playlists, tracks, + onOpenAlbum, onOpenPlaylist, onOpenArtist, onPlayTracks + ) + } + val greeting = remember { + val h = java.time.LocalTime.now().hour + when { + h < 12 -> "Good morning" + h < 18 -> "Good afternoon" + else -> "Good evening" + } + } + val stuffAlbums = remember(pins, liked, albums, tracks) { + val pinnedAlbumIds = pins.filter { it.kind == DesktopCollection.Kind.ALBUM }.map { it.id }.toSet() + albums.filter { album -> + album.id in pinnedAlbumIds || + album.tracks.any { it.id in liked } + }.ifEmpty { albums } + } + val stuffArtists = remember(pins, albums) { + val pinned = pins.filter { it.kind == DesktopCollection.Kind.ARTIST }.map { it.title } + pinned.ifEmpty { albums.map { it.artist }.distinct() } + } + Column( Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) - .padding(20.dp) + .padding(horizontal = 24.dp, vertical = 16.dp) ) { + Text( + greeting, + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onBackground + ) + Spacer(Modifier.height(12.dp)) Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { FilterChip(selected = true, onClick = {}, label = { Text("All") }) FilterChip(selected = false, onClick = {}, label = { Text("Music") }) } - Spacer(Modifier.height(16.dp)) - if (recentAlbums.isNotEmpty() || recents.isNotEmpty()) { - recentAlbums.take(8).chunked(4).forEach { row -> + if (shortcuts.isNotEmpty()) { + Spacer(Modifier.height(16.dp)) + shortcuts.chunked(4).forEach { row -> Row( Modifier.fillMaxWidth().padding(bottom = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - row.forEach { album -> - Surface( - modifier = Modifier.weight(1f).height(64.dp).clip(RoundedCornerShape(8.dp)) - .clickable { onOpenAlbum(album) }, - color = MaterialTheme.colorScheme.surfaceVariant, - shape = RoundedCornerShape(8.dp) - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - CoverArt(model = album.artworkUri, size = 64.dp, corner = 0.dp) - Text( - album.title, - modifier = Modifier.padding(horizontal = 12.dp), - maxLines = 2, - overflow = TextOverflow.Ellipsis, - fontWeight = FontWeight.SemiBold, - style = MaterialTheme.typography.bodyMedium - ) - } - } + row.forEach { pin -> + SpotifyPinCard(pin = pin, modifier = Modifier.weight(1f)) } repeat(4 - row.size) { Spacer(Modifier.weight(1f)) } } } - Spacer(Modifier.height(16.dp)) } - Text( - status, - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), - modifier = Modifier.padding(bottom = 8.dp) - ) + Spacer(Modifier.height(20.dp)) if (recents.isNotEmpty()) { - SectionHeader("Recents") + SectionHeader("Jump back in") LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { items(recents.take(12), key = { it.id }) { track -> AlbumCard( @@ -473,14 +493,14 @@ private fun HomeFeed( artworkUri = track.artworkUri, tracks = emptyList() ), - onClick = { onPlayRecent(track) } + onClick = { onPlayTracks(listOf(track) + tracks, 0) } ) } } Spacer(Modifier.height(24.dp)) } if (playlists.isNotEmpty()) { - SectionHeader("Playlists") + SectionHeader("Your playlists") LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { items(playlists.take(12), key = { it.id }) { pl -> AlbumCard( @@ -497,26 +517,40 @@ private fun HomeFeed( } Spacer(Modifier.height(24.dp)) } - if (albums.isNotEmpty()) { - SectionHeader("Albums") + if (recentAlbums.isNotEmpty()) { + SectionHeader("Recently played") + LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + items(recentAlbums.take(12), key = { it.id }) { album -> + AlbumCard(album = album, onClick = { onOpenAlbum(album) }) + } + } + Spacer(Modifier.height(24.dp)) + } + if (stuffAlbums.isNotEmpty()) { + val mixAlbums = remember(stuffAlbums) { stuffAlbums.shuffled().take(12) } + SectionHeader("Albums from My Stuff") LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { - items(albums.shuffled().take(12), key = { it.id }) { album -> + items(mixAlbums, key = { it.id }) { album -> AlbumCard(album = album, onClick = { onOpenAlbum(album) }) } } Spacer(Modifier.height(24.dp)) - val artists = albums.map { it.artist }.distinct().take(12) - SectionHeader("Artists") + } + if (stuffArtists.isNotEmpty()) { + SectionHeader("Artists from My Stuff") LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { - items(artists, key = { it }) { name -> + items(stuffArtists.take(12), key = { it }) { name -> val cover = albums.firstOrNull { it.artist == name }?.artworkUri Column( - Modifier.width(140.dp).clickable { - albums.firstOrNull { it.artist == name }?.let(onOpenAlbum) - }, + Modifier.width(140.dp).clickable { onOpenArtist(name) }, horizontalAlignment = Alignment.CenterHorizontally ) { - CoverArt(model = cover, size = 128.dp, corner = 64.dp) + CoverArt( + model = cover, + size = 128.dp, + corner = 64.dp, + contentScale = androidx.compose.ui.layout.ContentScale.Crop + ) Text( name, modifier = Modifier.padding(top = 8.dp), @@ -527,15 +561,142 @@ private fun HomeFeed( } } } - } else { + Spacer(Modifier.height(24.dp)) + } + if (albums.isEmpty() && playlists.isEmpty() && recents.isEmpty()) { Text( - "Nothing in the default music folders yet.", + status.ifBlank { "Nothing in My Stuff yet. Pin albums from the library rail." }, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) } } } +private data class SpotifyPin( + val id: String, + val title: String, + val artworkUri: String?, + val circular: Boolean = false, + val onClick: () -> Unit +) + +private fun buildHomeShortcuts( + pins: List, + liked: Set, + likedTracks: List, + recents: List, + albums: List, + playlists: List, + tracks: List, + onOpenAlbum: (AlbumPageModel) -> Unit, + onOpenPlaylist: (capital.yuri.yuriplayer.desktop.DesktopPlaylist) -> Unit, + onOpenArtist: (String) -> Unit, + onPlayTracks: (List, Int) -> Unit +): List { + val out = ArrayList(8) + fun add(pin: SpotifyPin) { + if (out.size >= 8) return + if (out.any { it.id == pin.id }) return + out += pin + } + if (liked.isNotEmpty()) { + add( + SpotifyPin( + id = "liked", + title = "Liked Songs", + artworkUri = likedTracks.firstOrNull()?.artworkUri + ) { onPlayTracks(likedTracks.ifEmpty { tracks }, 0) } + ) + } + for (pin in pins) { + when (pin.kind) { + DesktopCollection.Kind.ALBUM -> { + val album = albums.firstOrNull { it.id == pin.id } + add( + SpotifyPin(pin.id, pin.title, album?.artworkUri) { + album?.let(onOpenAlbum) + } + ) + } + DesktopCollection.Kind.ARTIST -> add( + SpotifyPin( + pin.id, + pin.title, + tracks.firstOrNull { it.displayArtist == pin.title }?.artworkUri, + circular = true + ) { onOpenArtist(pin.title) } + ) + DesktopCollection.Kind.SONG -> { + val t = tracks.firstOrNull { it.id == pin.id } + add( + SpotifyPin(pin.id, pin.title, t?.artworkUri) { + t?.let { onPlayTracks(listOf(it) + tracks, 0) } + } + ) + } + DesktopCollection.Kind.PLAYLIST -> { + val pl = playlists.firstOrNull { it.id == pin.id } + add( + SpotifyPin(pin.id, pin.title, pl?.artworkUri(tracks)) { + pl?.let(onOpenPlaylist) + } + ) + } + } + } + recents.mapNotNull { t -> albums.firstOrNull { a -> a.tracks.any { it.id == t.id } } } + .distinctBy { it.id } + .forEach { album -> + add(SpotifyPin(album.id, album.title, album.artworkUri) { onOpenAlbum(album) }) + } + playlists.forEach { pl -> + add( + SpotifyPin("playlist:${pl.id}", pl.name, pl.artworkUri(tracks)) { + onOpenPlaylist(pl) + } + ) + } + albums.forEach { album -> + add(SpotifyPin(album.id, album.title, album.artworkUri) { onOpenAlbum(album) }) + } + return out +} + +@Composable +private fun SpotifyPinCard(pin: SpotifyPin, modifier: Modifier = Modifier) { + Surface( + modifier = modifier + .height(72.dp) + .clip(RoundedCornerShape(6.dp)) + .clickable(onClick = pin.onClick), + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.55f), + shape = RoundedCornerShape(6.dp), + tonalElevation = 2.dp + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + CoverArt( + model = pin.artworkUri, + size = 72.dp, + corner = if (pin.circular) 36.dp else 0.dp, + contentScale = if (pin.circular) { + androidx.compose.ui.layout.ContentScale.Crop + } else { + androidx.compose.ui.layout.ContentScale.Fit + } + ) + Text( + pin.title, + modifier = Modifier.padding(horizontal = 12.dp), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface + ) + } + } +} + @Composable private fun SectionHeader(title: String) { Row( From f05c5fdc743cba4a779b95032ce881f7a71ed61f Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:29:42 +0000 Subject: [PATCH 24/69] Make desktop search work like mobile Explore Search now finds songs, albums, artists, and playlists from the index plus live Jellyfin/Subsonic queries. Library filters sit as chips (All, On this device, each server) instead of a sheet. --- .../yuri/yuriplayer/core/library/Track.kt | 4 +- .../core/library/LocalLibraryScanner.kt | 7 +- .../yuriplayer/core/source/JellyfinCatalog.kt | 27 +- .../yuriplayer/core/source/SubsonicCatalog.kt | 50 ++- .../yuri/yuriplayer/desktop/DesktopSession.kt | 17 + .../yuriplayer/desktop/ui/DesktopExplore.kt | 382 ++++++++++++++++++ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 21 +- 7 files changed, 488 insertions(+), 20 deletions(-) create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt index 5f66e062..815c706f 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt @@ -20,7 +20,9 @@ data class Track( val year: Int? = null, val genre: String? = null, val artworkUri: String? = null, - val path: String? = null + val path: String? = null, + /** Local library id `"local"` or a remote account id. */ + val sourceId: String? = null ) { val displayTitle: String get() = title?.takeIf { it.isNotBlank() } diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt index e0c69461..b9ea1da7 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/library/LocalLibraryScanner.kt @@ -9,6 +9,7 @@ import java.util.logging.Level import java.util.logging.Logger object LocalLibraryScanner { + const val SOURCE_LOCAL = "local" private val AUDIO_EXTENSIONS = setOf( "flac", "mp3", "ogg", "opus", "m4a", "mp4", "aac", "wav", "aiff", "aif", "wma", "alac" @@ -76,7 +77,8 @@ object LocalLibraryScanner { year = field(FieldKey.YEAR)?.take(4)?.toIntOrNull(), genre = field(FieldKey.GENRE), artworkUri = resolveCover(file, audio, coverByDir), - path = file.absolutePath + path = file.absolutePath, + sourceId = SOURCE_LOCAL ) } catch (_: Exception) { Track( @@ -84,7 +86,8 @@ object LocalLibraryScanner { uri = file.toURI().toString(), title = fallbackTitle, artworkUri = resolveCover(file, null, coverByDir), - path = file.absolutePath + path = file.absolutePath, + sourceId = SOURCE_LOCAL ) } } diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt index af0b1a2e..1d41f366 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt @@ -116,6 +116,30 @@ class JellyfinCatalog( out } + suspend fun searchTracks(account: RemoteAccount, query: String, limit: Int = 80): Result> = + runCatching { + val token = account.accessToken ?: error("Not signed in") + val userId = account.userId ?: error("Not signed in") + val root = normalizeBaseUrl(account.baseUrl) + val requestUrl = url(root) { + path("Users", userId, "Items") + param("SearchTerm", query) + param("IncludeItemTypes", "Audio") + param("Recursive", "true") + param("Fields", "Album,AlbumArtist,Artists,RunTimeTicks,IndexNumber,ParentIndexNumber,ProductionYear,Genres,PrimaryImageAspectRatio,Container") + param("EnableImageTypes", "Primary") + param("ImageTypeLimit", "1") + param("Limit", limit) + } + val response = http.get(requestUrl) { + header("X-Emby-Authorization", authHeader(token)) + header("X-Emby-Token", token) + } + if (!response.status.isSuccess()) error("Search HTTP ${response.status.value}") + val page = json.decodeFromString(response.bodyAsText()) + page.items.mapNotNull { toTrack(account.copy(baseUrl = root), it) } + } + private fun toTrack(account: RemoteAccount, item: JfItem): Track? { val id = item.id ?: return null val token = account.accessToken ?: return null @@ -150,7 +174,8 @@ class JellyfinCatalog( trackNumber = item.indexNumber, discNumber = item.parentIndexNumber, year = item.year, - artworkUri = art + artworkUri = art, + sourceId = account.id ) } diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt index 0d54b68f..46ec4664 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt @@ -52,7 +52,7 @@ class SubsonicCatalog( for (album in page) { kotlinx.coroutines.currentCoroutineContext().ensureActive() val id = album.id ?: continue - out += songsForAlbum(session, id, album) + out += songsForAlbum(session, id, album, account.id) if (out.size >= maxSongs) break } offset += page.size @@ -69,7 +69,7 @@ class SubsonicCatalog( for (album in albums) { kotlinx.coroutines.currentCoroutineContext().ensureActive() val id = album.id ?: continue - out += songsForAlbum(session, id, album) + out += songsForAlbum(session, id, album, account.id) } out } @@ -92,7 +92,8 @@ class SubsonicCatalog( private suspend fun songsForAlbum( session: Session, albumId: String, - albumRef: SubsonicAlbum + albumRef: SubsonicAlbum, + sourceId: String ): List { val body = apiGet(session, "getAlbum") { param("id", albumId) } val album = json.decodeFromString(body).subsonicResponse.album @@ -115,11 +116,44 @@ class SubsonicCatalog( discNumber = child.discNumber, year = child.year ?: album.year, genre = child.genre, - artworkUri = coverUrl(session, child.coverArt) ?: cover + artworkUri = coverUrl(session, child.coverArt) ?: cover, + sourceId = sourceId ) } } + suspend fun searchTracks(account: RemoteAccount, query: String, limit: Int = 80): Result> = + runCatching { + val session = sessionOf(account) + val body = apiGet(session, "search3") { + param("query", query) + param("songCount", limit) + param("albumCount", 12) + param("artistCount", 12) + } + val songs = json.decodeFromString(body) + .subsonicResponse.searchResult3?.song.orEmpty() + songs.mapNotNull { child -> + if (child.isDir == true) return@mapNotNull null + val sid = child.id ?: return@mapNotNull null + Track( + id = "subsonic:$sid", + uri = streamUrl(session, sid), + title = child.title ?: child.name, + artist = child.artist, + albumArtist = child.artist, + album = child.album, + durationMs = child.duration?.times(1000L), + trackNumber = child.track, + discNumber = child.discNumber, + year = child.year, + genre = child.genre, + artworkUri = coverUrl(session, child.coverArt), + sourceId = account.id + ) + } + } + fun streamUrl(session: Session, id: String): String = restUrl(session, "stream") { param("id", id) @@ -189,7 +223,13 @@ class SubsonicCatalog( val type: String? = null, val error: SubsonicError? = null, val albumList2: SubsonicAlbumList? = null, - val album: SubsonicAlbumDetail? = null + val album: SubsonicAlbumDetail? = null, + val searchResult3: SubsonicSearchResult? = null + ) + + @Serializable + private data class SubsonicSearchResult( + val song: List = emptyList() ) @Serializable diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index cc9c96c3..187805d2 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -555,6 +555,23 @@ class DesktopSession { fun extraScan(path: String) = addFolder(path) + suspend fun searchRemotes(query: String, sourceIds: Set?): List { + val q = query.trim() + if (q.isEmpty()) return emptyList() + val remotes = sources.remotes.value.filter { it.enabled } + .filter { sourceIds == null || it.id in sourceIds } + val out = ArrayList() + for (remote in remotes) { + val result = when (remote.kind) { + SourceKind.JELLYFIN -> jellyfin.searchTracks(remote, q) + SourceKind.SUBSONIC -> subsonic.searchTracks(remote, q) + SourceKind.LOCAL -> continue + } + result.onSuccess { out += it } + } + return out + } + fun release() { ticker?.cancel() media.release() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt new file mode 100644 index 00000000..b61a4fef --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt @@ -0,0 +1,382 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.model.AlbumPageModel +import capital.yuri.yuriplayer.components.model.albums +import capital.yuri.yuriplayer.components.model.toRow +import capital.yuri.yuriplayer.core.library.LocalLibraryScanner +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.desktop.DesktopPlaylist +import capital.yuri.yuriplayer.desktop.DesktopSession +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext + +private const val SEARCH_DEBOUNCE_MS = 280L +private const val SONG_HIT_LIMIT = 80 +private const val ALBUM_HIT_LIMIT = 12 +private const val ARTIST_HIT_LIMIT = 12 + +@Composable +fun DesktopExplore( + session: DesktopSession, + query: String, + tracks: List, + albums: List, + playlists: List, + onOpenAlbum: (AlbumPageModel) -> Unit, + onOpenPlaylist: (DesktopPlaylist) -> Unit, + onOpenArtist: (String) -> Unit, + onPlaySongs: (List, Int) -> Unit +) { + val remotes by session.sources.remotes.collectAsState() + val enabled = remember(remotes) { remotes.filter { it.enabled } } + val allKeys = remember(enabled) { + buildSet { + add(LocalLibraryScanner.SOURCE_LOCAL) + enabled.forEach { add(it.id) } + } + } + var selectedKeys by remember { mutableStateOf(emptySet()) } + val effective = if (selectedKeys.isEmpty()) allKeys else selectedKeys + + var songHits by remember { mutableStateOf>(emptyList()) } + var albumHits by remember { mutableStateOf>(emptyList()) } + var artistHits by remember { mutableStateOf>(emptyList()) } + var playlistHits by remember { mutableStateOf>(emptyList()) } + var busy by remember { mutableStateOf(false) } + + LaunchedEffect(query, selectedKeys, tracks, albums, playlists, enabled) { + val q = query.trim() + if (q.isEmpty()) { + songHits = emptyList() + albumHits = emptyList() + artistHits = emptyList() + playlistHits = emptyList() + busy = false + return@LaunchedEffect + } + busy = true + delay(SEARCH_DEBOUNCE_MS) + if (query.trim() != q) return@LaunchedEffect + + val includeLocal = selectedKeys.isEmpty() || LocalLibraryScanner.SOURCE_LOCAL in selectedKeys + val remoteIds = if (selectedKeys.isEmpty()) null else selectedKeys - LocalLibraryScanner.SOURCE_LOCAL + + val indexed = tracks.filter { track -> + val sid = track.sourceId ?: LocalLibraryScanner.SOURCE_LOCAL + sid in effective + } + val localMatches = withContext(Dispatchers.Default) { + indexed.filter { it.matches(q) }.take(SONG_HIT_LIMIT) + } + if (query.trim() != q) return@LaunchedEffect + + val remoteMatches = if (remoteIds == null || remoteIds.isNotEmpty()) { + withContext(Dispatchers.IO) { session.searchRemotes(q, remoteIds) } + } else { + emptyList() + } + if (query.trim() != q) return@LaunchedEffect + + val songs = (localMatches + remoteMatches).distinctBy { it.id }.take(SONG_HIT_LIMIT) + val foundAlbums = albums.filter { album -> + (album.title.contains(q, true) || album.artist.contains(q, true)) && + album.tracks.any { row -> + val t = tracks.firstOrNull { it.id == row.id } + val sid = t?.sourceId ?: LocalLibraryScanner.SOURCE_LOCAL + sid in effective + } + }.take(ALBUM_HIT_LIMIT).ifEmpty { + songs.albums().take(ALBUM_HIT_LIMIT) + } + val foundArtists = songs + .map { it.displayArtist } + .distinct() + .filter { it.contains(q, true) } + .take(ARTIST_HIT_LIMIT) + .map { name -> + val ofArtist = tracks.filter { it.displayArtist.equals(name, true) } + ArtistHit( + name = name, + artworkUri = ofArtist.firstNotNullOfOrNull { it.artworkUri }, + trackCount = ofArtist.size, + albumCount = ofArtist.map { it.displayAlbum }.distinct().size + ) + } + val foundPlaylists = if (includeLocal) { + playlists.filter { it.name.contains(q, true) }.take(12) + } else { + emptyList() + } + + songHits = songs + albumHits = foundAlbums + artistHits = foundArtists + playlistHits = foundPlaylists + busy = false + } + + Column(Modifier.fillMaxSize()) { + Text( + "Filters", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(start = 20.dp, top = 12.dp, end = 20.dp, bottom = 4.dp) + ) + LazyRow( + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(horizontal = 20.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + item { + FilterChip( + selected = selectedKeys.isEmpty(), + onClick = { selectedKeys = emptySet() }, + label = { Text("All", maxLines = 1) } + ) + } + item { + FilterChip( + selected = selectedKeys.isEmpty() || LocalLibraryScanner.SOURCE_LOCAL in selectedKeys, + onClick = { + selectedKeys = toggleKey( + LocalLibraryScanner.SOURCE_LOCAL, allKeys, selectedKeys + ) + }, + label = { Text("On this device", maxLines = 1) } + ) + } + items(enabled, key = { it.id }) { remote -> + FilterChip( + selected = selectedKeys.isEmpty() || remote.id in selectedKeys, + onClick = { selectedKeys = toggleKey(remote.id, allKeys, selectedKeys) }, + label = { Text(remote.name.ifBlank { remote.kind.name }, maxLines = 1) } + ) + } + } + + val status = when { + busy -> "Searching…" + query.trim().isEmpty() -> "Type to search songs, albums, artists, and playlists" + else -> buildList { + if (artistHits.isNotEmpty()) add("${artistHits.size} artists") + if (albumHits.isNotEmpty()) add("${albumHits.size} albums") + if (playlistHits.isNotEmpty()) add("${playlistHits.size} playlists") + add("${songHits.size} songs") + }.joinToString(" · ") + } + Row( + Modifier.padding(horizontal = 20.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (busy) { + CircularProgressIndicator(Modifier.size(14.dp), strokeWidth = 2.dp) + Spacer(Modifier.width(8.dp)) + } + Text( + status, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + } + + when { + query.trim().isEmpty() -> { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Icon( + Icons.Default.Search, + contentDescription = null, + modifier = Modifier.size(48.dp), + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.25f) + ) + Spacer(Modifier.height(12.dp)) + Text( + "Explore every library", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + Text( + "Artists, albums, songs, and playlists · local + Jellyfin + Subsonic", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f), + modifier = Modifier.padding(top = 6.dp) + ) + } + } + } + !busy && songHits.isEmpty() && albumHits.isEmpty() && + artistHits.isEmpty() && playlistHits.isEmpty() -> { + Text( + "No matches for \"${query.trim()}\".", + modifier = Modifier.padding(20.dp), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + else -> { + LazyColumn(Modifier.fillMaxSize()) { + if (artistHits.isNotEmpty()) { + item { ExploreSection("Artists") } + items(artistHits, key = { "ar-${it.name}" }) { artist -> + Row( + Modifier + .fillMaxWidth() + .clickable { onOpenArtist(artist.name) } + .padding(horizontal = 20.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CoverArt( + model = artist.artworkUri, + size = 48.dp, + corner = 24.dp, + contentScale = ContentScale.Crop + ) + Spacer(Modifier.width(12.dp)) + Column { + Text(artist.name, fontWeight = FontWeight.Medium, maxLines = 1) + Text( + "${artist.trackCount} tracks · ${artist.albumCount} albums", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + } + } + } + if (albumHits.isNotEmpty()) { + item { ExploreSection("Albums") } + items(albumHits, key = { "al-${it.id}" }) { album -> + Row( + Modifier + .fillMaxWidth() + .clickable { onOpenAlbum(album) } + .padding(horizontal = 20.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CoverArt(model = album.artworkUri, size = 48.dp, corner = 6.dp) + Spacer(Modifier.width(12.dp)) + Column { + Text(album.title, fontWeight = FontWeight.Medium, maxLines = 1) + Text( + album.artist, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } + } + if (playlistHits.isNotEmpty()) { + item { ExploreSection("Playlists") } + items(playlistHits, key = { "pl-${it.id}" }) { pl -> + Row( + Modifier + .fillMaxWidth() + .clickable { onOpenPlaylist(pl) } + .padding(horizontal = 20.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CoverArt( + model = pl.artworkUri(tracks), + size = 48.dp, + corner = 6.dp + ) + Spacer(Modifier.width(12.dp)) + Column { + Text(pl.name, fontWeight = FontWeight.Medium, maxLines = 1) + Text( + pl.description ?: "Playlist", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + maxLines = 1 + ) + } + } + } + } + if (songHits.isNotEmpty()) { + item { ExploreSection("Songs") } + itemsIndexed(songHits, key = { _, t -> t.id }) { index, track -> + TrackRow( + track = track.toRow(), + onClick = { onPlaySongs(songHits, index) } + ) + } + } + item { Spacer(Modifier.height(24.dp)) } + } + } + } + } +} + +@Composable +private fun ExploreSection(title: String) { + Text( + title, + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + modifier = Modifier.padding(horizontal = 20.dp, vertical = 10.dp) + ) +} + +private data class ArtistHit( + val name: String, + val artworkUri: String?, + val trackCount: Int, + val albumCount: Int +) + +private fun Track.matches(q: String): Boolean = + displayTitle.contains(q, true) || + displayArtist.contains(q, true) || + displayAlbum.contains(q, true) || + albumArtist.orEmpty().contains(q, true) + +private fun toggleKey(key: String, allKeys: Set, selected: Set): Set { + val effective = if (selected.isEmpty()) allKeys else selected + val next = if (key in effective) effective - key else effective + key + return if (next.isEmpty() || next.containsAll(allKeys)) emptySet() else next +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 4e1fd115..ada18bf9 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -200,12 +200,6 @@ fun YuriDesktopApp(session: DesktopSession) { pinItems + rest.filter { it.id !in pinIds } } val selectedLibraryId = (route as? Route.Album)?.album?.id - val visibleAlbums = remember(albums, query) { - if (query.isBlank()) albums - else albums.filter { - it.title.contains(query, true) || it.artist.contains(query, true) - } - } DesktopBentoLayout( nav = nav, @@ -328,11 +322,16 @@ fun YuriDesktopApp(session: DesktopSession) { }, onPlayTracks = { list, i -> session.player.play(list, i) } ) - Route.Search -> LibraryGrid( - albums = visibleAlbums, - status = if (query.isBlank()) "Search albums and artists" else "${visibleAlbums.size} matches", - emptyHint = if (query.isBlank()) "Type in the search bar." else "Nothing matches.", - onOpen = ::openAlbum + Route.Search -> DesktopExplore( + session = session, + query = query, + tracks = tracks, + albums = albums, + playlists = playlists, + onOpenAlbum = ::openAlbum, + onOpenPlaylist = { push(Route.Playlist(it.id)) }, + onOpenArtist = { name -> query = name }, + onPlaySongs = { list, i -> session.player.play(list, i) } ) is Route.Album -> { val live = albums.firstOrNull { it.id == r.album.id } ?: r.album From 4ff10b32696a4d477134a35f21274afad6cb6299 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:32:50 +0000 Subject: [PATCH 25/69] Match mobile indexing on desktop Persist the track index and per-source cursors so a scan survives restart. Partial scans skip warm libraries (or delta if the server grew); full scans resume from the last page and drop deleted tracks. Pause/stop checkpoint the cursor instead of starting over. --- .../yuriplayer/core/source/JellyfinCatalog.kt | 12 +- .../yuriplayer/core/source/SubsonicCatalog.kt | 19 +- .../yuriplayer/desktop/DesktopIndexStore.kt | 63 +++++ .../yuri/yuriplayer/desktop/DesktopSession.kt | 218 ++++++++++++++++-- 4 files changed, 281 insertions(+), 31 deletions(-) create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt index 1d41f366..0a5fcaa2 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt @@ -73,14 +73,16 @@ class JellyfinCatalog( account: RemoteAccount, maxItems: Int = 50_000, sortBy: String = "Album,SortName", - sortOrder: String = "Ascending" + sortOrder: String = "Ascending", + startFrom: Int = 0, + onPage: suspend (page: List, cursor: Int, total: Int?) -> Unit = { _, _, _ -> } ): Result> = runCatching { val token = account.accessToken ?: error("Not signed in") val userId = account.userId ?: error("Not signed in") val root = normalizeBaseUrl(account.baseUrl) val out = ArrayList(512) - var start = 0 + var start = startFrom.coerceAtLeast(0) val pageSize = 400 while (out.size < maxItems) { kotlinx.coroutines.currentCoroutineContext().ensureActive() @@ -105,10 +107,10 @@ class JellyfinCatalog( val page = json.decodeFromString(response.bodyAsText()) val items = page.items if (items.isEmpty()) break - items.forEach { item -> - toTrack(account.copy(baseUrl = root), item)?.let { out += it } - } + val mapped = items.mapNotNull { toTrack(account.copy(baseUrl = root), it) } + out += mapped start += items.size + onPage(mapped, start, page.total) if (items.size < take) break val total = page.total if (total != null && start >= total) break diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt index 46ec4664..bbb4a532 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt @@ -39,24 +39,33 @@ class SubsonicCatalog( ) } - suspend fun listTracks(account: RemoteAccount, maxSongs: Int = 80_000): Result> = + suspend fun listTracks( + account: RemoteAccount, + maxSongs: Int = 80_000, + startAlbumOffset: Int = 0, + onPage: suspend (page: List, albumOffset: Int, complete: Boolean) -> Unit = { _, _, _ -> } + ): Result> = runCatching { val session = sessionOf(account) val out = ArrayList(512) - var offset = 0 + var offset = startAlbumOffset.coerceAtLeast(0) val pageSize = 100 while (out.size < maxSongs) { kotlinx.coroutines.currentCoroutineContext().ensureActive() val page = listAlbums(session, offset, pageSize, type = "alphabeticalByName") if (page.isEmpty()) break + val batch = ArrayList() for (album in page) { kotlinx.coroutines.currentCoroutineContext().ensureActive() val id = album.id ?: continue - out += songsForAlbum(session, id, album, account.id) - if (out.size >= maxSongs) break + batch += songsForAlbum(session, id, album, account.id) + if (out.size + batch.size >= maxSongs) break } + out += batch offset += page.size - if (page.size < pageSize) break + val done = page.size < pageSize + onPage(batch, offset, done) + if (done) break } out } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt new file mode 100644 index 00000000..5fbaa8f4 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt @@ -0,0 +1,63 @@ +package capital.yuri.yuriplayer.desktop + +import capital.yuri.yuriplayer.core.library.Track +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File + +/** + * Durable library index + per-source scan cursors, matching mobile's + * Room catalog + [ScanCheckpointStore]. Survives process death so a + * 40k-track Jellyfin walk does not restart from zero. + */ +class DesktopIndexStore(cacheDir: String) { + private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true } + private val tracksFile = File(cacheDir, "index-tracks.json") + private val cpFile = File(cacheDir, "index-checkpoints.json") + + fun loadTracks(): List { + if (!tracksFile.isFile) return emptyList() + return runCatching { + json.decodeFromString>(tracksFile.readText()) + }.getOrDefault(emptyList()) + } + + fun saveTracks(tracks: List) { + runCatching { + tracksFile.parentFile?.mkdirs() + val tmp = File(tracksFile.parentFile, "${tracksFile.name}.tmp") + tmp.writeText(json.encodeToString(tracks)) + if (!tmp.renameTo(tracksFile)) { + tmp.copyTo(tracksFile, overwrite = true) + tmp.delete() + } + } + } + + fun loadCheckpoints(): List { + if (!cpFile.isFile) return emptyList() + return runCatching { + json.decodeFromString>(cpFile.readText()) + }.getOrDefault(emptyList()) + } + + fun saveCheckpoints(rows: List) { + runCatching { + cpFile.parentFile?.mkdirs() + cpFile.writeText(json.encodeToString(rows)) + } + } + + @Serializable + data class StoredCheckpoint( + val id: String, + val name: String, + val status: String, + val startIndex: Int = 0, + val delivered: Int = 0, + val totalHint: Int? = null, + val count: Int = 0, + val detail: String = "" + ) +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 187805d2..4024ac4a 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -45,7 +45,10 @@ data class DesktopScanSource( val name: String, val status: DesktopScanStatus, val detail: String = "", - val count: Int = 0 + val count: Int = 0, + val startIndex: Int = 0, + val delivered: Int = 0, + val totalHint: Int? = null ) class DesktopSession { @@ -85,6 +88,7 @@ class DesktopSession { val collection = DesktopCollection(dirs.configDir) val playlists = DesktopPlaylistStore(dirs.configDir) val sources = LibrarySourceStore(dirs.configDir) + private val index = DesktopIndexStore(dirs.cacheDir) val jellyfin = JellyfinCatalog(http, sources.deviceId) val subsonic = SubsonicCatalog(http) @@ -137,7 +141,34 @@ class DesktopSession { scope.launch { sources.remotes.collect { refreshScanSources() } } - rescan() + hydrateIndex() + } + + private fun hydrateIndex() { + val cached = index.loadTracks() + if (cached.isNotEmpty()) _tracks.value = cached + val cps = index.loadCheckpoints() + if (cps.isNotEmpty()) { + _scanSources.value = cps.map { row -> + DesktopScanSource( + id = row.id, + name = row.name, + status = runCatching { DesktopScanStatus.valueOf(row.status) } + .getOrDefault(DesktopScanStatus.IDLE), + detail = row.detail, + count = row.count, + startIndex = row.startIndex, + delivered = row.delivered, + totalHint = row.totalHint + ) + } + } + _scanMessage.value = when { + cached.isEmpty() -> "Indexing…" + else -> "${cached.size} tracks" + } + // Same as mobile: warm index → partial; empty → first full walk. + requestScan(force = cached.isEmpty()) } fun playTrack(track: Track) { @@ -266,7 +297,9 @@ class DesktopSession { fun removeRemote(id: String) { sources.removeRemote(id) - rescan() + replaceSourceTracks(id, emptyList()) + persistIndex() + refreshScanSources() } fun indexFolder(path: String) { @@ -283,6 +316,35 @@ class DesktopSession { _tracks.value = (_tracks.value + incoming).distinctBy { it.id } } + private fun replaceSourceTracks(sourceId: String, incoming: List) { + val rest = _tracks.value.filterNot { + (it.sourceId ?: LOCAL_SCAN_ID) == sourceId + } + _tracks.value = (rest + incoming).distinctBy { it.id } + } + + private fun persistIndex() { + index.saveTracks(_tracks.value) + persistCheckpoints() + } + + private fun persistCheckpoints() { + index.saveCheckpoints( + _scanSources.value.map { + DesktopIndexStore.StoredCheckpoint( + id = it.id, + name = it.name, + status = it.status.name, + startIndex = it.startIndex, + delivered = it.delivered, + totalHint = it.totalHint, + count = it.count, + detail = it.detail + ) + } + ) + } + fun rescan() = requestScan(force = true) fun requestScan(force: Boolean = false, sourceId: String? = null) { @@ -291,6 +353,14 @@ class DesktopSession { stoppedIds.clear() sourceJob?.cancel() scanJob?.cancel() + _scanSources.value = _scanSources.value.map { + it.copy(startIndex = 0, delivered = 0) + } + } + if (force && sourceId != null) { + _scanSources.value = _scanSources.value.map { + if (it.id == sourceId) it.copy(startIndex = 0, delivered = 0) else it + } } stopAll.set(false) pauseAll.set(false) @@ -314,6 +384,7 @@ class DesktopSession { sourceJob?.cancel() markRunning(DesktopScanStatus.PAUSED, "Paused") _scanMessage.value = "Paused" + persistIndex() } else { pausedIds.add(sourceId) patchScan(sourceId, DesktopScanStatus.PAUSED, "Paused") @@ -328,6 +399,7 @@ class DesktopSession { markRunning(DesktopScanStatus.STOPPED, "Stopped") _isScanning.value = false _scanMessage.value = "Stopped" + persistIndex() } else { stoppedIds.add(sourceId) patchScan(sourceId, DesktopScanStatus.STOPPED, "Stopped") @@ -357,7 +429,7 @@ class DesktopSession { for (remote in remotes) { if (halted()) break val got = scanOneSource(remote.id, remote.name, force, onlySourceId) { - fetchRemote(remote, partial = !force) + fetchRemote(remote, force = force) } remoteTracks += got } @@ -365,10 +437,20 @@ class DesktopSession { if (stopAll.get() || pauseAll.get()) return@coroutineScope if (onlySourceId == null && force) { - _tracks.value = (localTracks + remoteTracks).distinctBy { it.id } + val keepIds = buildSet { + if (doLocal) add(LOCAL_SCAN_ID) + remotes.forEach { add(it.id) } + } + val leftovers = _tracks.value.filterNot { + (it.sourceId ?: LOCAL_SCAN_ID) in keepIds + } + _tracks.value = (leftovers + localTracks + remoteTracks).distinctBy { it.id } + } else if (onlySourceId != null && force) { + replaceSourceTracks(onlySourceId, localTracks + remoteTracks) } else { mergeTracks(localTracks + remoteTracks) } + persistIndex() _scanMessage.value = when { _tracks.value.isEmpty() -> "No tracks yet — add a folder or a server in Settings → Library" @@ -400,9 +482,19 @@ class DesktopSession { if (id in stoppedIds && onlySourceId != id) return false if (id in pausedIds && onlySourceId != id && !force) return false val src = _scanSources.value.firstOrNull { it.id == id } - if (force || onlySourceId == id) return true - // Partial all: skip already-indexed sources. - if (src?.status == DesktopScanStatus.DONE && (src.count > 0)) return false + if (force) return true + if (onlySourceId == id) return true + if (src?.status == DesktopScanStatus.PAUSED || src?.status == DesktopScanStatus.STOPPED) { + return true + } + if (src?.status == DesktopScanStatus.DONE && src.count > 0) { + val othersNeedFirst = _scanSources.value.any { + it.id != id && it.count == 0 && + it.status != DesktopScanStatus.DONE && + it.status != DesktopScanStatus.PAUSED + } + return !othersNeedFirst + } return true } @@ -452,6 +544,7 @@ class DesktopSession { }, nextCount ) + persistIndex() incoming } catch (e: CancellationException) { when { @@ -486,15 +579,21 @@ class DesktopSession { return if (roots.isEmpty()) emptyList() else LocalLibraryScanner.scan(roots) } - private suspend fun fetchRemote(remote: RemoteAccount, partial: Boolean): List { - val known = _scanSources.value.firstOrNull { it.id == remote.id }?.count ?: 0 - val result = when (remote.kind) { + private suspend fun fetchRemote(remote: RemoteAccount, force: Boolean): List { + val src = _scanSources.value.firstOrNull { it.id == remote.id } + val known = src?.count ?: _tracks.value.count { it.sourceId == remote.id } + val already = known > 0 && src?.status == DesktopScanStatus.DONE + val incremental = !force && already + val resumeFrom = if (force || incremental) 0 else src?.startIndex ?: 0 + val priorDelivered = if (force || incremental) 0 else src?.delivered ?: 0 + + return when (remote.kind) { SourceKind.JELLYFIN -> { val authed = if (remote.accessToken.isNullOrBlank()) { jellyfin.authenticate(remote).getOrElse { throw it } .also { sources.upsertRemote(it) } } else remote - if (partial && known > 0) { + if (incremental) { val total = jellyfin.audioCount(authed).getOrElse { -1 } if (total in 0..known) return emptyList() val cap = if (total > known) (total - known + 64).coerceIn(1, 2_000) else 256 @@ -503,18 +602,82 @@ class DesktopSession { maxItems = cap, sortBy = "DateCreated", sortOrder = "Descending" - ) + ).getOrElse { throw it } } else { - jellyfin.listTracks(authed) + walkJellyfin(authed, remote, resumeFrom, priorDelivered) } } SourceKind.SUBSONIC -> { - if (partial && known > 0) subsonic.listNewestTracks(remote) - else subsonic.listTracks(remote) + if (incremental) { + subsonic.listNewestTracks(remote).getOrElse { throw it } + } else { + walkSubsonic(remote, resumeFrom, priorDelivered) + } + } + SourceKind.LOCAL -> emptyList() + } + } + + private suspend fun walkJellyfin( + authed: RemoteAccount, + remote: RemoteAccount, + startFrom: Int, + priorDelivered: Int + ): List { + var cursor = startFrom + var delivered = priorDelivered + val collected = ArrayList() + val result = jellyfin.listTracks(authed, startFrom = startFrom) { page, next, total -> + collected += page + cursor = next + delivered += page.size + mergeTracks(page) + patchScan( + id = remote.id, + status = DesktopScanStatus.RUNNING, + detail = if (total != null) "$delivered / $total" else "$delivered", + count = delivered, + startIndex = cursor, + delivered = delivered, + totalHint = total + ) + persistCheckpoints() + if (pauseAll.get() || remote.id in pausedIds) { + throw CancellationException("paused") + } + if (stopAll.get() || remote.id in stoppedIds) { + throw CancellationException("stopped") } - SourceKind.LOCAL -> Result.success(emptyList()) } - return result.getOrElse { throw it } + return result.getOrElse { throw it }.ifEmpty { collected } + } + + private suspend fun walkSubsonic( + remote: RemoteAccount, + startAlbumOffset: Int, + priorDelivered: Int + ): List { + var cursor = startAlbumOffset + var delivered = priorDelivered + val collected = ArrayList() + val result = subsonic.listTracks(remote, startAlbumOffset = startAlbumOffset) { page, next, _ -> + collected += page + cursor = next + delivered += page.size + mergeTracks(page) + patchScan( + id = remote.id, + status = DesktopScanStatus.RUNNING, + detail = "$delivered", + count = delivered, + startIndex = cursor, + delivered = delivered + ) + persistCheckpoints() + if (pauseAll.get() || remote.id in pausedIds) throw CancellationException("paused") + if (stopAll.get() || remote.id in stoppedIds) throw CancellationException("stopped") + } + return result.getOrElse { throw it }.ifEmpty { collected } } private fun refreshScanSources() { @@ -528,7 +691,10 @@ class DesktopSession { name = remote.name, status = prev?.status ?: DesktopScanStatus.IDLE, detail = prev?.detail.orEmpty(), - count = prev?.count ?: 0 + count = prev?.count ?: 0, + startIndex = prev?.startIndex ?: 0, + delivered = prev?.delivered ?: 0, + totalHint = prev?.totalHint ) } _scanSources.value = listOf(local.copy(name = "Local library")) + remotes @@ -538,11 +704,21 @@ class DesktopSession { id: String, status: DesktopScanStatus, detail: String, - count: Int? = null + count: Int? = null, + startIndex: Int? = null, + delivered: Int? = null, + totalHint: Int? = null ) { _scanSources.value = _scanSources.value.map { if (it.id != id) it - else it.copy(status = status, detail = detail, count = count ?: it.count) + else it.copy( + status = status, + detail = detail, + count = count ?: it.count, + startIndex = startIndex ?: it.startIndex, + delivered = delivered ?: it.delivered, + totalHint = totalHint ?: it.totalHint + ) } } From 5977ae5dcda278ac7e6e06e48ebc12deea51773e Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:35:46 +0000 Subject: [PATCH 26/69] Fix desktop search missing local Twenty One Pilots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match on path/folder names, fold stylized letters (ø → o), and require query tokens instead of a raw substring. Local hits render immediately so a slow Jellyfin search cannot hide on-disk results. --- .../yuriplayer/core/library/TrackSearch.kt | 57 ++++++++++++ .../core/library/TrackSearchTest.kt | 34 +++++++ .../yuriplayer/desktop/ui/DesktopExplore.kt | 93 ++++++++++--------- 3 files changed, 138 insertions(+), 46 deletions(-) create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/TrackSearch.kt create mode 100644 core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackSearchTest.kt diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/TrackSearch.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/TrackSearch.kt new file mode 100644 index 00000000..2b87e3c8 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/TrackSearch.kt @@ -0,0 +1,57 @@ +package capital.yuri.yuriplayer.core.library + +/** + * Library search matching used by desktop Explore (and shared later). + * Folds stylized letters (ø → o), ignores extra punctuation/spacing, and + * requires every query token to appear somewhere in title/artist/album/path. + */ +fun Track.matchesQuery(query: String): Boolean { + val tokens = searchTokens(query) + if (tokens.isEmpty()) return false + val hay = foldSearch( + listOfNotNull( + title, artist, albumArtist, album, genre, path, uri, displayTitle, displayArtist, displayAlbum + ).joinToString(" ") + ) + return tokens.all { hay.contains(it) } +} + +fun String.matchesSearch(query: String): Boolean { + val tokens = searchTokens(query) + if (tokens.isEmpty()) return false + val hay = foldSearch(this) + return tokens.all { hay.contains(it) } +} + +fun searchTokens(query: String): List = + foldSearch(query).split(' ').filter { it.isNotEmpty() } + +fun foldSearch(raw: String): String { + val sb = StringBuilder(raw.length) + var i = 0 + while (i < raw.length) { + val ch = raw[i] + when (ch) { + 'ø', 'Ø', 'ó', 'ò', 'ö', 'ô', 'õ', 'Ó', 'Ò', 'Ö', 'Ô', 'Õ' -> sb.append('o') + 'á', 'à', 'ä', 'â', 'ã', 'å', 'Á', 'À', 'Ä', 'Â', 'Ã', 'Å' -> sb.append('a') + 'é', 'è', 'ë', 'ê', 'É', 'È', 'Ë', 'Ê' -> sb.append('e') + 'í', 'ì', 'ï', 'î', 'Í', 'Ì', 'Ï', 'Î' -> sb.append('i') + 'ú', 'ù', 'ü', 'û', 'Ú', 'Ù', 'Ü', 'Û' -> sb.append('u') + 'ñ', 'Ñ' -> sb.append('n') + 'ç', 'Ç' -> sb.append('c') + 'ß' -> sb.append("ss") + 'æ', 'Æ' -> sb.append("ae") + 'œ', 'Œ' -> sb.append("oe") + '&' -> sb.append(" and ") + else -> { + val lower = ch.lowercaseChar() + if (lower.isLetterOrDigit()) sb.append(lower) + else if (lower.isWhitespace() || lower == '/' || lower == '\\' || lower == '-' || lower == '_') { + if (sb.isNotEmpty() && sb.last() != ' ') sb.append(' ') + } + } + } + i++ + } + return sb.toString().trim() +} diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackSearchTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackSearchTest.kt new file mode 100644 index 00000000..9dbe3466 --- /dev/null +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackSearchTest.kt @@ -0,0 +1,34 @@ +package capital.yuri.yuriplayer.core.library + +import kotlin.test.Test +import kotlin.test.assertTrue + +class TrackSearchTest { + @Test + fun matchesStylizedTwentyOnePilots() { + val track = Track( + id = "1", + uri = "file:///music/Twenty Øne Piløts/Clancy/01.flac", + title = "Overcompensate", + artist = "Twenty Øne Piløts", + album = "Clancy", + path = "/home/mey/Music/Twenty One Pilots/Clancy/01.flac" + ) + assertTrue(track.matchesQuery("twenty one pilots")) + assertTrue(track.matchesQuery("Twenty Øne Piløts")) + assertTrue(track.matchesQuery("clancy")) + assertTrue("Twenty Øne Piløts".matchesSearch("twenty one pilots")) + } + + @Test + fun matchesFolderWhenTagsMissing() { + val track = Track( + id = "2", + uri = "file:///data/Twenty One Pilots/Vessel/02.flac", + title = "02", + path = "/data/Twenty One Pilots/Vessel/02.flac" + ) + assertTrue(track.matchesQuery("twenty one pilots")) + assertTrue(track.matchesQuery("vessel")) + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt index b61a4fef..c165486e 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt @@ -44,6 +44,8 @@ import capital.yuri.yuriplayer.components.model.albums import capital.yuri.yuriplayer.components.model.toRow import capital.yuri.yuriplayer.core.library.LocalLibraryScanner import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.matchesQuery +import capital.yuri.yuriplayer.core.library.matchesSearch import capital.yuri.yuriplayer.desktop.DesktopPlaylist import capital.yuri.yuriplayer.desktop.DesktopSession import kotlinx.coroutines.Dispatchers @@ -106,53 +108,54 @@ fun DesktopExplore( sid in effective } val localMatches = withContext(Dispatchers.Default) { - indexed.filter { it.matches(q) }.take(SONG_HIT_LIMIT) + indexed.filter { it.matchesQuery(q) }.take(SONG_HIT_LIMIT) } if (query.trim() != q) return@LaunchedEffect - val remoteMatches = if (remoteIds == null || remoteIds.isNotEmpty()) { - withContext(Dispatchers.IO) { session.searchRemotes(q, remoteIds) } - } else { - emptyList() - } - if (query.trim() != q) return@LaunchedEffect - - val songs = (localMatches + remoteMatches).distinctBy { it.id }.take(SONG_HIT_LIMIT) - val foundAlbums = albums.filter { album -> - (album.title.contains(q, true) || album.artist.contains(q, true)) && - album.tracks.any { row -> - val t = tracks.firstOrNull { it.id == row.id } - val sid = t?.sourceId ?: LocalLibraryScanner.SOURCE_LOCAL - sid in effective + fun applyHits(songs: List) { + val unique = songs.distinctBy { it.id }.take(SONG_HIT_LIMIT) + songHits = unique + albumHits = albums.filter { album -> + album.title.matchesSearch(q) || + album.artist.matchesSearch(q) || + album.tracks.any { row -> unique.any { it.id == row.id } } || + indexed.any { + it.displayAlbum.equals(album.title, true) && it.matchesQuery(q) + } + }.take(ALBUM_HIT_LIMIT).ifEmpty { unique.albums().take(ALBUM_HIT_LIMIT) } + artistHits = indexed + .map { it.displayArtist } + .distinct() + .filter { it.matchesSearch(q) } + .take(ARTIST_HIT_LIMIT) + .map { name -> + val ofArtist = tracks.filter { it.displayArtist.equals(name, true) } + ArtistHit( + name = name, + artworkUri = ofArtist.firstNotNullOfOrNull { it.artworkUri }, + trackCount = ofArtist.size, + albumCount = ofArtist.map { it.displayAlbum }.distinct().size + ) } - }.take(ALBUM_HIT_LIMIT).ifEmpty { - songs.albums().take(ALBUM_HIT_LIMIT) - } - val foundArtists = songs - .map { it.displayArtist } - .distinct() - .filter { it.contains(q, true) } - .take(ARTIST_HIT_LIMIT) - .map { name -> - val ofArtist = tracks.filter { it.displayArtist.equals(name, true) } - ArtistHit( - name = name, - artworkUri = ofArtist.firstNotNullOfOrNull { it.artworkUri }, - trackCount = ofArtist.size, - albumCount = ofArtist.map { it.displayAlbum }.distinct().size - ) + playlistHits = if (includeLocal) { + playlists.filter { it.name.matchesSearch(q) }.take(12) + } else { + emptyList() } - val foundPlaylists = if (includeLocal) { - playlists.filter { it.name.contains(q, true) }.take(12) + busy = false + } + + applyHits(localMatches) + + val remoteMatches = if (remoteIds == null || remoteIds.isNotEmpty()) { + runCatching { + withContext(Dispatchers.IO) { session.searchRemotes(q, remoteIds) } + }.getOrDefault(emptyList()) } else { emptyList() } - - songHits = songs - albumHits = foundAlbums - artistHits = foundArtists - playlistHits = foundPlaylists - busy = false + if (query.trim() != q) return@LaunchedEffect + applyHits(localMatches + remoteMatches) } Column(Modifier.fillMaxSize()) { @@ -247,7 +250,11 @@ fun DesktopExplore( !busy && songHits.isEmpty() && albumHits.isEmpty() && artistHits.isEmpty() && playlistHits.isEmpty() -> { Text( - "No matches for \"${query.trim()}\".", + if (tracks.isEmpty()) { + "Nothing indexed yet — scan a folder from the globe menu or Settings → Library." + } else { + "No matches for \"${query.trim()}\"." + }, modifier = Modifier.padding(20.dp), color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) ) @@ -369,12 +376,6 @@ private data class ArtistHit( val albumCount: Int ) -private fun Track.matches(q: String): Boolean = - displayTitle.contains(q, true) || - displayArtist.contains(q, true) || - displayAlbum.contains(q, true) || - albumArtist.orEmpty().contains(q, true) - private fun toggleKey(key: String, allKeys: Set, selected: Set): Set { val effective = if (selected.isEmpty()) allKeys else selected val next = if (key in effective) effective - key else effective + key From be4eb912d95bfc8514841c1a44d1807b4d6d47e6 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:37:09 +0000 Subject: [PATCH 27/69] Keep home pins and My Stuff rows empty unless saved The 8-up grid no longer fills with recents or random library albums. Pins are liked songs plus actual pins; album/artist rows only use that same collection. --- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index ada18bf9..494f1f21 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -422,9 +422,9 @@ private fun HomeFeed( }.distinctBy { it.id } } val likedTracks = remember(liked, tracks) { tracks.filter { it.id in liked } } - val shortcuts = remember(pins, liked, recents, albums, playlists, tracks) { + val shortcuts = remember(pins, liked, likedTracks, albums, playlists, tracks) { buildHomeShortcuts( - pins, liked, likedTracks, recents, albums, playlists, tracks, + pins, liked, likedTracks, albums, playlists, tracks, onOpenAlbum, onOpenPlaylist, onOpenArtist, onPlayTracks ) } @@ -436,16 +436,21 @@ private fun HomeFeed( else -> "Good evening" } } - val stuffAlbums = remember(pins, liked, albums, tracks) { + val stuffAlbums = remember(pins, liked, albums) { val pinnedAlbumIds = pins.filter { it.kind == DesktopCollection.Kind.ALBUM }.map { it.id }.toSet() + val pinnedTitles = pins.filter { it.kind == DesktopCollection.Kind.ALBUM } + .map { it.title.lowercase() }.toSet() albums.filter { album -> album.id in pinnedAlbumIds || + album.title.lowercase() in pinnedTitles || album.tracks.any { it.id in liked } - }.ifEmpty { albums } + } } - val stuffArtists = remember(pins, albums) { + val stuffArtists = remember(pins, liked, albums, tracks) { val pinned = pins.filter { it.kind == DesktopCollection.Kind.ARTIST }.map { it.title } - pinned.ifEmpty { albums.map { it.artist }.distinct() } + val fromAlbums = stuffAlbums.map { it.artist } + val fromLiked = tracks.filter { it.id in liked }.map { it.displayArtist } + (pinned + fromAlbums + fromLiked).map { it.trim() }.filter { it.isNotEmpty() }.distinct() } Column( @@ -562,9 +567,16 @@ private fun HomeFeed( } Spacer(Modifier.height(24.dp)) } - if (albums.isEmpty() && playlists.isEmpty() && recents.isEmpty()) { + if (shortcuts.isEmpty() && stuffAlbums.isEmpty() && playlists.isEmpty() && recents.isEmpty()) { + Text( + "Pin albums, artists, or playlists from My Stuff and they’ll show up here.", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + modifier = Modifier.padding(top = 24.dp) + ) + } + if (albums.isEmpty() && playlists.isEmpty() && recents.isEmpty() && tracks.isEmpty()) { Text( - status.ifBlank { "Nothing in My Stuff yet. Pin albums from the library rail." }, + status.ifBlank { "Nothing in the library yet." }, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) } @@ -583,7 +595,6 @@ private fun buildHomeShortcuts( pins: List, liked: Set, likedTracks: List, - recents: List, albums: List, playlists: List, tracks: List, @@ -604,13 +615,14 @@ private fun buildHomeShortcuts( id = "liked", title = "Liked Songs", artworkUri = likedTracks.firstOrNull()?.artworkUri - ) { onPlayTracks(likedTracks.ifEmpty { tracks }, 0) } + ) { onPlayTracks(likedTracks, 0) } ) } for (pin in pins) { when (pin.kind) { DesktopCollection.Kind.ALBUM -> { val album = albums.firstOrNull { it.id == pin.id } + ?: albums.firstOrNull { it.title.equals(pin.title, true) } add( SpotifyPin(pin.id, pin.title, album?.artworkUri) { album?.let(onOpenAlbum) @@ -621,7 +633,7 @@ private fun buildHomeShortcuts( SpotifyPin( pin.id, pin.title, - tracks.firstOrNull { it.displayArtist == pin.title }?.artworkUri, + tracks.firstOrNull { it.displayArtist.equals(pin.title, true) }?.artworkUri, circular = true ) { onOpenArtist(pin.title) } ) @@ -629,7 +641,7 @@ private fun buildHomeShortcuts( val t = tracks.firstOrNull { it.id == pin.id } add( SpotifyPin(pin.id, pin.title, t?.artworkUri) { - t?.let { onPlayTracks(listOf(it) + tracks, 0) } + t?.let { onPlayTracks(listOf(it), 0) } } ) } @@ -643,21 +655,6 @@ private fun buildHomeShortcuts( } } } - recents.mapNotNull { t -> albums.firstOrNull { a -> a.tracks.any { it.id == t.id } } } - .distinctBy { it.id } - .forEach { album -> - add(SpotifyPin(album.id, album.title, album.artworkUri) { onOpenAlbum(album) }) - } - playlists.forEach { pl -> - add( - SpotifyPin("playlist:${pl.id}", pl.name, pl.artworkUri(tracks)) { - onOpenPlaylist(pl) - } - ) - } - albums.forEach { album -> - add(SpotifyPin(album.id, album.title, album.artworkUri) { onOpenAlbum(album) }) - } return out } From b6e017ab8a118a34697f35a9d00f4467472d8358 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 19:52:19 +0000 Subject: [PATCH 28/69] Desktop artist pages, player spacing, session shuffle/repeat Shuffle and repeat (off / all / one) live entirely in PlayerSession so engines only play audio. Repeat-all wraps; shuffle keeps the current track and restores original order. Seek bar sits below the transport with a gap. Artist pages follow the Spotify layout (banner, popular, you liked, discography chips, appears on, about). --- .../yuriplayer/components/album/AlbumPage.kt | 20 +- .../components/artist/ArtistPage.kt | 408 ++++++++++++++++++ .../yuriplayer/components/model/UiModels.kt | 56 +++ .../components/player/BottomPlayerBar.kt | 11 +- .../yuriplayer/core/player/PlayerSession.kt | 142 +++--- .../core/player/PlayerSessionTest.kt | 116 +++++ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 47 +- 7 files changed, 733 insertions(+), 67 deletions(-) create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt create mode 100644 core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt index b3ed784e..ec76153c 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt @@ -1,6 +1,7 @@ package capital.yuri.yuriplayer.components.album import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -47,6 +48,7 @@ fun AlbumPage( onTrack: (Int) -> Unit, onEdit: () -> Unit = {}, onEditTrack: (Int) -> Unit = {}, + onArtist: (String) -> Unit = {}, modifier: Modifier = Modifier ) { val coverColors by rememberCoverColors( @@ -64,11 +66,11 @@ fun AlbumPage( val widthClass = windowWidthClass(maxWidth) when (widthClass) { WindowWidthClass.Compact -> AlbumPageCompact( - album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack + album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist ) WindowWidthClass.Medium, WindowWidthClass.Expanded -> AlbumPageWide( - album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack + album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist ) } } @@ -83,7 +85,8 @@ private fun AlbumPageCompact( onPlay: () -> Unit, onTrack: (Int) -> Unit, onEdit: () -> Unit, - onEditTrack: (Int) -> Unit + onEditTrack: (Int) -> Unit, + onArtist: (String) -> Unit ) { Column(Modifier.fillMaxSize()) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -108,7 +111,7 @@ private fun AlbumPageCompact( .aspectRatio(1f), corner = 12.dp ) - AlbumMeta(album, playing, onPlay, Modifier.padding(20.dp)) + AlbumMeta(album, playing, onPlay, onArtist, Modifier.padding(20.dp)) TrackList(album, onTrack, onEditTrack) } } @@ -121,7 +124,8 @@ private fun AlbumPageWide( onPlay: () -> Unit, onTrack: (Int) -> Unit, onEdit: () -> Unit, - onEditTrack: (Int) -> Unit + onEditTrack: (Int) -> Unit, + onArtist: (String) -> Unit ) { Row(Modifier.fillMaxSize().padding(24.dp)) { Column( @@ -148,7 +152,7 @@ private fun AlbumPageWide( corner = 12.dp ) Spacer(Modifier.height(16.dp)) - AlbumMeta(album, playing, onPlay) + AlbumMeta(album, playing, onPlay, onArtist) } TrackList(album, onTrack, onEditTrack, modifier = Modifier.weight(1f)) } @@ -159,6 +163,7 @@ private fun AlbumMeta( album: AlbumPageModel, playing: Boolean, onPlay: () -> Unit, + onArtist: (String) -> Unit, modifier: Modifier = Modifier ) { Column(modifier) { @@ -171,7 +176,8 @@ private fun AlbumMeta( Text( album.artist, style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + modifier = Modifier.clickable { onArtist(album.artist) } ) val bits = buildList { album.year?.let { add(it.toString()) } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt new file mode 100644 index 00000000..a65cbcf5 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt @@ -0,0 +1,408 @@ +package capital.yuri.yuriplayer.components.artist + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreHoriz +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Shuffle +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +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 capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.AlbumCard +import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.list.formatTime +import capital.yuri.yuriplayer.components.model.AlbumPageModel +import capital.yuri.yuriplayer.components.model.ArtistPageModel +import capital.yuri.yuriplayer.components.model.TrackRowModel + +private enum class DiscoFilter { Popular, Albums, Singles } + +@Composable +fun ArtistPage( + artist: ArtistPageModel, + playing: Boolean, + onPlay: () -> Unit, + onShuffle: () -> Unit, + onTrack: (Int) -> Unit, + onOpenAlbum: (AlbumPageModel) -> Unit, + modifier: Modifier = Modifier +) { + val listState = rememberLazyListState() + val mini by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } + var disco by remember { mutableStateOf(DiscoFilter.Popular) } + val releases = remember(artist.discography, disco) { + when (disco) { + DiscoFilter.Popular -> artist.discography + DiscoFilter.Albums -> artist.discography.filter { it.releaseKind() == "Album" } + DiscoFilter.Singles -> artist.discography.filter { it.releaseKind() != "Album" } + } + } + + Box(modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) { + LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) { + item { + ArtistBanner(artist) + } + item { + Row( + Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + FilledIconButton( + onClick = onPlay, + modifier = Modifier.size(56.dp), + shape = CircleShape, + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Icon( + if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, + contentDescription = if (playing) "Pause" else "Play", + modifier = Modifier.size(32.dp) + ) + } + IconButton(onClick = onShuffle) { + Icon( + Icons.Default.Shuffle, + contentDescription = "Shuffle", + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) + ) + } + IconButton(onClick = {}) { + Icon( + Icons.Default.MoreHoriz, + contentDescription = "More", + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) + ) + } + } + } + item { + Row( + Modifier.fillMaxWidth().padding(horizontal = 24.dp), + horizontalArrangement = Arrangement.spacedBy(32.dp) + ) { + Column(Modifier.weight(1.2f)) { + Text( + "Popular", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = 8.dp) + ) + artist.popular.forEachIndexed { i, track -> + PopularRow( + index = i + 1, + track = track, + onClick = { onTrack(i) } + ) + } + } + if (artist.likedCount > 0) { + Column(Modifier.weight(0.8f)) { + Text( + "You Liked", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = 8.dp) + ) + Surface( + shape = RoundedCornerShape(8.dp), + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f), + modifier = Modifier.fillMaxWidth().clickable(onClick = onPlay) + ) { + Row( + Modifier.padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CoverArt( + model = artist.liked.firstOrNull()?.artworkUri ?: artist.artworkUri, + size = 72.dp, + corner = 36.dp, + contentScale = ContentScale.Crop + ) + Spacer(Modifier.width(14.dp)) + Column { + Text( + "${artist.likedCount} songs · ${artist.likedReleaseCount} releases", + fontWeight = FontWeight.SemiBold + ) + Text( + "By ${artist.name}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + } + } + } + } + } + } + item { + Row( + Modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, top = 28.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "Discography", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f) + ) + TextButton(onClick = {}) { Text("Show all") } + } + Row( + Modifier.padding(horizontal = 24.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + FilterChip( + selected = disco == DiscoFilter.Popular, + onClick = { disco = DiscoFilter.Popular }, + label = { Text("Popular releases") } + ) + FilterChip( + selected = disco == DiscoFilter.Albums, + onClick = { disco = DiscoFilter.Albums }, + label = { Text("Albums") } + ) + FilterChip( + selected = disco == DiscoFilter.Singles, + onClick = { disco = DiscoFilter.Singles }, + label = { Text("Singles and EPs") } + ) + } + Spacer(Modifier.height(12.dp)) + LazyRow( + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(releases, key = { it.id }) { album -> + AlbumCard( + album = album.copy(artist = "${album.year ?: ""} · ${album.releaseKind()}".trim(' ', '·')), + onClick = { onOpenAlbum(album) } + ) + } + } + } + if (artist.appearsOn.isNotEmpty()) { + item { + Text( + "Appears on", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 28.dp, bottom = 8.dp) + ) + LazyRow( + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(artist.appearsOn, key = { "ao-${it.id}" }) { album -> + AlbumCard(album = album, onClick = { onOpenAlbum(album) }) + } + } + } + } + item { + Text( + "About", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 28.dp, bottom = 8.dp) + ) + Surface( + modifier = Modifier + .padding(horizontal = 24.dp) + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)), + color = MaterialTheme.colorScheme.surfaceVariant + ) { + Box(Modifier.fillMaxWidth().height(220.dp)) { + CoverArt( + model = artist.artworkUri, + modifier = Modifier.fillMaxSize(), + corner = 0.dp, + contentScale = ContentScale.Crop + ) + Box( + Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + listOf(Color.Transparent, Color.Black.copy(alpha = 0.75f)) + ) + ) + ) + Text( + artist.about ?: artist.stats, + color = Color.White, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.align(Alignment.BottomStart).padding(16.dp) + ) + } + } + Spacer(Modifier.height(48.dp)) + } + } + if (mini) { + Row( + Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.95f)) + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + FilledIconButton( + onClick = onPlay, + modifier = Modifier.size(40.dp), + shape = CircleShape, + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Icon( + if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, + contentDescription = null + ) + } + Spacer(Modifier.width(12.dp)) + Text(artist.name, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleMedium) + } + } + } +} + +@Composable +private fun ArtistBanner(artist: ArtistPageModel) { + Box( + Modifier + .fillMaxWidth() + .height(340.dp) + ) { + CoverArt( + model = artist.bannerUri ?: artist.artworkUri, + modifier = Modifier.fillMaxSize(), + corner = 0.dp, + contentScale = ContentScale.Crop + ) + Box( + Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + 0f to Color.Black.copy(alpha = 0.15f), + 0.55f to Color.Transparent, + 1f to MaterialTheme.colorScheme.background + ) + ) + ) + Column( + Modifier + .align(Alignment.BottomStart) + .padding(horizontal = 28.dp, vertical = 20.dp) + ) { + Text( + artist.name, + color = Color.White, + fontWeight = FontWeight.Black, + fontSize = 56.sp, + lineHeight = 60.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + Text( + artist.stats, + color = Color.White.copy(alpha = 0.85f), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 6.dp) + ) + } + } +} + +@Composable +private fun PopularRow(index: Int, track: TrackRowModel, onClick: () -> Unit) { + Row( + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(4.dp)) + .clickable(onClick = onClick) + .padding(vertical = 6.dp, horizontal = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + index.toString(), + modifier = Modifier.width(28.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + CoverArt(model = track.artworkUri, size = 40.dp, corner = 4.dp) + Spacer(Modifier.width(12.dp)) + Text( + track.title, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Medium + ) + Text( + formatTime(track.durationMs ?: 0L), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } +} + +internal fun AlbumPageModel.releaseKind(): String = when { + tracks.size <= 3 -> "Single" + tracks.size <= 7 -> "EP" + else -> "Album" +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt index a5e5e77a..3aee68ce 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt @@ -1,6 +1,7 @@ package capital.yuri.yuriplayer.components.model import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.matchesSearch data class CoverRef( val id: String, @@ -30,6 +31,20 @@ data class AlbumPageModel( val tracks: List ) +data class ArtistPageModel( + val name: String, + val artworkUri: String?, + val bannerUri: String? = null, + val stats: String, + val about: String? = null, + val popular: List, + val liked: List, + val likedCount: Int, + val likedReleaseCount: Int, + val discography: List, + val appearsOn: List +) + fun Track.toRow(highlighted: Boolean = false) = TrackRowModel( id = id, title = displayTitle, @@ -65,3 +80,44 @@ fun List.albums(): List = ) } .sortedBy { it.title.lowercase() } + +fun List.artistPage( + name: String, + likedIds: Set = emptySet(), + recents: List = emptyList() +): ArtistPageModel { + val allAlbums = albums() + val discography = allAlbums.filter { it.artist.matchesSearch(name) } + .sortedByDescending { it.year ?: Int.MIN_VALUE } + val appearsOn = allAlbums.filter { album -> + !album.artist.matchesSearch(name) && + album.tracks.any { row -> + row.artist.matchesSearch(name) + } + }.sortedByDescending { it.year ?: Int.MIN_VALUE } + val ofArtist = filter { it.displayArtist.matchesSearch(name) || (it.albumArtist?.matchesSearch(name) == true) } + val recentRank = recents.mapIndexed { i, t -> t.id to i }.toMap() + val popular = ofArtist + .sortedWith( + compareBy { recentRank[it.id] ?: Int.MAX_VALUE } + .thenBy { it.displayAlbum } + .thenBy { it.trackNumber ?: Int.MAX_VALUE } + ) + .distinctBy { it.displayTitle.lowercase() } + .take(5) + .map { it.toRow() } + val likedTracks = ofArtist.filter { it.id in likedIds } + return ArtistPageModel( + name = name, + artworkUri = ofArtist.firstNotNullOfOrNull { it.artworkUri } ?: discography.firstOrNull()?.artworkUri, + bannerUri = discography.firstOrNull()?.artworkUri ?: ofArtist.firstNotNullOfOrNull { it.artworkUri }, + stats = "${discography.size} albums · ${ofArtist.size} tracks", + about = null, + popular = popular, + liked = likedTracks.take(8).map { it.toRow() }, + likedCount = likedTracks.size, + likedReleaseCount = likedTracks.map { it.displayAlbum }.distinct().size, + discography = discography, + appearsOn = appearsOn + ) +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt index 6c0eb611..c6cb876b 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt @@ -79,8 +79,8 @@ fun BottomPlayerBar( Box( Modifier .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 8.dp) - .height(72.dp) + .padding(horizontal = 12.dp, vertical = 10.dp) + .height(84.dp) ) { Row( Modifier.align(Alignment.CenterStart).widthIn(max = 360.dp), @@ -165,12 +165,17 @@ fun BottomPlayerBar( IconButton(onClick = onCycleRepeat, modifier = Modifier.size(36.dp)) { Icon( if (repeat == RepeatMode.ONE) Icons.Default.RepeatOne else Icons.Default.Repeat, - contentDescription = "Repeat", + contentDescription = when (repeat) { + RepeatMode.OFF -> "Repeat off" + RepeatMode.ALL -> "Repeat all" + RepeatMode.ONE -> "Repeat one" + }, modifier = Modifier.size(18.dp), tint = if (repeat != RepeatMode.OFF) accent else muted ) } } + Spacer(Modifier.height(8.dp)) Row( Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt index 3574aed6..a22bd9a9 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.asStateFlow /** * Host-side queue + now-playing. Engines only produce sound. + * Shuffle / repeat live here so every backend behaves the same. * Previous within [RESTART_WINDOW_MS] seeks to 0 (Spotify 3s rule). */ class PlayerSession( @@ -15,6 +16,9 @@ class PlayerSession( private val _queue = MutableStateFlow>(emptyList()) val queue: StateFlow> = _queue.asStateFlow() + /** Unshuffled insertion order. Playing [_queue] is a view of this. */ + private var linear: List = emptyList() + private val _index = MutableStateFlow(0) val index: StateFlow = _index.asStateFlow() @@ -42,30 +46,15 @@ class PlayerSession( engine.seekTo(0) engine.play() } - RepeatMode.ALL -> { - val q = _queue.value - if (q.isEmpty()) return - if (!engine.playPreparedNext()) { - val i = _index.value + 1 - if (i in q.indices) next() - else loadAt(0, 0L, play = true) - } - } - RepeatMode.OFF -> { - if (!engine.playPreparedNext()) next() - } + RepeatMode.ALL, RepeatMode.OFF -> advance(fromUser = false) } } override fun onAutoAdvanced() { - val q = _queue.value - val i = _index.value + 1 - if (i in q.indices) { - recordHistory(_current.value) - _index.value = i - _current.value = q[i] - warmSuccessor() - } + val target = peekNextIndex(fromUser = false) ?: return + recordHistory(_current.value) + applyIndex(target) + warmSuccessor() } } @@ -75,8 +64,15 @@ class PlayerSession( fun play(tracks: List, startIndex: Int = 0) { if (tracks.isEmpty()) return - _queue.value = tracks - loadAt(startIndex.coerceIn(tracks.indices), 0L, play = true) + linear = tracks + val start = startIndex.coerceIn(tracks.indices) + if (_shuffle.value) { + applyShuffledView(current = tracks[start]) + loadAt(0, 0L, play = true) + } else { + _queue.value = tracks + loadAt(start, 0L, play = true) + } } fun togglePlay() { @@ -92,20 +88,7 @@ class PlayerSession( fun stop() = engine.stop() - fun next() { - val q = _queue.value - if (q.isEmpty()) return - if (engine.playPreparedNext()) { - recordHistory(_current.value) - val i = (_index.value + 1).coerceAtMost(q.lastIndex) - _index.value = i - _current.value = q[i] - warmSuccessor() - return - } - val i = _index.value + 1 - if (i in q.indices) loadAt(i, 0L, play = true) - } + fun next() = advance(fromUser = true) fun previous() { val q = _queue.value @@ -115,28 +98,34 @@ class PlayerSession( return } val i = _index.value - 1 - if (i in q.indices) loadAt(i, 0L, play = true) - else engine.seekTo(0) + when { + i in q.indices -> loadAt(i, 0L, play = true) + _repeat.value == RepeatMode.ALL -> loadAt(q.lastIndex, 0L, play = true) + else -> engine.seekTo(0) + } } fun seekTo(positionMs: Long) = engine.seekTo(positionMs.coerceAtLeast(0)) fun toggleShuffle() { - _shuffle.value = !_shuffle.value - if (_shuffle.value) { - val cur = _current.value - val rest = _queue.value.filter { it.id != cur?.id }.shuffled() - if (cur != null) { - _queue.value = listOf(cur) + rest - _index.value = 0 - } else { - _queue.value = _queue.value.shuffled() - } + val turningOn = !_shuffle.value + _shuffle.value = turningOn + val cur = _current.value + if (turningOn) { + if (linear.isEmpty()) linear = _queue.value + applyShuffledView(current = cur) + } else { + val restored = linear.ifEmpty { _queue.value } + _queue.value = restored + _index.value = cur?.let { c -> restored.indexOfFirst { it.id == c.id } } + ?.takeIf { it >= 0 } ?: 0 } + warmSuccessor() } fun cycleRepeat() { _repeat.value = _repeat.value.next() + warmSuccessor() } fun setVolume(value: Float) { @@ -159,6 +148,10 @@ class PlayerSession( val i = (_index.value + 1).coerceIn(0, q.size) q.add(i, track) _queue.value = q + val lin = linear.toMutableList() + val linAt = _current.value?.let { c -> lin.indexOfFirst { it.id == c.id } } ?: lin.lastIndex + lin.add((linAt + 1).coerceIn(0, lin.size), track) + linear = lin warmSuccessor() } @@ -169,6 +162,7 @@ class PlayerSession( q.add(to, item) val currentId = _current.value?.id _queue.value = q + if (!_shuffle.value) linear = q if (currentId != null) { _index.value = q.indexOfFirst { it.id == currentId }.coerceAtLeast(0) } @@ -178,10 +172,12 @@ class PlayerSession( fun clearQueueKeepCurrent() { val cur = _current.value ?: run { _queue.value = emptyList() + linear = emptyList() _index.value = 0 return } _queue.value = listOf(cur) + linear = listOf(cur) _index.value = 0 warmSuccessor() } @@ -198,13 +194,51 @@ class PlayerSession( engine.release() } + private fun advance(fromUser: Boolean) { + val target = peekNextIndex(fromUser) ?: return + val q = _queue.value + val expected = q.getOrNull(target) ?: return + val sequential = target == _index.value + 1 + if (sequential && engine.hasPreparedNext() && engine.playPreparedNext()) { + recordHistory(_current.value) + applyIndex(target) + warmSuccessor() + return + } + loadAt(target, 0L, play = true) + } + + private fun peekNextIndex(fromUser: Boolean): Int? { + val q = _queue.value + if (q.isEmpty()) return null + val i = _index.value + 1 + if (i in q.indices) return i + if (_repeat.value == RepeatMode.ALL) return 0 + if (fromUser) return null + return null + } + + private fun applyShuffledView(current: Track?) { + val pool = linear.ifEmpty { _queue.value } + val rest = pool.filter { it.id != current?.id }.shuffled() + _queue.value = if (current != null) listOf(current) + rest else rest + _index.value = 0 + } + + private fun applyIndex(i: Int) { + val track = _queue.value.getOrNull(i) ?: return + _index.value = i + _current.value = track + } + private fun loadAt(i: Int, startMs: Long, play: Boolean) { val q = _queue.value val track = q.getOrNull(i) ?: return recordHistory(_current.value) _index.value = i _current.value = track - val next = q.getOrNull(i + 1) + val nextIdx = peekNextIndex(fromUser = false) + val next = nextIdx?.let { q.getOrNull(it) }?.takeIf { it.id != track.id } engine.load(track.toPlaybackMedia(), next?.toPlaybackMedia(), startMs) if (play) engine.play() else engine.pause() warmSuccessor() @@ -217,7 +251,15 @@ class PlayerSession( } private fun warmSuccessor() { - val next = _queue.value.getOrNull(_index.value + 1) ?: return + val nextIdx = peekNextIndex(fromUser = false) ?: run { + engine.setNext(null) + return + } + val next = _queue.value.getOrNull(nextIdx) ?: return + if (next.id == _current.value?.id) { + engine.setNext(null) + return + } engine.setNext(next.toPlaybackMedia()) engine.warmupNext() } diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt new file mode 100644 index 00000000..2f6fbb6e --- /dev/null +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt @@ -0,0 +1,116 @@ +package capital.yuri.yuriplayer.core.player + +import capital.yuri.yuriplayer.core.library.Track +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class PlayerSessionTest { + private fun tracks(n: Int) = (1..n).map { Track(id = "$it", uri = "file://$it", title = "t$it") } + + @Test + fun repeatAllWrapsToStart() { + val engine = FakeEngine() + val session = PlayerSession(engine) + session.play(tracks(3), 0) + session.cycleRepeat() // ALL + session.skipTo(2) + session.next() + assertEquals("1", session.current.value?.id) + } + + @Test + fun repeatOffDoesNotWrap() { + val engine = FakeEngine() + val session = PlayerSession(engine) + session.play(tracks(3), 2) + session.next() + assertEquals("3", session.current.value?.id) + } + + @Test + fun repeatOneRestartsOnEnded() { + val engine = FakeEngine() + val session = PlayerSession(engine) + session.play(tracks(3), 1) + session.cycleRepeat() + session.cycleRepeat() // ONE + engine.emitEnded() + assertEquals("2", session.current.value?.id) + assertEquals(0L, engine.lastSeek) + assertTrue(engine.played) + } + + @Test + fun shuffleKeepsCurrentThenRestores() { + val engine = FakeEngine() + val session = PlayerSession(engine) + val list = tracks(8) + session.play(list, 3) + assertEquals("4", session.current.value?.id) + session.toggleShuffle() + assertTrue(session.shuffle.value) + assertEquals("4", session.current.value?.id) + assertEquals("4", session.queue.value.first().id) + session.toggleShuffle() + assertFalse(session.shuffle.value) + assertEquals("4", session.current.value?.id) + assertEquals(list.map { it.id }, session.queue.value.map { it.id }) + } +} + +private class FakeEngine : PlaybackEngine { + private val _playing = MutableStateFlow(false) + override val isPlaying: StateFlow = _playing + private val _uri = MutableStateFlow(null) + override val currentUri: StateFlow = _uri + private val listeners = mutableListOf() + var lastSeek = -1L + var played = false + private var next: PlaybackMedia? = null + + override fun load(current: PlaybackMedia, successor: PlaybackMedia?, startPositionMs: Long) { + _uri.value = current.uri + next = successor + _playing.value = true + } + + override fun play() { + played = true + _playing.value = true + } + + override fun pause() { + _playing.value = false + } + + override fun stop() { + _playing.value = false + } + + override fun seekTo(positionMs: Long) { + lastSeek = positionMs + } + + override fun getPositionMs(): Long = 0 + override fun getDurationMs(): Long = 180_000 + override fun hasPreparedNext(): Boolean = next != null + override fun playPreparedNext(): Boolean = false + override fun setNext(item: PlaybackMedia?) { + next = item + } + + override fun release() {} + override fun addListener(listener: PlaybackEngine.Listener) { + listeners += listener + } + + override fun removeListener(listener: PlaybackEngine.Listener) { + listeners -= listener + } + + fun emitEnded() = listeners.forEach { it.onEnded() } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 494f1f21..1c43ee0a 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.album.AlbumPage import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.artist.ArtistPage import capital.yuri.yuriplayer.components.layout.DesktopBentoLayout import capital.yuri.yuriplayer.components.layout.DesktopNav import capital.yuri.yuriplayer.components.layout.LibraryFilter @@ -47,6 +48,7 @@ import capital.yuri.yuriplayer.components.layout.LibraryRailItem import capital.yuri.yuriplayer.components.list.AlbumCard import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.model.albums +import capital.yuri.yuriplayer.components.model.artistPage import capital.yuri.yuriplayer.components.model.toCover import capital.yuri.yuriplayer.components.model.toRow import capital.yuri.yuriplayer.components.player.BottomPlayerBar @@ -63,6 +65,7 @@ private sealed class Route { data object Search : Route() data class Album(val album: AlbumPageModel) : Route() data class Playlist(val id: String) : Route() + data class Artist(val name: String) : Route() } @Composable @@ -121,6 +124,7 @@ fun YuriDesktopApp(session: DesktopSession) { } fun openAlbum(album: AlbumPageModel) = push(Route.Album(album)) + fun openArtist(name: String) = push(Route.Artist(name)) val recency = remember(history) { history.mapIndexed { i, t -> t.id to i }.toMap() @@ -229,9 +233,9 @@ fun YuriDesktopApp(session: DesktopSession) { push(Route.Playlist(item.id.removePrefix("playlist:"))) } item.id.startsWith("artist:") -> { - query = item.title - push(Route.Search) + openArtist(item.id.removePrefix("artist:").ifBlank { item.title }) } + item.circular -> openArtist(item.title) else -> albums.firstOrNull { it.id == item.id }?.let { openAlbum(it) } } }, @@ -316,10 +320,7 @@ fun YuriDesktopApp(session: DesktopSession) { status = engineMessage ?: status, onOpenAlbum = ::openAlbum, onOpenPlaylist = { push(Route.Playlist(it.id)) }, - onOpenArtist = { name -> - query = name - push(Route.Search) - }, + onOpenArtist = ::openArtist, onPlayTracks = { list, i -> session.player.play(list, i) } ) Route.Search -> DesktopExplore( @@ -330,7 +331,7 @@ fun YuriDesktopApp(session: DesktopSession) { playlists = playlists, onOpenAlbum = ::openAlbum, onOpenPlaylist = { push(Route.Playlist(it.id)) }, - onOpenArtist = { name -> query = name }, + onOpenArtist = ::openArtist, onPlaySongs = { list, i -> session.player.play(list, i) } ) is Route.Album -> { @@ -355,7 +356,39 @@ fun YuriDesktopApp(session: DesktopSession) { onEdit = { editAlbum = live }, onEditTrack = { i -> albumTracks.getOrNull(i)?.let { editSong = it } + }, + onArtist = ::openArtist + ) + } + is Route.Artist -> { + val model = remember(r.name, tracks, liked, history) { + tracks.artistPage(r.name, liked, history) + } + val artistTracks = remember(r.name, tracks) { + tracks.filter { + it.displayArtist.equals(r.name, true) || + it.albumArtist.equals(r.name, true) } + } + ArtistPage( + artist = model, + playing = playing && artistTracks.any { it.id == current?.id }, + onPlay = { + if (artistTracks.isNotEmpty()) session.player.play(artistTracks, 0) + }, + onShuffle = { + if (artistTracks.isNotEmpty()) { + if (!shuffle) session.player.toggleShuffle() + session.player.play(artistTracks, 0) + } + }, + onTrack = { i -> + val popularIds = model.popular.map { it.id } + val list = popularIds.mapNotNull { id -> artistTracks.firstOrNull { it.id == id } } + .ifEmpty { artistTracks } + if (i in list.indices) session.player.play(list, i) + }, + onOpenAlbum = ::openAlbum ) } is Route.Playlist -> { From 96e5ead3956291a70d7a1f21566cd70bb790f23d Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 20:06:34 +0000 Subject: [PATCH 29/69] Load artist banners and bios from Wikipedia and AudioDB Desktop artist pages resolve the same remote sources as mobile: Wikipedia extract + TheAudioDB fanart/banner. Headers can be picked from those results, set from a local file, or cleared. The About card shows the fetched bio. --- .../components/artist/ArtistPage.kt | 52 +++- .../yuriplayer/core/artist/ArtistProfile.kt | 31 ++ .../core/artist/ArtistInfoClient.kt | 277 ++++++++++++++++++ .../core/artist/ArtistProfileStore.kt | 51 ++++ .../yuri/yuriplayer/desktop/DesktopSession.kt | 6 + .../desktop/ui/ArtistBannerPicker.kt | 126 ++++++++ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 49 +++- 7 files changed, 579 insertions(+), 13 deletions(-) create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfile.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfileStore.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt index a65cbcf5..df4df24d 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt @@ -27,6 +27,8 @@ import androidx.compose.material.icons.filled.MoreHoriz import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Shuffle +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.FilterChip import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon @@ -70,11 +72,15 @@ fun ArtistPage( onShuffle: () -> Unit, onTrack: (Int) -> Unit, onOpenAlbum: (AlbumPageModel) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onChangeHeader: () -> Unit = {}, + onFetchHeader: () -> Unit = {}, + onClearHeader: () -> Unit = {} ) { val listState = rememberLazyListState() val mini by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } var disco by remember { mutableStateOf(DiscoFilter.Popular) } + var more by remember { mutableStateOf(false) } val releases = remember(artist.discography, disco) { when (disco) { DiscoFilter.Popular -> artist.discography @@ -116,13 +122,27 @@ fun ArtistPage( tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) ) } - IconButton(onClick = {}) { + IconButton(onClick = { more = true }) { Icon( Icons.Default.MoreHoriz, contentDescription = "More", tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) ) } + DropdownMenu(expanded = more, onDismissRequest = { more = false }) { + DropdownMenuItem( + text = { Text("Change header") }, + onClick = { more = false; onChangeHeader() } + ) + DropdownMenuItem( + text = { Text("Choose from sources") }, + onClick = { more = false; onFetchHeader() } + ) + DropdownMenuItem( + text = { Text("Clear header") }, + onClick = { more = false; onClearHeader() } + ) + } } } item { @@ -264,7 +284,7 @@ fun ArtistPage( .clip(RoundedCornerShape(12.dp)), color = MaterialTheme.colorScheme.surfaceVariant ) { - Box(Modifier.fillMaxWidth().height(220.dp)) { + Box(Modifier.fillMaxWidth().height(280.dp)) { CoverArt( model = artist.artworkUri, modifier = Modifier.fillMaxSize(), @@ -276,16 +296,28 @@ fun ArtistPage( .fillMaxSize() .background( Brush.verticalGradient( - listOf(Color.Transparent, Color.Black.copy(alpha = 0.75f)) + listOf(Color.Transparent, Color.Black.copy(alpha = 0.82f)) ) ) ) - Text( - artist.about ?: artist.stats, - color = Color.White, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.align(Alignment.BottomStart).padding(16.dp) - ) + Column( + Modifier.align(Alignment.BottomStart).padding(16.dp) + ) { + Text( + artist.stats, + color = Color.White.copy(alpha = 0.8f), + style = MaterialTheme.typography.labelMedium, + modifier = Modifier.padding(bottom = 8.dp) + ) + Text( + artist.about?.takeIf { it.isNotBlank() } + ?: "No bio yet — we’ll fill this in from Wikipedia and TheAudioDB.", + color = Color.White, + style = MaterialTheme.typography.bodyMedium, + maxLines = 8, + overflow = TextOverflow.Ellipsis + ) + } } } Spacer(Modifier.height(48.dp)) diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfile.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfile.kt new file mode 100644 index 00000000..a1175619 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfile.kt @@ -0,0 +1,31 @@ +package capital.yuri.yuriplayer.core.artist + +import kotlinx.serialization.Serializable + +enum class ArtistImageKind { PROFILE, BANNER } + +@Serializable +data class ArtistImageCandidate( + val url: String, + val sourceId: String, + val label: String = sourceId, + val width: Int? = null, + val height: Int? = null +) + +@Serializable +data class ArtistProfile( + val artistKey: String, + val displayName: String, + val bio: String? = null, + val imageUri: String? = null, + val bannerUri: String? = null, + val source: String = "local", + val updatedAtMs: Long = 0L, + val bannerCleared: Boolean = false +) + +fun artistKey(name: String?): String? { + val t = name?.trim()?.lowercase()?.replace(Regex("\\s+"), " ") ?: return null + return t.takeIf { it.isNotEmpty() } +} diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt new file mode 100644 index 00000000..bd5feb2f --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt @@ -0,0 +1,277 @@ +package capital.yuri.yuriplayer.core.artist + +import capital.yuri.yuriplayer.core.http.url +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.statement.bodyAsBytes +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpHeaders +import io.ktor.http.isSuccess +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +/** + * Wikipedia + TheAudioDB — same sources mobile uses for bios and banners. + */ +class ArtistInfoClient( + private val http: HttpClient, + private val store: ArtistProfileStore +) { + private val json = Json { ignoreUnknownKeys = true; isLenient = true } + + suspend fun resolve(name: String, force: Boolean = false): ArtistProfile = withContext(Dispatchers.IO) { + val cached = store.load(name) + val fresh = cached != null && + !force && + (System.currentTimeMillis() - cached.updatedAtMs) < CACHE_TTL_MS && + (!cached.bio.isNullOrBlank() || !cached.bannerUri.isNullOrBlank() || cached.bannerCleared) + if (fresh && cached != null) return@withContext cached + + val remote = fetchRemote(name) + val merged = merge(name, cached, remote) + store.save(merged) + merged + } + + suspend fun bannerCandidates(name: String): List = withContext(Dispatchers.IO) { + coroutineScope { + val wiki = async { wikipediaImages(name) } + val adb = async { audioDbImages(name) } + (adb.await() + wiki.await()).distinctBy { it.url } + } + } + + suspend fun applyBannerUrl(name: String, url: String): ArtistProfile { + val bytes = getBytes(url) ?: error("Couldn't download image") + val uri = store.writeBannerBytes(name, bytes) ?: error("Couldn't save banner") + val current = store.load(name) ?: ArtistProfile( + artistKey = artistKey(name) ?: name.lowercase(), + displayName = name + ) + val next = current.copy( + bannerUri = uri, + bannerCleared = false, + source = listOf(current.source, "user").filter { it.isNotBlank() }.distinct().joinToString(","), + updatedAtMs = System.currentTimeMillis() + ) + store.save(next) + return next + } + + fun applyLocalBanner(name: String, file: java.io.File): ArtistProfile { + val uri = store.setLocalBanner(name, file) ?: error("Couldn't save banner") + val current = store.load(name) ?: ArtistProfile( + artistKey = artistKey(name) ?: name.lowercase(), + displayName = name + ) + val next = current.copy( + bannerUri = uri, + bannerCleared = false, + source = "user", + updatedAtMs = System.currentTimeMillis() + ) + store.save(next) + return next + } + + fun clearBanner(name: String): ArtistProfile { + store.clearBanner(name) + val current = store.load(name) ?: ArtistProfile( + artistKey = artistKey(name) ?: name.lowercase(), + displayName = name + ) + val next = current.copy( + bannerUri = null, + bannerCleared = true, + updatedAtMs = System.currentTimeMillis() + ) + store.save(next) + return next + } + + private suspend fun fetchRemote(name: String): ArtistProfile { + return coroutineScope { + val wiki = async { wikipediaProfile(name) } + val adb = async { audioDbProfile(name) } + merge(name, wiki.await(), adb.await()) + } + } + + private fun merge(name: String, a: ArtistProfile?, b: ArtistProfile?): ArtistProfile { + val key = artistKey(name) ?: name.lowercase() + if (a == null && b == null) { + return ArtistProfile(artistKey = key, displayName = name.trim(), updatedAtMs = System.currentTimeMillis()) + } + if (a == null) return b!!.copy(updatedAtMs = System.currentTimeMillis()) + if (b == null) return a.copy(updatedAtMs = System.currentTimeMillis()) + return a.copy( + displayName = a.displayName.ifBlank { b.displayName }, + bio = preferBio(a.bio, b.bio), + imageUri = a.imageUri ?: b.imageUri, + bannerUri = when { + a.bannerCleared -> null + !a.bannerUri.isNullOrBlank() -> a.bannerUri + else -> b.bannerUri + }, + source = listOf(a.source, b.source).filter { it.isNotBlank() }.distinct().joinToString(","), + bannerCleared = a.bannerCleared, + updatedAtMs = System.currentTimeMillis() + ) + } + + private fun preferBio(a: String?, b: String?): String? { + val aa = a?.trim()?.takeIf { it.isNotBlank() } + val bb = b?.trim()?.takeIf { it.isNotBlank() } + if (aa == null) return bb + if (bb == null) return aa + return if (aa.length >= bb.length) aa else bb + } + + private suspend fun wikipediaProfile(name: String): ArtistProfile? { + val summary = wikipediaSummary(name) ?: return null + val title = summary.str("title") ?: name + val extract = summary.str("extract")?.takeIf { it.isNotBlank() } ?: return null + if (isWork(summary.str("description"), extract)) return null + val image = summary.obj("originalimage")?.str("source") + ?: summary.obj("thumbnail")?.str("source") + val banner = summary.obj("originalimage")?.let { img -> + val w = img.int("width") ?: 0 + val h = img.int("height") ?: 0 + if (w >= h && w >= 800) img.str("source") else null + } + return ArtistProfile( + artistKey = artistKey(name) ?: name.lowercase(), + displayName = title, + bio = extract, + imageUri = image, + bannerUri = banner, + source = "wikipedia" + ) + } + + private suspend fun wikipediaImages(name: String): List { + val summary = wikipediaSummary(name) ?: return emptyList() + val title = summary.str("title") ?: name + val out = ArrayList() + summary.obj("originalimage")?.str("source")?.let { + out += ArtistImageCandidate(it, "wikipedia", "Wikipedia · $title", summary.obj("originalimage")?.int("width"), summary.obj("originalimage")?.int("height")) + } + summary.obj("thumbnail")?.str("source")?.let { + out += ArtistImageCandidate(it, "wikipedia", "Wikipedia thumb · $title") + } + return out + } + + private suspend fun wikipediaSummary(name: String): JsonObject? { + val titles = listOf( + name.trim(), + "${name.trim()} (band)", + "${name.trim()} (musician)", + "${name.trim()} (duo)" + ) + for (title in titles) { + val requestUrl = url("https://en.wikipedia.org") { + path("api", "rest_v1", "page", "summary", title.replace(' ', '_'), encodeSlash = true) + } + val body = getText(requestUrl) ?: continue + val root = runCatching { json.parseToJsonElement(body).jsonObject }.getOrNull() ?: continue + if (root.str("type") == "disambiguation") continue + val desc = root.str("description").orEmpty() + val extract = root.str("extract").orEmpty() + if (isWork(desc, extract)) continue + if (root.str("title").isNullOrBlank()) continue + return root + } + return null + } + + private suspend fun audioDbProfile(name: String): ArtistProfile? { + val a = audioDbArtist(name) ?: return null + val bio = a.str("strBiographyEN")?.takeIf { it.isNotBlank() && it != "null" } + val banner = listOf("strArtistFanart", "strArtistFanart2", "strArtistBanner") + .firstNotNullOfOrNull { field -> a.str(field)?.takeIf { it.isNotBlank() && it != "null" } } + val thumb = a.str("strArtistThumb")?.takeIf { it.isNotBlank() && it != "null" } + return ArtistProfile( + artistKey = artistKey(name) ?: name.lowercase(), + displayName = a.str("strArtist")?.ifBlank { name } ?: name, + bio = bio, + imageUri = thumb, + bannerUri = banner, + source = "theaudiodb" + ) + } + + private suspend fun audioDbImages(name: String): List { + val a = audioDbArtist(name) ?: return emptyList() + val display = a.str("strArtist") ?: name + val fields = listOf( + "strArtistFanart" to "AudioDB fanart", + "strArtistFanart2" to "AudioDB fanart 2", + "strArtistFanart3" to "AudioDB fanart 3", + "strArtistBanner" to "AudioDB banner", + "strArtistWideThumb" to "AudioDB wide", + "strArtistThumb" to "AudioDB thumb" + ) + return fields.mapNotNull { (field, label) -> + val u = a.str(field)?.takeIf { it.isNotBlank() && it != "null" } ?: return@mapNotNull null + ArtistImageCandidate(u, "theaudiodb", "$label · $display") + } + } + + private suspend fun audioDbArtist(name: String): JsonObject? { + val requestUrl = url("https://www.theaudiodb.com") { + path("api", "v1", "json", "2", "search.php") + param("s", name.trim()) + } + val body = getText(requestUrl) ?: return null + val root = runCatching { json.parseToJsonElement(body).jsonObject }.getOrNull() ?: return null + return root["artists"]?.jsonArray?.firstOrNull()?.jsonObject + } + + private fun isWork(description: String?, extract: String): Boolean { + val d = (description.orEmpty() + " " + extract.take(280)).lowercase() + val work = listOf(" album", " song by", " single by", " ep by", " soundtrack", " mixtape") + val artist = listOf("musician", "singer", "rapper", "band", "duo", "group", "artist", "dj") + if (work.any { d.contains(it) } && artist.none { d.contains(it) }) return true + return false + } + + private suspend fun getText(url: String): String? = runCatching { + val response = http.get(url) { + header(HttpHeaders.UserAgent, USER_AGENT) + header(HttpHeaders.Accept, "application/json") + } + if (response.status.isSuccess()) response.bodyAsText() else null + }.getOrNull() + + private suspend fun getBytes(url: String): ByteArray? = runCatching { + val response = http.get(url) { + header(HttpHeaders.UserAgent, USER_AGENT) + } + if (response.status.isSuccess()) response.bodyAsBytes() else null + }.getOrNull() + + private fun JsonObject.str(key: String): String? = + this[key]?.jsonPrimitive?.contentOrNull?.takeIf { it.isNotBlank() } + + private fun JsonObject.int(key: String): Int? = + this[key]?.jsonPrimitive?.contentOrNull?.toIntOrNull() + + private fun JsonObject.obj(key: String): JsonObject? = + this[key]?.jsonObject + + companion object { + private const val CACHE_TTL_MS = 14L * 24 * 60 * 60 * 1000 + private const val USER_AGENT = + "YuriPlayer/1.0 (desktop; https://github.com/LizAinslie/yuriplayer)" + } +} diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfileStore.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfileStore.kt new file mode 100644 index 00000000..eb5cae04 --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfileStore.kt @@ -0,0 +1,51 @@ +package capital.yuri.yuriplayer.core.artist + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File + +class ArtistProfileStore(configDir: String, cacheDir: String) { + private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true } + private val dir = File(configDir, "artists").also { it.mkdirs() } + private val bannerDir = File(cacheDir, "artist-banners").also { it.mkdirs() } + + fun load(name: String): ArtistProfile? { + val key = artistKey(name) ?: return null + val file = File(dir, "$key.json") + if (!file.isFile) return null + return runCatching { json.decodeFromString(file.readText()) }.getOrNull() + } + + fun save(profile: ArtistProfile) { + runCatching { + File(dir, "${profile.artistKey}.json").writeText(json.encodeToString(profile)) + } + } + + fun bannerFile(name: String): File? { + val key = artistKey(name) ?: return null + return File(bannerDir, key) + } + + fun setLocalBanner(name: String, file: File): String? { + val key = artistKey(name) ?: return null + val dest = File(bannerDir, key) + return runCatching { + file.copyTo(dest, overwrite = true) + dest.toURI().toString() + }.getOrNull() + } + + fun writeBannerBytes(name: String, bytes: ByteArray): String? { + val key = artistKey(name) ?: return null + val dest = File(bannerDir, key) + return runCatching { + dest.writeBytes(bytes) + dest.toURI().toString() + }.getOrNull() + } + + fun clearBanner(name: String) { + bannerFile(name)?.delete() + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 4024ac4a..3a03a3bd 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -1,5 +1,7 @@ package capital.yuri.yuriplayer.desktop +import capital.yuri.yuriplayer.core.artist.ArtistInfoClient +import capital.yuri.yuriplayer.core.artist.ArtistProfileStore import capital.yuri.yuriplayer.core.library.CoverPixels import capital.yuri.yuriplayer.core.library.LocalLibraryScanner import capital.yuri.yuriplayer.core.library.Track @@ -89,6 +91,10 @@ class DesktopSession { val playlists = DesktopPlaylistStore(dirs.configDir) val sources = LibrarySourceStore(dirs.configDir) private val index = DesktopIndexStore(dirs.cacheDir) + val artists = ArtistInfoClient( + http, + ArtistProfileStore(dirs.configDir, dirs.cacheDir) + ) val jellyfin = JellyfinCatalog(http, sources.deviceId) val subsonic = SubsonicCatalog(http) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt new file mode 100644 index 00000000..19f16f86 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt @@ -0,0 +1,126 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.core.artist.ArtistImageCandidate +import capital.yuri.yuriplayer.core.artist.ArtistInfoClient + +@Composable +fun ArtistBannerPicker( + artistName: String, + client: ArtistInfoClient, + onDismiss: () -> Unit, + onPicked: (ArtistImageCandidate) -> Unit +) { + var loading by remember { mutableStateOf(true) } + var candidates by remember { mutableStateOf>(emptyList()) } + var error by remember { mutableStateOf(null) } + + LaunchedEffect(artistName) { + loading = true + error = null + candidates = runCatching { client.bannerCandidates(artistName) } + .onFailure { error = it.message } + .getOrDefault(emptyList()) + loading = false + } + + Dialog(onDismissRequest = onDismiss) { + Surface( + shape = RoundedCornerShape(16.dp), + tonalElevation = 6.dp, + modifier = Modifier.width(640.dp) + ) { + Column(Modifier.padding(20.dp)) { + Text("Choose a header", style = MaterialTheme.typography.titleLarge) + Text( + "Wikipedia and TheAudioDB · $artistName", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(top = 4.dp, bottom = 12.dp) + ) + when { + loading -> Box( + Modifier.fillMaxWidth().height(180.dp), + contentAlignment = Alignment.Center + ) { CircularProgressIndicator() } + error != null -> Text(error!!, color = MaterialTheme.colorScheme.error) + candidates.isEmpty() -> Text( + "Couldn't find headers for $artistName", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + else -> LazyVerticalGrid( + columns = GridCells.Fixed(3), + modifier = Modifier.height(360.dp), + contentPadding = PaddingValues(bottom = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(candidates, key = { it.url }) { c -> + Column { + CoverArt( + model = c.url, + modifier = Modifier + .fillMaxWidth() + .aspectRatio(16f / 9f) + .clip(RoundedCornerShape(8.dp)) + .border( + 1.dp, + MaterialTheme.colorScheme.outline.copy(alpha = 0.4f), + RoundedCornerShape(8.dp) + ) + .clickable { onPicked(c) } + .background(MaterialTheme.colorScheme.surfaceVariant), + corner = 8.dp, + contentScale = ContentScale.Crop + ) + Text( + c.label, + style = MaterialTheme.typography.labelSmall, + maxLines = 1, + modifier = Modifier.padding(top = 4.dp), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + } + } + } + } + TextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.End)) { + Text("Cancel") + } + } + } + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 1c43ee0a..e7b6d01d 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -27,10 +27,12 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -56,9 +58,11 @@ import capital.yuri.yuriplayer.components.player.NowPlayingSidebar import capital.yuri.yuriplayer.components.theme.PlayerChromeTheme import capital.yuri.yuriplayer.components.theme.YuriTheme import capital.yuri.yuriplayer.components.theme.playerColorsFromPixels +import capital.yuri.yuriplayer.core.artist.ArtistProfile import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.desktop.DesktopCollection import capital.yuri.yuriplayer.desktop.DesktopSession +import kotlinx.coroutines.launch private sealed class Route { data object Home : Route() @@ -104,6 +108,7 @@ fun YuriDesktopApp(session: DesktopSession) { var editAlbum by remember { mutableStateOf(null) } val route = stack.last() val nav = if (route is Route.Search) DesktopNav.Search else DesktopNav.Home + val scope = rememberCoroutineScope() fun push(next: Route) { stack = stack + next @@ -361,8 +366,22 @@ fun YuriDesktopApp(session: DesktopSession) { ) } is Route.Artist -> { - val model = remember(r.name, tracks, liked, history) { - tracks.artistPage(r.name, liked, history) + var profile by remember(r.name) { mutableStateOf(null) } + var pickBanner by remember { mutableStateOf(false) } + LaunchedEffect(r.name) { + profile = runCatching { session.artists.resolve(r.name) }.getOrNull() + } + val model = remember(r.name, tracks, liked, history, profile) { + val base = tracks.artistPage(r.name, liked, history) + base.copy( + about = profile?.bio ?: base.about, + artworkUri = profile?.imageUri ?: base.artworkUri, + bannerUri = when { + profile?.bannerCleared == true -> "" + !profile?.bannerUri.isNullOrBlank() -> profile!!.bannerUri + else -> base.bannerUri + } + ) } val artistTracks = remember(r.name, tracks) { tracks.filter { @@ -388,8 +407,32 @@ fun YuriDesktopApp(session: DesktopSession) { .ifEmpty { artistTracks } if (i in list.indices) session.player.play(list, i) }, - onOpenAlbum = ::openAlbum + onOpenAlbum = ::openAlbum, + onChangeHeader = { + DesktopFiles.pickImage("Choose header")?.let { file -> + profile = session.artists.applyLocalBanner(r.name, file) + } + }, + onFetchHeader = { pickBanner = true }, + onClearHeader = { + profile = session.artists.clearBanner(r.name) + } ) + if (pickBanner) { + ArtistBannerPicker( + artistName = r.name, + client = session.artists, + onDismiss = { pickBanner = false }, + onPicked = { c -> + pickBanner = false + scope.launch { + profile = runCatching { + session.artists.applyBannerUrl(r.name, c.url) + }.getOrNull() ?: profile + } + } + ) + } } is Route.Playlist -> { val pl = playlists.firstOrNull { it.id == r.id } From 314f8257597cc4ece2a08a35bfecd681a0856c8d Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 20:11:49 +0000 Subject: [PATCH 30/69] Wire mobile artist sources, My Stuff rail, playlist create Artist pages now merge Wikipedia, TheAudioDB, Deezer, MusicBrainz, Wikidata, plus Jellyfin/Navidrome art. About lists genres. The left rail is only My Stuff (pins, likes, playlists). New playlists get a name/description dialog and land in the collection. --- .kotlin/errors/errors-1787337886009.log | 3 + .kotlin/errors/errors-1787340227855.log | 3 + .kotlin/errors/errors-1787343069317.log | 3 + .../components/artist/ArtistPage.kt | 15 ++ .../yuriplayer/components/model/UiModels.kt | 3 +- .../yuriplayer/core/artist/ArtistProfile.kt | 1 + .../core/artist/ArtistInfoClient.kt | 204 +++++++++++++++++- .../yuriplayer/core/source/JellyfinCatalog.kt | 58 +++++ .../yuriplayer/core/source/SubsonicCatalog.kt | 34 ++- .../yuri/yuriplayer/desktop/DesktopSession.kt | 20 +- .../yuriplayer/desktop/ui/PlaylistPage.kt | 41 +++- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 110 +++++++--- 12 files changed, 450 insertions(+), 45 deletions(-) create mode 100644 .kotlin/errors/errors-1787337886009.log create mode 100644 .kotlin/errors/errors-1787340227855.log create mode 100644 .kotlin/errors/errors-1787343069317.log diff --git a/.kotlin/errors/errors-1787337886009.log b/.kotlin/errors/errors-1787337886009.log new file mode 100644 index 00000000..cf45d602 --- /dev/null +++ b/.kotlin/errors/errors-1787337886009.log @@ -0,0 +1,3 @@ +kotlin version: 2.3.21 +error message: Daemon compilation failed + diff --git a/.kotlin/errors/errors-1787340227855.log b/.kotlin/errors/errors-1787340227855.log new file mode 100644 index 00000000..cf45d602 --- /dev/null +++ b/.kotlin/errors/errors-1787340227855.log @@ -0,0 +1,3 @@ +kotlin version: 2.3.21 +error message: Daemon compilation failed + diff --git a/.kotlin/errors/errors-1787343069317.log b/.kotlin/errors/errors-1787343069317.log new file mode 100644 index 00000000..cf45d602 --- /dev/null +++ b/.kotlin/errors/errors-1787343069317.log @@ -0,0 +1,3 @@ +kotlin version: 2.3.21 +error message: Daemon compilation failed + diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt index df4df24d..7d2ac69c 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt @@ -3,6 +3,8 @@ package capital.yuri.yuriplayer.components.artist import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -27,6 +29,7 @@ import androidx.compose.material.icons.filled.MoreHoriz import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Shuffle +import androidx.compose.material3.AssistChip import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.FilterChip @@ -64,6 +67,7 @@ import capital.yuri.yuriplayer.components.model.TrackRowModel private enum class DiscoFilter { Popular, Albums, Singles } +@OptIn(ExperimentalLayoutApi::class) @Composable fun ArtistPage( artist: ArtistPageModel, @@ -303,6 +307,17 @@ fun ArtistPage( Column( Modifier.align(Alignment.BottomStart).padding(16.dp) ) { + if (artist.genres.isNotEmpty()) { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.padding(bottom = 10.dp) + ) { + artist.genres.take(8).forEach { g -> + AssistChip(onClick = {}, label = { Text(g) }) + } + } + } Text( artist.stats, color = Color.White.copy(alpha = 0.8f), diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt index 3aee68ce..90e5fe51 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt @@ -42,7 +42,8 @@ data class ArtistPageModel( val likedCount: Int, val likedReleaseCount: Int, val discography: List, - val appearsOn: List + val appearsOn: List, + val genres: List = emptyList() ) fun Track.toRow(highlighted: Boolean = false) = TrackRowModel( diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfile.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfile.kt index a1175619..aa680cd4 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfile.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfile.kt @@ -20,6 +20,7 @@ data class ArtistProfile( val bio: String? = null, val imageUri: String? = null, val bannerUri: String? = null, + val genres: List = emptyList(), val source: String = "local", val updatedAtMs: Long = 0L, val bannerCleared: Boolean = false diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt index bd5feb2f..4d2b058b 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt @@ -20,11 +20,13 @@ import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive /** - * Wikipedia + TheAudioDB — same sources mobile uses for bios and banners. + * Same public sources as mobile: Wikipedia, TheAudioDB, Deezer, MusicBrainz, Wikidata, + * plus library servers (Jellyfin / Navidrome) supplied by the host. */ class ArtistInfoClient( private val http: HttpClient, - private val store: ArtistProfileStore + private val store: ArtistProfileStore, + private val libraryImages: suspend (String) -> List = { emptyList() } ) { private val json = Json { ignoreUnknownKeys = true; isLenient = true } @@ -46,7 +48,12 @@ class ArtistInfoClient( coroutineScope { val wiki = async { wikipediaImages(name) } val adb = async { audioDbImages(name) } - (adb.await() + wiki.await()).distinctBy { it.url } + val deezer = async { deezerImages(name) } + val mb = async { musicBrainzImages(name) } + val wd = async { wikidataImages(name) } + val lib = async { runCatching { libraryImages(name) }.getOrDefault(emptyList()) } + (adb.await() + wiki.await() + deezer.await() + mb.await() + wd.await() + lib.await()) + .distinctBy { it.url } } } @@ -100,9 +107,18 @@ class ArtistInfoClient( private suspend fun fetchRemote(name: String): ArtistProfile { return coroutineScope { - val wiki = async { wikipediaProfile(name) } - val adb = async { audioDbProfile(name) } - merge(name, wiki.await(), adb.await()) + val parts = listOf( + async { wikipediaProfile(name) }, + async { audioDbProfile(name) }, + async { musicBrainzProfile(name) }, + async { wikidataProfile(name) } + ).map { runCatching { it.await() }.getOrNull() } + parts.fold(null as ArtistProfile?) { acc, p -> merge(name, acc, p) } + ?: ArtistProfile( + artistKey = artistKey(name) ?: name.lowercase(), + displayName = name.trim(), + updatedAtMs = System.currentTimeMillis() + ) } } @@ -122,6 +138,7 @@ class ArtistInfoClient( !a.bannerUri.isNullOrBlank() -> a.bannerUri else -> b.bannerUri }, + genres = (a.genres + b.genres).map { it.trim() }.filter { it.isNotEmpty() }.distinctBy { it.lowercase() }, source = listOf(a.source, b.source).filter { it.isNotBlank() }.distinct().joinToString(","), bannerCleared = a.bannerCleared, updatedAtMs = System.currentTimeMillis() @@ -200,12 +217,17 @@ class ArtistInfoClient( val banner = listOf("strArtistFanart", "strArtistFanart2", "strArtistBanner") .firstNotNullOfOrNull { field -> a.str(field)?.takeIf { it.isNotBlank() && it != "null" } } val thumb = a.str("strArtistThumb")?.takeIf { it.isNotBlank() && it != "null" } + val genres = buildList { + a.str("strGenre")?.takeIf { it != "null" }?.let { add(it) } + a.str("strStyle")?.takeIf { it != "null" }?.let { add(it) } + }.flatMap { it.split(',', '/', '|') }.map { it.trim() }.filter { it.isNotEmpty() }.distinctBy { it.lowercase() } return ArtistProfile( artistKey = artistKey(name) ?: name.lowercase(), displayName = a.str("strArtist")?.ifBlank { name } ?: name, bio = bio, imageUri = thumb, bannerUri = banner, + genres = genres, source = "theaudiodb" ) } @@ -237,6 +259,176 @@ class ArtistInfoClient( return root["artists"]?.jsonArray?.firstOrNull()?.jsonObject } + private suspend fun deezerImages(name: String): List { + val requestUrl = url("https://api.deezer.com") { + path("search", "artist") + param("q", name.trim()) + param("limit", 8) + } + val body = getText(requestUrl) ?: return emptyList() + val data = runCatching { json.parseToJsonElement(body).jsonObject["data"]?.jsonArray }.getOrNull() + ?: return emptyList() + val out = ArrayList() + for (el in data) { + val a = el.jsonObject + val artistName = a.str("name") ?: continue + val pic = listOf("picture_xl", "picture_big", "picture_medium") + .firstNotNullOfOrNull { a.str(it) } + ?.takeIf { !it.contains("artist-default") } ?: continue + out += ArtistImageCandidate(pic, "deezer", "Deezer · $artistName", 1000, 1000) + } + return out + } + + private suspend fun musicBrainzProfile(name: String): ArtistProfile? { + val searchUrl = url("https://musicbrainz.org") { + path("ws", "2", "artist") + param("query", "artist:\"${name.trim()}\"") + param("fmt", "json") + param("limit", 3) + } + val body = getText(searchUrl) ?: return null + val artists = runCatching { json.parseToJsonElement(body).jsonObject["artists"]?.jsonArray }.getOrNull() + ?: return null + val first = artists.firstOrNull()?.jsonObject ?: return null + val mbid = first.str("id") ?: return null + val display = first.str("name") ?: name + val genres = first["tags"]?.jsonArray.orEmpty().mapNotNull { + it.jsonObject.str("name") + }.take(8) + val detailUrl = url("https://musicbrainz.org") { + path("ws", "2", "artist", mbid) + param("inc", "url-rels+tags+genres") + param("fmt", "json") + } + val detailBody = getText(detailUrl) + val detail = detailBody?.let { runCatching { json.parseToJsonElement(it).jsonObject }.getOrNull() } + val moreGenres = detail?.get("genres")?.jsonArray.orEmpty().mapNotNull { it.jsonObject.str("name") } + val relations = detail?.get("relations")?.jsonArray + var wikiImage: String? = null + relations?.forEach { rel -> + val obj = rel.jsonObject + val type = obj.str("type").orEmpty() + val urlStr = obj.obj("url")?.str("resource") ?: return@forEach + if (type.contains("wikidata", true)) { + val qid = Regex("Q\\d+").find(urlStr)?.value + if (qid != null) wikiImage = wikidataP18(qid).firstOrNull() + } + } + return ArtistProfile( + artistKey = artistKey(name) ?: name.lowercase(), + displayName = display, + imageUri = wikiImage, + genres = (genres + moreGenres).distinctBy { it.lowercase() }, + source = "musicbrainz" + ) + } + + private suspend fun musicBrainzImages(name: String): List { + val p = musicBrainzProfile(name) ?: return emptyList() + val url = p.imageUri ?: return emptyList() + return listOf(ArtistImageCandidate(url, "musicbrainz", "MusicBrainz · ${p.displayName}")) + } + + private suspend fun wikidataProfile(name: String): ArtistProfile? { + val qid = wikidataSearch(name) ?: return null + val genres = wikidataGenres(qid) + val images = wikidataP18(qid) + return ArtistProfile( + artistKey = artistKey(name) ?: name.lowercase(), + displayName = name.trim(), + imageUri = images.firstOrNull(), + genres = genres, + source = "wikidata" + ) + } + + private suspend fun wikidataImages(name: String): List { + val qid = wikidataSearch(name) ?: return emptyList() + return wikidataP18(qid).mapIndexed { i, u -> + ArtistImageCandidate(u, "wikidata", "Wikidata P18${if (i > 0) " #${i + 1}" else ""} · $name") + } + } + + private suspend fun wikidataSearch(name: String): String? { + val requestUrl = url("https://www.wikidata.org") { + path("w", "api.php") + param("action", "wbsearchentities") + param("search", name.trim()) + param("language", "en") + param("type", "item") + param("limit", 8) + param("format", "json") + } + val body = getText(requestUrl) ?: return null + val search = runCatching { json.parseToJsonElement(body).jsonObject["search"]?.jsonArray }.getOrNull() + ?: return null + val hints = listOf("musician", "singer", "rapper", "band", "duo", "group", "artist", "dj") + val works = listOf("album", "song", "single", "ep", "soundtrack") + for (el in search) { + val o = el.jsonObject + val id = o.str("id") ?: continue + if (!id.startsWith("Q")) continue + val desc = o.str("description").orEmpty().lowercase() + if (works.any { desc.contains(it) } && hints.none { desc.contains(it) }) continue + return id + } + return search.firstOrNull()?.jsonObject?.str("id") + } + + private suspend fun wikidataP18(qid: String): List { + val claims = wikidataClaims(qid) ?: return emptyList() + val p18 = claims["P18"]?.jsonArray ?: return emptyList() + return p18.mapNotNull { claim -> + val file = claim.jsonObject.obj("mainsnak")?.obj("datavalue")?.str("value") ?: return@mapNotNull null + url("https://commons.wikimedia.org") { + path("wiki", "Special:FilePath", file.replace(' ', '_'), encodeSlash = true) + param("width", 1200) + } + } + } + + private suspend fun wikidataGenres(qid: String): List { + val claims = wikidataClaims(qid) ?: return emptyList() + val p136 = claims["P136"]?.jsonArray ?: return emptyList() + val ids = p136.mapNotNull { claim -> + claim.jsonObject.obj("mainsnak")?.obj("datavalue")?.obj("value")?.str("id") + }.take(10) + if (ids.isEmpty()) return emptyList() + val requestUrl = url("https://www.wikidata.org") { + path("w", "api.php") + param("action", "wbgetentities") + param("ids", ids.joinToString("|")) + param("props", "labels") + param("languages", "en") + param("format", "json") + } + val body = getText(requestUrl) ?: return emptyList() + val entities = runCatching { json.parseToJsonElement(body).jsonObject["entities"]?.jsonObject }.getOrNull() + ?: return emptyList() + return ids.mapNotNull { id -> + entities[id]?.jsonObject?.obj("labels")?.obj("en")?.str("value") + } + } + + private suspend fun wikidataClaims(qid: String): JsonObject? { + val requestUrl = url("https://www.wikidata.org") { + path("w", "api.php") + param("action", "wbgetentities") + param("ids", qid) + param("props", "claims") + param("format", "json") + } + val body = getText(requestUrl) ?: return null + return runCatching { + json.parseToJsonElement(body).jsonObject["entities"]?.jsonObject + ?.get(qid)?.jsonObject?.obj("claims") + }.getOrNull() + } + + private fun kotlinx.serialization.json.JsonArray?.orEmpty() = + this ?: kotlinx.serialization.json.JsonArray(emptyList()) + private fun isWork(description: String?, extract: String): Boolean { val d = (description.orEmpty() + " " + extract.take(280)).lowercase() val work = listOf(" album", " song by", " single by", " ep by", " soundtrack", " mixtape") diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt index 0a5fcaa2..b51d579c 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt @@ -142,6 +142,63 @@ class JellyfinCatalog( page.items.mapNotNull { toTrack(account.copy(baseUrl = root), it) } } + suspend fun searchArtistImages(account: RemoteAccount, query: String): Result> = + runCatching { + val token = account.accessToken ?: error("Not signed in") + val userId = account.userId ?: error("Not signed in") + val root = normalizeBaseUrl(account.baseUrl) + val requestUrl = url(root) { + path("Users", userId, "Items") + param("SearchTerm", query) + param("IncludeItemTypes", "MusicArtist") + param("Recursive", "true") + param("EnableImages", "true") + param("EnableImageTypes", "Primary,Backdrop") + param("ImageTypeLimit", "2") + param("Limit", 8) + } + val response = http.get(requestUrl) { + header("X-Emby-Authorization", authHeader(token)) + header("X-Emby-Token", token) + } + if (!response.status.isSuccess()) error("Search HTTP ${response.status.value}") + val page = json.decodeFromString(response.bodyAsText()) + val out = ArrayList() + for (item in page.items) { + val id = item.id ?: continue + val name = item.name ?: continue + if (item.imageTags?.containsKey("Primary") == true) { + out += capital.yuri.yuriplayer.core.artist.ArtistImageCandidate( + url = url(root) { + path("Items", id, "Images", "Primary") + param("maxWidth", 800) + param("quality", 90) + param("api_key", token) + }, + sourceId = "jellyfin", + label = "Jellyfin · ${account.name} · $name", + width = 800, + height = 800 + ) + } + if (!item.backdropTags.isNullOrEmpty() || item.imageTags?.containsKey("Backdrop") == true) { + out += capital.yuri.yuriplayer.core.artist.ArtistImageCandidate( + url = url(root) { + path("Items", id, "Images", "Backdrop") + param("maxWidth", 1920) + param("quality", 90) + param("api_key", token) + }, + sourceId = "jellyfin", + label = "Jellyfin backdrop · ${account.name} · $name", + width = 1920, + height = 1080 + ) + } + } + out + } + private fun toTrack(account: RemoteAccount, item: JfItem): Track? { val id = item.id ?: return null val token = account.accessToken ?: return null @@ -223,6 +280,7 @@ class JellyfinCatalog( @SerialName("ProductionYear") val year: Int? = null, @SerialName("AlbumId") val albumId: String? = null, @SerialName("ImageTags") val imageTags: Map? = null, + @SerialName("BackdropImageTags") val backdropTags: List? = null, @SerialName("Container") val container: String? = null ) } diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt index bbb4a532..67245527 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt @@ -163,6 +163,30 @@ class SubsonicCatalog( } } + suspend fun searchArtistImages(account: RemoteAccount, query: String): Result> = + runCatching { + val session = sessionOf(account) + val body = apiGet(session, "search3") { + param("query", query) + param("songCount", 0) + param("albumCount", 0) + param("artistCount", 12) + } + val artists = json.decodeFromString(body) + .subsonicResponse.searchResult3?.artist.orEmpty() + artists.mapNotNull { a -> + val cover = a.coverArt ?: return@mapNotNull null + val name = a.name ?: return@mapNotNull null + capital.yuri.yuriplayer.core.artist.ArtistImageCandidate( + url = coverUrl(session, cover, 1200) ?: return@mapNotNull null, + sourceId = "navidrome", + label = "Navidrome · ${account.name} · $name", + width = 1200, + height = 1200 + ) + } + } + fun streamUrl(session: Session, id: String): String = restUrl(session, "stream") { param("id", id) @@ -238,7 +262,15 @@ class SubsonicCatalog( @Serializable private data class SubsonicSearchResult( - val song: List = emptyList() + val song: List = emptyList(), + val artist: List = emptyList() + ) + + @Serializable + private data class SubsonicArtistRef( + val id: String? = null, + val name: String? = null, + val coverArt: String? = null ) @Serializable diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 3a03a3bd..154567e1 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -1,5 +1,6 @@ package capital.yuri.yuriplayer.desktop +import capital.yuri.yuriplayer.core.artist.ArtistImageCandidate import capital.yuri.yuriplayer.core.artist.ArtistInfoClient import capital.yuri.yuriplayer.core.artist.ArtistProfileStore import capital.yuri.yuriplayer.core.library.CoverPixels @@ -91,12 +92,12 @@ class DesktopSession { val playlists = DesktopPlaylistStore(dirs.configDir) val sources = LibrarySourceStore(dirs.configDir) private val index = DesktopIndexStore(dirs.cacheDir) + val jellyfin = JellyfinCatalog(http, sources.deviceId) + val subsonic = SubsonicCatalog(http) val artists = ArtistInfoClient( http, ArtistProfileStore(dirs.configDir, dirs.cacheDir) - ) - val jellyfin = JellyfinCatalog(http, sources.deviceId) - val subsonic = SubsonicCatalog(http) + ) { name -> libraryArtistImages(name) } private var ticker: Job? = null private var scanJob: Job? = null @@ -737,6 +738,19 @@ class DesktopSession { fun extraScan(path: String) = addFolder(path) + private suspend fun libraryArtistImages(name: String): List { + val out = ArrayList() + for (remote in sources.remotes.value.filter { it.enabled }) { + val result = when (remote.kind) { + SourceKind.JELLYFIN -> jellyfin.searchArtistImages(remote, name) + SourceKind.SUBSONIC -> subsonic.searchArtistImages(remote, name) + SourceKind.LOCAL -> continue + } + result.onSuccess { out += it } + } + return out + } + suspend fun searchRemotes(query: String, sourceIds: Set?): List { val q = query.trim() if (q.isEmpty()) return emptyList() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt index 4690b89b..4fb689a8 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -48,6 +48,7 @@ import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.toRow import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.matchesQuery import capital.yuri.yuriplayer.desktop.DesktopCover import capital.yuri.yuriplayer.desktop.DesktopPlaylist import capital.yuri.yuriplayer.desktop.DesktopPlaylistStore @@ -223,9 +224,7 @@ private fun AddSongsDialog( val hits = remember(query, library) { val q = query.trim() if (q.isEmpty()) library.take(40) - else library.filter { - it.displayTitle.contains(q, true) || it.displayArtist.contains(q, true) - }.take(40) + else library.filter { it.matchesQuery(q) }.take(40) } DialogWindow(onCloseRequest = onDismiss, title = "Add songs") { Surface(Modifier.size(520.dp, 560.dp)) { @@ -357,3 +356,39 @@ private fun AddCoverChip(label: String, onClick: () -> Unit) { Text(label, style = MaterialTheme.typography.labelMedium) } } + +@Composable +fun NewPlaylistDialog( + onDismiss: () -> Unit, + onCreate: (name: String, description: String?) -> Unit +) { + var name by remember { mutableStateOf("") } + var description by remember { mutableStateOf("") } + DialogWindow(onCloseRequest = onDismiss, title = "New playlist") { + Surface(Modifier.size(420.dp, 280.dp)) { + Column(Modifier.padding(20.dp)) { + Text("New playlist", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + OutlinedTextField( + value = name, + onValueChange = { name = it }, + label = { Text("Name") }, + singleLine = true, + modifier = Modifier.fillMaxWidth().padding(top = 12.dp) + ) + OutlinedTextField( + value = description, + onValueChange = { description = it }, + label = { Text("Description") }, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) + ) + Row(Modifier.fillMaxWidth().padding(top = 16.dp), horizontalArrangement = Arrangement.End) { + TextButton(onClick = onDismiss) { Text("Cancel") } + TextButton( + onClick = { onCreate(name, description.takeIf { it.isNotBlank() }) }, + enabled = name.isNotBlank() + ) { Text("Create") } + } + } + } + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index e7b6d01d..a0ccfa47 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -101,8 +101,9 @@ fun YuriDesktopApp(session: DesktopSession) { var stack by remember { mutableStateOf(listOf(Route.Home)) } var forward by remember { mutableStateOf(listOf()) } var query by remember { mutableStateOf("") } - var libraryFilter by remember { mutableStateOf(LibraryFilter.Recents) } + var libraryFilter by remember { mutableStateOf(LibraryFilter.Playlists) } var showSettings by remember { mutableStateOf(false) } + var showNewPlaylist by remember { mutableStateOf(false) } var showSidebar by remember { mutableStateOf(true) } var editSong by remember { mutableStateOf(null) } var editAlbum by remember { mutableStateOf(null) } @@ -138,10 +139,13 @@ fun YuriDesktopApp(session: DesktopSession) { album.tracks.minOfOrNull { recency[it.id] ?: Int.MAX_VALUE } ?: Int.MAX_VALUE val libraryItems = remember(albums, tracks, libraryFilter, pins, history, liked, playlists) { + val likedAlbums = albums.filter { album -> album.tracks.any { it.id in liked } } + val likedArtists = tracks.filter { it.id in liked }.map { it.displayArtist }.distinct() val pinItems = pins.map { pin -> when (pin.kind) { DesktopCollection.Kind.ALBUM -> { val album = albums.firstOrNull { it.id == pin.id } + ?: albums.firstOrNull { it.title.equals(pin.title, true) } LibraryRailItem( pin.id, pin.title, pin.subtitle, album?.artworkUri, pinned = true @@ -149,7 +153,7 @@ fun YuriDesktopApp(session: DesktopSession) { } DesktopCollection.Kind.ARTIST -> LibraryRailItem( pin.id, pin.title, pin.subtitle, - tracks.firstOrNull { it.displayArtist == pin.title }?.artworkUri, + tracks.firstOrNull { it.displayArtist.equals(pin.title, true) }?.artworkUri, circular = true, pinned = true ) DesktopCollection.Kind.SONG -> { @@ -168,23 +172,41 @@ fun YuriDesktopApp(session: DesktopSession) { } } } + val likedRow = if (liked.isNotEmpty()) { + listOf( + LibraryRailItem( + "liked", + "Liked Songs", + "${liked.size} songs", + tracks.firstOrNull { it.id in liked }?.artworkUri + ) + ) + } else emptyList() val rest = when (libraryFilter) { - LibraryFilter.Artists -> tracks - .map { it.displayArtist } - .distinct() - .map { name -> - val cover = tracks.firstOrNull { it.displayArtist == name }?.artworkUri - val rec = tracks.filter { it.displayArtist == name } - .minOfOrNull { recency[it.id] ?: Int.MAX_VALUE } ?: Int.MAX_VALUE - Triple(rec, name, cover) - } - .sortedBy { it.first } - .map { (_, name, cover) -> - LibraryRailItem("artist:$name", name, "Artist", cover, circular = true) + LibraryFilter.Artists -> { + val names = ( + pins.filter { it.kind == DesktopCollection.Kind.ARTIST }.map { it.title } + + likedArtists + ).distinct() + names.map { name -> + LibraryRailItem( + "artist:$name", + name, + "Artist", + tracks.firstOrNull { it.displayArtist.equals(name, true) }?.artworkUri, + circular = true + ) } - LibraryFilter.Albums -> albums - .sortedBy { albumRecency(it) } - .map { LibraryRailItem(it.id, it.title, it.artist, it.artworkUri) } + } + LibraryFilter.Albums -> { + val pinnedAlbums = pins.filter { it.kind == DesktopCollection.Kind.ALBUM } + .mapNotNull { pin -> + albums.firstOrNull { it.id == pin.id } + ?: albums.firstOrNull { it.title.equals(pin.title, true) } + } + (pinnedAlbums + likedAlbums).distinctBy { it.id } + .map { LibraryRailItem(it.id, it.title, it.artist, it.artworkUri) } + } LibraryFilter.Playlists -> listOf( LibraryRailItem("new-playlist", "New playlist", "Create", null) ) + playlists.sortedByDescending { it.updatedAtMs }.map { pl -> @@ -195,18 +217,24 @@ fun YuriDesktopApp(session: DesktopSession) { pl.artworkUri(tracks) ) } - LibraryFilter.Recents -> albums - .sortedBy { albumRecency(it) } - .map { - LibraryRailItem( - it.id, it.title, - "Album · ${it.artist}", - it.artworkUri - ) - } + LibraryFilter.Recents -> { + val stuffAlbumIds = ( + pins.filter { it.kind == DesktopCollection.Kind.ALBUM }.map { it.id } + + likedAlbums.map { it.id } + ).toSet() + val stuffPlaylistIds = playlists.map { it.id }.toSet() + buildList { + history.forEach { t -> + val album = albums.firstOrNull { a -> a.tracks.any { it.id == t.id } } + if (album != null && (album.id in stuffAlbumIds || album.tracks.any { it.id in liked })) { + add(LibraryRailItem(album.id, album.title, "Album · ${album.artist}", album.artworkUri)) + } + } + }.distinctBy { it.id } + } } val pinIds = pinItems.map { it.id }.toSet() - pinItems + rest.filter { it.id !in pinIds } + likedRow + pinItems + rest.filter { it.id !in pinIds && it.id !in likedRow.map { r -> r.id }.toSet() } } val selectedLibraryId = (route as? Route.Album)?.album?.id @@ -230,9 +258,10 @@ fun YuriDesktopApp(session: DesktopSession) { selectedLibraryId = selectedLibraryId, onLibraryItem = { item -> when { - item.id == "new-playlist" -> { - val created = session.playlists.create("New playlist") - push(Route.Playlist(created.id)) + item.id == "new-playlist" -> showNewPlaylist = true + item.id == "liked" -> { + val likedTracks = tracks.filter { it.id in liked } + if (likedTracks.isNotEmpty()) session.player.play(likedTracks, 0) } item.id.startsWith("playlist:") -> { push(Route.Playlist(item.id.removePrefix("playlist:"))) @@ -380,7 +409,8 @@ fun YuriDesktopApp(session: DesktopSession) { profile?.bannerCleared == true -> "" !profile?.bannerUri.isNullOrBlank() -> profile!!.bannerUri else -> base.bannerUri - } + }, + genres = profile?.genres.orEmpty() ) } val artistTracks = remember(r.name, tracks) { @@ -459,6 +489,24 @@ fun YuriDesktopApp(session: DesktopSession) { onDismiss = { showSettings = false } ) } + if (showNewPlaylist) { + NewPlaylistDialog( + onDismiss = { showNewPlaylist = false }, + onCreate = { name, description -> + val created = session.playlists.create(name, description) + session.collection.pin( + DesktopCollection.Pin( + DesktopCollection.Kind.PLAYLIST, + created.id, + created.name, + "Playlist" + ) + ) + showNewPlaylist = false + push(Route.Playlist(created.id)) + } + ) + } editSong?.let { song -> EditSongDialog( track = song, From 333765a52f5952bd39b7110daae0beb943d3e7fd Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 20:11:58 +0000 Subject: [PATCH 31/69] Stop tracking Kotlin daemon error logs --- .gitignore | 1 + .kotlin/errors/errors-1787337886009.log | 3 --- .kotlin/errors/errors-1787340227855.log | 3 --- .kotlin/errors/errors-1787343069317.log | 3 --- 4 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 .kotlin/errors/errors-1787337886009.log delete mode 100644 .kotlin/errors/errors-1787340227855.log delete mode 100644 .kotlin/errors/errors-1787343069317.log diff --git a/.gitignore b/.gitignore index afe7d92c..13effbfd 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ build/ .externalNativeBuild .cxx local.properties +.kotlin/ # Signing (never commit secrets or keystores) keystore.properties diff --git a/.kotlin/errors/errors-1787337886009.log b/.kotlin/errors/errors-1787337886009.log deleted file mode 100644 index cf45d602..00000000 --- a/.kotlin/errors/errors-1787337886009.log +++ /dev/null @@ -1,3 +0,0 @@ -kotlin version: 2.3.21 -error message: Daemon compilation failed - diff --git a/.kotlin/errors/errors-1787340227855.log b/.kotlin/errors/errors-1787340227855.log deleted file mode 100644 index cf45d602..00000000 --- a/.kotlin/errors/errors-1787340227855.log +++ /dev/null @@ -1,3 +0,0 @@ -kotlin version: 2.3.21 -error message: Daemon compilation failed - diff --git a/.kotlin/errors/errors-1787343069317.log b/.kotlin/errors/errors-1787343069317.log deleted file mode 100644 index cf45d602..00000000 --- a/.kotlin/errors/errors-1787343069317.log +++ /dev/null @@ -1,3 +0,0 @@ -kotlin version: 2.3.21 -error message: Daemon compilation failed - From 2ec195840529bdc9cd7d001c97003ad5a47d0c71 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 20:13:39 +0000 Subject: [PATCH 32/69] Fix Android rememberSaveable import and add CI rememberSaveable lives in runtime-saveable; the wrong import broke tab stack types. GitHub Actions now builds Android debug, desktop, and JVM tests on every push and PR. --- .github/workflows/ci.yml | 47 +++++++++++++++++++ .../yuriplayer/activities/MainActivity.kt | 6 +-- 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..f2a2391f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + pull_request: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - name: Set up Android SDK + uses: android-actions/setup-android@v3 + with: + packages: > + tools + platform-tools + platforms;android-37 + build-tools;36.0.0 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build Android + desktop + unit tests + run: | + chmod +x ./gradlew + ./gradlew --no-daemon --stacktrace \ + :core:compileKotlinJvm \ + :core:jvmTest \ + :components:compileKotlinJvm \ + :desktop:compileKotlin \ + :app:assembleDebug diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt index c461c8f3..e8db1f24 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/MainActivity.kt @@ -66,7 +66,7 @@ import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberSaveable +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -415,8 +415,8 @@ fun YuriApp( mutableStateOf( mapOf( TopTab.Home to emptyList(), - TopTab.MyStuff to emptyList(), - TopTab.Explore to emptyList() + TopTab.MyStuff to emptyList(), + TopTab.Explore to emptyList() ) ) } From 2e418a547cbe7c3115c51ac0012e7627c54d302c Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 20:21:49 +0000 Subject: [PATCH 33/69] Fix artist banners to a clipped 3:1 Twitter header CoverArt was forcing 1:1 so the header overflowed as a giant square of album/Wikipedia art. Banners are now 3:1, clipped, and auto-picked from AudioDB/Jellyfin landscape fanart. --- .../yuriplayer/components/art/CoverArt.kt | 11 +-- .../components/artist/ArtistPage.kt | 27 ++++--- .../yuriplayer/components/model/UiModels.kt | 2 +- .../core/artist/ArtistInfoClient.kt | 75 ++++++++++++++++--- .../desktop/ui/ArtistBannerPicker.kt | 2 +- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 2 +- 6 files changed, 90 insertions(+), 29 deletions(-) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt index 1313881f..9fdfb11e 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/art/CoverArt.kt @@ -30,13 +30,14 @@ fun CoverArt( size: Dp? = null, corner: Dp = 8.dp, contentDescription: String? = null, - contentScale: ContentScale = ContentScale.Fit + contentScale: ContentScale = ContentScale.Fit, + square: Boolean = true ) { val shape = RoundedCornerShape(corner) - val frame = if (size != null) { - modifier.size(size) - } else { - modifier.aspectRatio(1f) + val frame = when { + size != null -> modifier.size(size) + square -> modifier.aspectRatio(1f) + else -> modifier } Box( modifier = frame diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt index 7d2ac69c..d29dbc85 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt @@ -52,6 +52,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -369,24 +370,30 @@ fun ArtistPage( @Composable private fun ArtistBanner(artist: ArtistPageModel) { + val banner = artist.bannerUri?.takeIf { it.isNotBlank() } Box( Modifier .fillMaxWidth() - .height(340.dp) + .aspectRatio(3f) + .clip(RectangleShape) + .background(MaterialTheme.colorScheme.surfaceVariant) ) { - CoverArt( - model = artist.bannerUri ?: artist.artworkUri, - modifier = Modifier.fillMaxSize(), - corner = 0.dp, - contentScale = ContentScale.Crop - ) + if (banner != null) { + CoverArt( + model = banner, + modifier = Modifier.fillMaxSize(), + corner = 0.dp, + contentScale = ContentScale.Crop, + square = false + ) + } Box( Modifier .fillMaxSize() .background( Brush.verticalGradient( - 0f to Color.Black.copy(alpha = 0.15f), - 0.55f to Color.Transparent, + 0f to Color.Black.copy(alpha = 0.25f), + 0.45f to Color.Transparent, 1f to MaterialTheme.colorScheme.background ) ) @@ -394,7 +401,7 @@ private fun ArtistBanner(artist: ArtistPageModel) { Column( Modifier .align(Alignment.BottomStart) - .padding(horizontal = 28.dp, vertical = 20.dp) + .padding(horizontal = 28.dp, vertical = 16.dp) ) { Text( artist.name, diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt index 90e5fe51..2e8019f3 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt @@ -111,7 +111,7 @@ fun List.artistPage( return ArtistPageModel( name = name, artworkUri = ofArtist.firstNotNullOfOrNull { it.artworkUri } ?: discography.firstOrNull()?.artworkUri, - bannerUri = discography.firstOrNull()?.artworkUri ?: ofArtist.firstNotNullOfOrNull { it.artworkUri }, + bannerUri = null, stats = "${discography.size} albums · ${ofArtist.size} tracks", about = null, popular = popular, diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt index 4d2b058b..cf5249fe 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistInfoClient.kt @@ -32,14 +32,24 @@ class ArtistInfoClient( suspend fun resolve(name: String, force: Boolean = false): ArtistProfile = withContext(Dispatchers.IO) { val cached = store.load(name) + val bannerLooksWrong = cached?.bannerUri.let { uri -> + uri.isNullOrBlank() || + uri.contains("wikipedia", true) || + uri.contains("wikimedia", true) + } val fresh = cached != null && !force && + !bannerLooksWrong && (System.currentTimeMillis() - cached.updatedAtMs) < CACHE_TTL_MS && (!cached.bio.isNullOrBlank() || !cached.bannerUri.isNullOrBlank() || cached.bannerCleared) if (fresh && cached != null) return@withContext cached val remote = fetchRemote(name) - val merged = merge(name, cached, remote) + var merged = merge(name, cached, remote) + if (!merged.bannerCleared) { + val wide = pickWideBanner(name, merged.bannerUri) + if (wide != null) merged = merged.copy(bannerUri = wide) + } store.save(merged) merged } @@ -52,8 +62,9 @@ class ArtistInfoClient( val mb = async { musicBrainzImages(name) } val wd = async { wikidataImages(name) } val lib = async { runCatching { libraryImages(name) }.getOrDefault(emptyList()) } - (adb.await() + wiki.await() + deezer.await() + mb.await() + wd.await() + lib.await()) + (adb.await() + lib.await() + wiki.await() + deezer.await() + mb.await() + wd.await()) .distinctBy { it.url } + .sortedByDescending { it.headerScore() } } } @@ -135,8 +146,10 @@ class ArtistInfoClient( imageUri = a.imageUri ?: b.imageUri, bannerUri = when { a.bannerCleared -> null - !a.bannerUri.isNullOrBlank() -> a.bannerUri - else -> b.bannerUri + a.bannerUri.isWideHeaderUrl() -> a.bannerUri + b.bannerUri.isWideHeaderUrl() -> b.bannerUri + !a.bannerUri.isNullOrBlank() && !a.bannerUri.isPortraitSource() -> a.bannerUri + else -> b.bannerUri?.takeUnless { it.isPortraitSource() } }, genres = (a.genres + b.genres).map { it.trim() }.filter { it.isNotEmpty() }.distinctBy { it.lowercase() }, source = listOf(a.source, b.source).filter { it.isNotBlank() }.distinct().joinToString(","), @@ -162,8 +175,8 @@ class ArtistInfoClient( ?: summary.obj("thumbnail")?.str("source") val banner = summary.obj("originalimage")?.let { img -> val w = img.int("width") ?: 0 - val h = img.int("height") ?: 0 - if (w >= h && w >= 800) img.str("source") else null + val h = img.int("height") ?: 1 + if (h > 0 && w.toFloat() / h >= 2.4f) img.str("source") else null } return ArtistProfile( artistKey = artistKey(name) ?: name.lowercase(), @@ -214,7 +227,7 @@ class ArtistInfoClient( private suspend fun audioDbProfile(name: String): ArtistProfile? { val a = audioDbArtist(name) ?: return null val bio = a.str("strBiographyEN")?.takeIf { it.isNotBlank() && it != "null" } - val banner = listOf("strArtistFanart", "strArtistFanart2", "strArtistBanner") + val banner = listOf("strArtistBanner", "strArtistFanart", "strArtistFanart2", "strArtistFanart3", "strArtistWideThumb") .firstNotNullOfOrNull { field -> a.str(field)?.takeIf { it.isNotBlank() && it != "null" } } val thumb = a.str("strArtistThumb")?.takeIf { it.isNotBlank() && it != "null" } val genres = buildList { @@ -236,16 +249,20 @@ class ArtistInfoClient( val a = audioDbArtist(name) ?: return emptyList() val display = a.str("strArtist") ?: name val fields = listOf( + "strArtistBanner" to "AudioDB banner", "strArtistFanart" to "AudioDB fanart", "strArtistFanart2" to "AudioDB fanart 2", "strArtistFanart3" to "AudioDB fanart 3", - "strArtistBanner" to "AudioDB banner", - "strArtistWideThumb" to "AudioDB wide", - "strArtistThumb" to "AudioDB thumb" + "strArtistWideThumb" to "AudioDB wide" ) return fields.mapNotNull { (field, label) -> val u = a.str(field)?.takeIf { it.isNotBlank() && it != "null" } ?: return@mapNotNull null - ArtistImageCandidate(u, "theaudiodb", "$label · $display") + val banner = label.contains("banner", true) + ArtistImageCandidate( + u, "theaudiodb", "$label · $display", + width = if (banner) 1500 else 1280, + height = if (banner) 500 else 720 + ) } } @@ -426,6 +443,42 @@ class ArtistInfoClient( }.getOrNull() } + private suspend fun pickWideBanner(name: String, current: String?): String? { + if (current.isWideHeaderUrl() && !current.isPortraitSource()) return current + val cands = runCatching { bannerCandidates(name) }.getOrDefault(emptyList()) + return cands.firstOrNull { it.headerScore() >= 50 }?.url + ?: current?.takeUnless { it.isPortraitSource() } + } + + private fun ArtistImageCandidate.headerScore(): Int { + val w = width ?: 0 + val h = height ?: 0 + val ratio = if (h > 0) w.toFloat() / h else 0f + val label = label.lowercase() + var score = 0 + if (ratio >= 2.4f) score += 80 + else if (ratio >= 1.7f) score += 50 + else if (ratio in 0.01f..0.99f) score -= 40 + if (label.contains("banner")) score += 40 + if (label.contains("fanart") || label.contains("backdrop") || label.contains("wide")) score += 25 + if (sourceId == "theaudiodb" || sourceId == "jellyfin") score += 10 + if (sourceId == "wikipedia" || sourceId == "wikidata" || sourceId == "deezer") score -= 20 + return score + } + + private fun String?.isWideHeaderUrl(): Boolean { + val u = this ?: return false + if (u.startsWith("file:")) return true + return u.contains("banner", true) || u.contains("fanart", true) || u.contains("Backdrop", true) + } + + private fun String?.isPortraitSource(): Boolean { + val u = this ?: return false + return u.contains("wikipedia", true) || + u.contains("wikimedia", true) || + u.contains("deezer.com", true) + } + private fun kotlinx.serialization.json.JsonArray?.orEmpty() = this ?: kotlinx.serialization.json.JsonArray(emptyList()) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt index 19f16f86..2713bee1 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt @@ -94,7 +94,7 @@ fun ArtistBannerPicker( model = c.url, modifier = Modifier .fillMaxWidth() - .aspectRatio(16f / 9f) + .aspectRatio(3f) .clip(RoundedCornerShape(8.dp)) .border( 1.dp, diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index a0ccfa47..6429b5c5 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -408,7 +408,7 @@ fun YuriDesktopApp(session: DesktopSession) { bannerUri = when { profile?.bannerCleared == true -> "" !profile?.bannerUri.isNullOrBlank() -> profile!!.bannerUri - else -> base.bannerUri + else -> null }, genres = profile?.genres.orEmpty() ) From e0f440b89dbb5b4a3f178c54ab19c5ad219dde35 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 20:23:59 +0000 Subject: [PATCH 34/69] Clip artist header and about art so they cannot paint under UI Banner uses matchParentSize + clipToBounds. About no longer forces a 1:1 CoverArt. Content rows sit on an opaque background. --- .../components/artist/ArtistPage.kt | 143 ++++++++++-------- 1 file changed, 80 insertions(+), 63 deletions(-) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt index d29dbc85..d32d75d8 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt @@ -50,6 +50,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape @@ -58,6 +59,7 @@ 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 coil3.compose.AsyncImage import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.list.AlbumCard import capital.yuri.yuriplayer.components.list.TrackRow @@ -101,7 +103,10 @@ fun ArtistPage( } item { Row( - Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 16.dp), + Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = 24.dp, vertical = 16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) ) { @@ -152,7 +157,10 @@ fun ArtistPage( } item { Row( - Modifier.fillMaxWidth().padding(horizontal = 24.dp), + Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = 24.dp), horizontalArrangement = Arrangement.spacedBy(32.dp) ) { Column(Modifier.weight(1.2f)) { @@ -212,70 +220,75 @@ fun ArtistPage( } } item { - Row( - Modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, top = 28.dp, bottom = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - "Discography", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold, - modifier = Modifier.weight(1f) - ) - TextButton(onClick = {}) { Text("Show all") } - } - Row( - Modifier.padding(horizontal = 24.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - FilterChip( - selected = disco == DiscoFilter.Popular, - onClick = { disco = DiscoFilter.Popular }, - label = { Text("Popular releases") } - ) - FilterChip( - selected = disco == DiscoFilter.Albums, - onClick = { disco = DiscoFilter.Albums }, - label = { Text("Albums") } - ) - FilterChip( - selected = disco == DiscoFilter.Singles, - onClick = { disco = DiscoFilter.Singles }, - label = { Text("Singles and EPs") } - ) - } - Spacer(Modifier.height(12.dp)) - LazyRow( - contentPadding = PaddingValues(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - items(releases, key = { it.id }) { album -> - AlbumCard( - album = album.copy(artist = "${album.year ?: ""} · ${album.releaseKind()}".trim(' ', '·')), - onClick = { onOpenAlbum(album) } + Column(Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.background)) { + Row( + Modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, top = 28.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "Discography", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f) ) + TextButton(onClick = {}) { Text("Show all") } } - } - } - if (artist.appearsOn.isNotEmpty()) { - item { - Text( - "Appears on", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 28.dp, bottom = 8.dp) - ) + Row( + Modifier.padding(horizontal = 24.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + FilterChip( + selected = disco == DiscoFilter.Popular, + onClick = { disco = DiscoFilter.Popular }, + label = { Text("Popular releases") } + ) + FilterChip( + selected = disco == DiscoFilter.Albums, + onClick = { disco = DiscoFilter.Albums }, + label = { Text("Albums") } + ) + FilterChip( + selected = disco == DiscoFilter.Singles, + onClick = { disco = DiscoFilter.Singles }, + label = { Text("Singles and EPs") } + ) + } + Spacer(Modifier.height(12.dp)) LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(4.dp) ) { - items(artist.appearsOn, key = { "ao-${it.id}" }) { album -> - AlbumCard(album = album, onClick = { onOpenAlbum(album) }) + items(releases, key = { it.id }) { album -> + AlbumCard( + album = album.copy(artist = "${album.year ?: ""} · ${album.releaseKind()}".trim(' ', '·')), + onClick = { onOpenAlbum(album) } + ) + } + } + } + } + if (artist.appearsOn.isNotEmpty()) { + item { + Column(Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.background)) { + Text( + "Appears on", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 28.dp, bottom = 8.dp) + ) + LazyRow( + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(artist.appearsOn, key = { "ao-${it.id}" }) { album -> + AlbumCard(album = album, onClick = { onOpenAlbum(album) }) + } } } } } item { + Column(Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.background)) { Text( "About", style = MaterialTheme.typography.titleLarge, @@ -286,15 +299,17 @@ fun ArtistPage( modifier = Modifier .padding(horizontal = 24.dp) .fillMaxWidth() + .height(280.dp) .clip(RoundedCornerShape(12.dp)), color = MaterialTheme.colorScheme.surfaceVariant ) { - Box(Modifier.fillMaxWidth().height(280.dp)) { + Box(Modifier.fillMaxSize().clipToBounds()) { CoverArt( model = artist.artworkUri, - modifier = Modifier.fillMaxSize(), + modifier = Modifier.matchParentSize(), corner = 0.dp, - contentScale = ContentScale.Crop + contentScale = ContentScale.Crop, + square = false ) Box( Modifier @@ -337,6 +352,7 @@ fun ArtistPage( } } Spacer(Modifier.height(48.dp)) + } } } if (mini) { @@ -375,21 +391,22 @@ private fun ArtistBanner(artist: ArtistPageModel) { Modifier .fillMaxWidth() .aspectRatio(3f) + .clipToBounds() .clip(RectangleShape) .background(MaterialTheme.colorScheme.surfaceVariant) ) { if (banner != null) { - CoverArt( + AsyncImage( model = banner, - modifier = Modifier.fillMaxSize(), - corner = 0.dp, + contentDescription = artist.name, + modifier = Modifier.matchParentSize(), contentScale = ContentScale.Crop, - square = false + alignment = Alignment.Center ) } Box( Modifier - .fillMaxSize() + .matchParentSize() .background( Brush.verticalGradient( 0f to Color.Black.copy(alpha = 0.25f), From fb00f92d5bbaeafbfcd7550a86c7c2a1397ac6e5 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 20:25:21 +0000 Subject: [PATCH 35/69] =?UTF-8?q?Add=20Room=20migrations=204=E2=86=925=20a?= =?UTF-8?q?nd=205=E2=86=926=20so=20old=20installs=20boot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stylo 4 was stuck on catalog v4; we only shipped 6→10. Chain is now 4 through 10 (artist/album genresJson, then existing steps). --- .../yuri/yuriplayer/data/db/YuriMigrations.kt | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriMigrations.kt b/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriMigrations.kt index d4c7e10f..2721f110 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriMigrations.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/db/YuriMigrations.kt @@ -14,6 +14,16 @@ import androidx.sqlite.execSQL */ object YuriMigrations { + val MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(db: SupportSQLiteDatabase) = sql4to5 { db.execSQL(it) } + override fun migrate(connection: SQLiteConnection) = sql4to5 { connection.execSQL(it) } + } + + val MIGRATION_5_6 = object : Migration(5, 6) { + override fun migrate(db: SupportSQLiteDatabase) = sql5to6 { db.execSQL(it) } + override fun migrate(connection: SQLiteConnection) = sql5to6 { connection.execSQL(it) } + } + val MIGRATION_6_7 = object : Migration(6, 7) { override fun migrate(db: SupportSQLiteDatabase) = sql6to7 { db.execSQL(it) } override fun migrate(connection: SQLiteConnection) = sql6to7 { connection.execSQL(it) } @@ -34,7 +44,22 @@ object YuriMigrations { override fun migrate(connection: SQLiteConnection) = sql9to10 { connection.execSQL(it) } } - val ALL: Array = arrayOf(MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10) + val ALL: Array = arrayOf( + MIGRATION_4_5, + MIGRATION_5_6, + MIGRATION_6_7, + MIGRATION_7_8, + MIGRATION_8_9, + MIGRATION_9_10 + ) + + private fun sql4to5(exec: (String) -> Unit) { + exec("ALTER TABLE artist_profiles ADD COLUMN genresJson TEXT") + } + + private fun sql5to6(exec: (String) -> Unit) { + exec("ALTER TABLE album_metadata ADD COLUMN genresJson TEXT") + } private fun sql6to7(exec: (String) -> Unit) { exec( From 21110332a402a33a7a24c323ffd792df2890f3bc Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 21:21:21 +0000 Subject: [PATCH 36/69] Render desktop dialogs in-window and restore hearts Settings, crop, metadata, playlist, banner, and delete prompts are overlays in the main window. Hearts are back on tracks, albums, artists, playlists, queue, explore, and now playing. --- .../yuriplayer/components/album/AlbumPage.kt | 56 +++++++++++---- .../components/artist/ArtistPage.kt | 19 +++++- .../components/dialog/InWindowDialog.kt | 68 +++++++++++++++++++ .../yuriplayer/components/list/LikeHeart.kt | 27 ++++++++ .../yuriplayer/components/list/TrackRow.kt | 7 +- .../components/player/NowPlayingSidebar.kt | 8 +++ .../components/player/QueuePanel.kt | 10 ++- .../yuriplayer/desktop/DesktopCollection.kt | 10 ++- .../desktop/ui/ArtistBannerPicker.kt | 7 +- .../yuriplayer/desktop/ui/DesktopExplore.kt | 8 ++- .../yuriplayer/desktop/ui/DesktopProviders.kt | 39 +++++------ .../yuriplayer/desktop/ui/DesktopSettings.kt | 8 +-- .../yuriplayer/desktop/ui/ImageCropDialog.kt | 6 +- .../desktop/ui/MetadataEditDialog.kt | 10 +-- .../yuriplayer/desktop/ui/PlaylistPage.kt | 40 +++++++---- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 55 +++++++++++++-- 16 files changed, 297 insertions(+), 81 deletions(-) create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/dialog/InWindowDialog.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/LikeHeart.kt diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt index ec76153c..65f50b61 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.layout.WindowWidthClass import capital.yuri.yuriplayer.components.layout.windowWidthClass +import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.theme.AlbumArtBackdrop @@ -49,6 +50,10 @@ fun AlbumPage( onEdit: () -> Unit = {}, onEditTrack: (Int) -> Unit = {}, onArtist: (String) -> Unit = {}, + liked: Boolean = false, + onToggleLike: () -> Unit = {}, + likedTrackIds: Set = emptySet(), + onToggleTrackLike: (String) -> Unit = {}, modifier: Modifier = Modifier ) { val coverColors by rememberCoverColors( @@ -66,11 +71,13 @@ fun AlbumPage( val widthClass = windowWidthClass(maxWidth) when (widthClass) { WindowWidthClass.Compact -> AlbumPageCompact( - album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist + album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist, + liked, onToggleLike, likedTrackIds, onToggleTrackLike ) WindowWidthClass.Medium, WindowWidthClass.Expanded -> AlbumPageWide( - album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist + album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist, + liked, onToggleLike, likedTrackIds, onToggleTrackLike ) } } @@ -86,7 +93,11 @@ private fun AlbumPageCompact( onTrack: (Int) -> Unit, onEdit: () -> Unit, onEditTrack: (Int) -> Unit, - onArtist: (String) -> Unit + onArtist: (String) -> Unit, + liked: Boolean, + onToggleLike: () -> Unit, + likedTrackIds: Set, + onToggleTrackLike: (String) -> Unit ) { Column(Modifier.fillMaxSize()) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -99,6 +110,7 @@ private fun AlbumPageCompact( } Text("Album", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground) Spacer(Modifier.weight(1f)) + LikeHeart(liked = liked, onToggle = onToggleLike) IconButton(onClick = onEdit) { Icon(Icons.Default.Edit, contentDescription = "Edit album") } @@ -111,8 +123,8 @@ private fun AlbumPageCompact( .aspectRatio(1f), corner = 12.dp ) - AlbumMeta(album, playing, onPlay, onArtist, Modifier.padding(20.dp)) - TrackList(album, onTrack, onEditTrack) + AlbumMeta(album, playing, onPlay, onArtist, liked, onToggleLike, Modifier.padding(20.dp)) + TrackList(album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike) } } @@ -125,7 +137,11 @@ private fun AlbumPageWide( onTrack: (Int) -> Unit, onEdit: () -> Unit, onEditTrack: (Int) -> Unit, - onArtist: (String) -> Unit + onArtist: (String) -> Unit, + liked: Boolean, + onToggleLike: () -> Unit, + likedTrackIds: Set, + onToggleTrackLike: (String) -> Unit ) { Row(Modifier.fillMaxSize().padding(24.dp)) { Column( @@ -142,6 +158,7 @@ private fun AlbumPageWide( ) } Spacer(Modifier.weight(1f)) + LikeHeart(liked = liked, onToggle = onToggleLike) IconButton(onClick = onEdit) { Icon(Icons.Default.Edit, contentDescription = "Edit album") } @@ -152,9 +169,9 @@ private fun AlbumPageWide( corner = 12.dp ) Spacer(Modifier.height(16.dp)) - AlbumMeta(album, playing, onPlay, onArtist) + AlbumMeta(album, playing, onPlay, onArtist, liked, onToggleLike) } - TrackList(album, onTrack, onEditTrack, modifier = Modifier.weight(1f)) + TrackList(album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike, modifier = Modifier.weight(1f)) } } @@ -164,6 +181,8 @@ private fun AlbumMeta( playing: Boolean, onPlay: () -> Unit, onArtist: (String) -> Unit, + liked: Boolean, + onToggleLike: () -> Unit, modifier: Modifier = Modifier ) { Column(modifier) { @@ -189,12 +208,15 @@ private fun AlbumMeta( color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), modifier = Modifier.padding(top = 4.dp) ) - IconButton(onClick = onPlay, modifier = Modifier.padding(top = 8.dp)) { - Icon( - if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, - contentDescription = if (playing) "Pause" else "Play", - tint = MaterialTheme.colorScheme.primary - ) + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 8.dp)) { + IconButton(onClick = onPlay) { + Icon( + if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, + contentDescription = if (playing) "Pause" else "Play", + tint = MaterialTheme.colorScheme.primary + ) + } + LikeHeart(liked = liked, onToggle = onToggleLike) } } } @@ -204,6 +226,8 @@ private fun TrackList( album: AlbumPageModel, onTrack: (Int) -> Unit, onEditTrack: (Int) -> Unit = {}, + likedTrackIds: Set = emptySet(), + onToggleTrackLike: (String) -> Unit = {}, modifier: Modifier = Modifier ) { val discs = album.tracks.groupBy { it.discNumber ?: 1 } @@ -227,7 +251,9 @@ private fun TrackList( onClick = { onTrack(global.coerceAtLeast(0)) }, onLongClick = { onEditTrack(global.coerceAtLeast(0)) }, showCover = false, - showAlbum = false + showAlbum = false, + liked = track.id in likedTrackIds, + onToggleLike = { onToggleTrackLike(track.id) } ) } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt index d32d75d8..3e0b7d53 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt @@ -61,6 +61,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.list.AlbumCard import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.list.formatTime @@ -82,7 +83,11 @@ fun ArtistPage( modifier: Modifier = Modifier, onChangeHeader: () -> Unit = {}, onFetchHeader: () -> Unit = {}, - onClearHeader: () -> Unit = {} + onClearHeader: () -> Unit = {}, + liked: Boolean = false, + onToggleLike: () -> Unit = {}, + likedTrackIds: Set = emptySet(), + onToggleTrackLike: (String) -> Unit = {} ) { val listState = rememberLazyListState() val mini by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } @@ -132,6 +137,7 @@ fun ArtistPage( tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) ) } + LikeHeart(liked = liked, onToggle = onToggleLike) IconButton(onClick = { more = true }) { Icon( Icons.Default.MoreHoriz, @@ -174,6 +180,8 @@ fun ArtistPage( PopularRow( index = i + 1, track = track, + liked = track.id in likedTrackIds, + onToggleLike = { onToggleTrackLike(track.id) }, onClick = { onTrack(i) } ) } @@ -440,7 +448,13 @@ private fun ArtistBanner(artist: ArtistPageModel) { } @Composable -private fun PopularRow(index: Int, track: TrackRowModel, onClick: () -> Unit) { +private fun PopularRow( + index: Int, + track: TrackRowModel, + liked: Boolean, + onToggleLike: () -> Unit, + onClick: () -> Unit +) { Row( Modifier .fillMaxWidth() @@ -464,6 +478,7 @@ private fun PopularRow(index: Int, track: TrackRowModel, onClick: () -> Unit) { overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.Medium ) + LikeHeart(liked = liked, onToggle = onToggleLike) Text( formatTime(track.durationMs ?: 0L), style = MaterialTheme.typography.bodySmall, diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/dialog/InWindowDialog.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/dialog/InWindowDialog.kt new file mode 100644 index 00000000..f3093af9 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/dialog/InWindowDialog.kt @@ -0,0 +1,68 @@ +package capital.yuri.yuriplayer.components.dialog + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex + +/** + * Modal that paints inside the current window — never a separate OS dialog. + */ +@Composable +fun InWindowDialog( + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + Box( + Modifier + .fillMaxSize() + .zIndex(40f) + .background(Color.Black.copy(alpha = 0.52f)) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + onClick = onDismiss + ), + contentAlignment = Alignment.Center + ) { + Box( + modifier.clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() }, + onClick = {} + ) + ) { + content() + } + } +} + +@Composable +fun InWindowPanel( + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + InWindowDialog(onDismiss = onDismiss, modifier = modifier) { + Surface( + shape = RoundedCornerShape(16.dp), + tonalElevation = 6.dp, + color = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface + ) { + content() + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/LikeHeart.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/LikeHeart.kt new file mode 100644 index 00000000..d7c0fe9e --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/LikeHeart.kt @@ -0,0 +1,27 @@ +package capital.yuri.yuriplayer.components.list + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun LikeHeart( + liked: Boolean, + onToggle: () -> Unit, + modifier: Modifier = Modifier, + contentDescription: String? = if (liked) "Unlike" else "Like" +) { + IconButton(onClick = onToggle, modifier = modifier) { + Icon( + imageVector = if (liked) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = contentDescription, + tint = if (liked) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt index 1be03912..362a3b0c 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt @@ -28,7 +28,9 @@ fun TrackRow( modifier: Modifier = Modifier, showCover: Boolean = true, showAlbum: Boolean = true, - onLongClick: (() -> Unit)? = null + onLongClick: (() -> Unit)? = null, + liked: Boolean = false, + onToggleLike: (() -> Unit)? = null ) { val highlight = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) Row( @@ -67,6 +69,9 @@ fun TrackRow( color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) ) } + if (onToggleLike != null) { + LikeHeart(liked = liked, onToggle = onToggleLike) + } track.durationMs?.let { Text( formatTime(it), diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index 09bac2fe..8b9f5bcf 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.model.CoverRef import capital.yuri.yuriplayer.components.model.TrackRowModel @@ -33,6 +34,10 @@ fun NowPlayingSidebar( onClearQueue: () -> Unit = {}, onClearHistory: () -> Unit = {}, onMoveUpcoming: ((from: Int, to: Int) -> Unit)? = null, + liked: Boolean = false, + onToggleLike: () -> Unit = {}, + likedIds: Set = emptySet(), + onToggleTrackLike: (String) -> Unit = {}, modifier: Modifier = Modifier ) { Column( @@ -73,6 +78,7 @@ fun NowPlayingSidebar( color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) ) } + LikeHeart(liked = liked, onToggle = onToggleLike) } Spacer(Modifier.height(8.dp)) } @@ -95,6 +101,8 @@ fun NowPlayingSidebar( onClearQueue = onClearQueue, onClearHistory = onClearHistory, onMove = onMoveUpcoming, + likedIds = likedIds, + onToggleLike = onToggleTrackLike, modifier = Modifier.weight(1f) ) } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt index 6dc950fa..fcf55ced 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt @@ -49,6 +49,8 @@ fun QueuePanel( onClearQueue: () -> Unit = {}, onClearHistory: () -> Unit = {}, onMove: ((from: Int, to: Int) -> Unit)? = null, + likedIds: Set = emptySet(), + onToggleLike: (String) -> Unit = {}, modifier: Modifier = Modifier ) { var tab by remember { mutableStateOf(QueueTab.Queue) } @@ -131,7 +133,9 @@ fun QueuePanel( track = row, onClick = { onPlay(row) }, showCover = true, - showAlbum = false + showAlbum = false, + liked = row.id in likedIds, + onToggleLike = { onToggleLike(row.id) } ) } } @@ -160,7 +164,9 @@ fun QueuePanel( track = row, onClick = { onPlay(row) }, showCover = true, - showAlbum = true + showAlbum = true, + liked = row.id in likedIds, + onToggleLike = { onToggleLike(row.id) } ) } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt index b1f7876d..547039ef 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt @@ -43,14 +43,18 @@ class DesktopCollection(configDir: String) { persist() } + fun isPinned(kind: Kind, id: String): Boolean = + _pinned.value.any { it.kind == kind && it.id == id } + + fun togglePin(pin: Pin) { + if (isPinned(pin.kind, pin.id)) unpin(pin.kind, pin.id) else pin(pin) + } + fun unpin(kind: Kind, id: String) { _pinned.value = _pinned.value.filterNot { it.kind == kind && it.id == id } persist() } - fun isPinned(kind: Kind, id: String): Boolean = - _pinned.value.any { it.kind == kind && it.id == id } - private fun load() { if (!file.exists()) return runCatching { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt index 2713bee1..93572f6e 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt @@ -32,8 +32,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.dialog.InWindowPanel import capital.yuri.yuriplayer.core.artist.ArtistImageCandidate import capital.yuri.yuriplayer.core.artist.ArtistInfoClient @@ -57,11 +57,10 @@ fun ArtistBannerPicker( loading = false } - Dialog(onDismissRequest = onDismiss) { + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.width(640.dp)) { Surface( shape = RoundedCornerShape(16.dp), - tonalElevation = 6.dp, - modifier = Modifier.width(640.dp) + tonalElevation = 0.dp ) { Column(Modifier.padding(20.dp)) { Text("Choose a header", style = MaterialTheme.typography.titleLarge) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt index c165486e..66ccc632 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt @@ -67,7 +67,9 @@ fun DesktopExplore( onOpenAlbum: (AlbumPageModel) -> Unit, onOpenPlaylist: (DesktopPlaylist) -> Unit, onOpenArtist: (String) -> Unit, - onPlaySongs: (List, Int) -> Unit + onPlaySongs: (List, Int) -> Unit, + likedIds: Set = emptySet(), + onToggleLike: (String) -> Unit = {} ) { val remotes by session.sources.remotes.collectAsState() val enabled = remember(remotes) { remotes.filter { it.enabled } } @@ -347,7 +349,9 @@ fun DesktopExplore( itemsIndexed(songHits, key = { _, t -> t.id }) { index, track -> TrackRow( track = track.toRow(), - onClick = { onPlaySongs(songHits, index) } + onClick = { onPlaySongs(songHits, index) }, + liked = track.id in likedIds, + onToggleLike = { onToggleLike(track.id) } ) } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt index 29788ca0..9420d5f9 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopProviders.kt @@ -18,7 +18,6 @@ import androidx.compose.material.icons.filled.Folder import androidx.compose.material.icons.filled.Storage import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff -import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -44,6 +43,7 @@ import capital.yuri.yuriplayer.components.settings.SettingsNote import capital.yuri.yuriplayer.components.settings.SettingsSectionTitle import capital.yuri.yuriplayer.components.settings.SettingsSwitchRow import capital.yuri.yuriplayer.components.settings.SettingsTopBar +import capital.yuri.yuriplayer.components.dialog.InWindowPanel import capital.yuri.yuriplayer.core.source.RemoteAccount import capital.yuri.yuriplayer.core.source.SourceKind import capital.yuri.yuriplayer.desktop.DesktopSession @@ -372,25 +372,26 @@ private fun ProviderEditor( } if (confirmDelete && existing != null) { - AlertDialog( - onDismissRequest = { confirmDelete = false }, - title = { Text("Delete provider?") }, - text = { - Text("“${label.ifBlank { kindLabel(kind) }}” will be removed. Local files are unaffected.") - }, - confirmButton = { - TextButton( - onClick = { - session.removeRemote(existing.id) - confirmDelete = false - onBack() - } - ) { Text("Delete") } - }, - dismissButton = { - TextButton(onClick = { confirmDelete = false }) { Text("Cancel") } + InWindowPanel(onDismiss = { confirmDelete = false }, modifier = Modifier.padding(24.dp)) { + Column(Modifier.padding(24.dp).fillMaxWidth()) { + Text("Delete provider?", style = MaterialTheme.typography.titleLarge) + Text( + "“${label.ifBlank { kindLabel(kind) }}” will be removed. Local files are unaffected.", + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 8.dp) + ) + Row(Modifier.fillMaxWidth().padding(top = 16.dp), horizontalArrangement = Arrangement.End) { + TextButton(onClick = { confirmDelete = false }) { Text("Cancel") } + TextButton( + onClick = { + session.removeRemote(existing.id) + confirmDelete = false + onBack() + } + ) { Text("Delete") } + } } - ) + } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt index 1e5cedc4..2296d605 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt @@ -27,8 +27,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties +import capital.yuri.yuriplayer.components.dialog.InWindowDialog import capital.yuri.yuriplayer.components.settings.SettingsCategory import capital.yuri.yuriplayer.components.settings.SettingsShell import capital.yuri.yuriplayer.components.theme.AccentPicker @@ -43,10 +42,7 @@ fun DesktopSettingsDialog( onDismiss: () -> Unit ) { var category by remember { mutableStateOf(SettingsCategory.Appearance) } - Dialog( - onDismissRequest = onDismiss, - properties = DialogProperties(usePlatformDefaultWidth = false) - ) { + InWindowDialog(onDismiss = onDismiss) { SettingsShell( selected = category, onSelect = { category = it }, diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt index 0e481b26..9cdb883a 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt @@ -30,7 +30,7 @@ import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.DialogWindow +import capital.yuri.yuriplayer.components.dialog.InWindowPanel import java.awt.image.BufferedImage import java.io.File import javax.imageio.ImageIO @@ -56,8 +56,8 @@ fun ImageCropDialog( var zoom by remember { mutableFloatStateOf(1f) } var pan by remember { mutableStateOf(Offset.Zero) } - DialogWindow(onCloseRequest = onCancel, title = title) { - Surface(Modifier.size(560.dp, 640.dp), color = MaterialTheme.colorScheme.surface) { + InWindowPanel(onDismiss = onCancel, modifier = Modifier.size(560.dp, 640.dp)) { + Surface(color = MaterialTheme.colorScheme.surface) { Column(Modifier.fillMaxSize().padding(16.dp)) { Text(title, style = MaterialTheme.typography.titleLarge) Text( diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt index 9b276a34..69f15b78 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt @@ -20,8 +20,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.DialogWindow import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.dialog.InWindowPanel import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.desktop.TagWriter @@ -54,8 +54,8 @@ fun EditSongDialog( ) } - DialogWindow(onCloseRequest = onDismiss, title = "Edit song") { - Surface(Modifier.size(480.dp, 520.dp)) { + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.size(480.dp, 520.dp)) { + Surface { Column(Modifier.padding(20.dp)) { Text("Edit song", style = MaterialTheme.typography.titleLarge) if (!writable) { @@ -150,8 +150,8 @@ fun EditAlbumDialog( ) } - DialogWindow(onCloseRequest = onDismiss, title = "Edit album") { - Surface(Modifier.size(480.dp, 560.dp)) { + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.size(480.dp, 560.dp)) { + Surface { Column(Modifier.padding(20.dp)) { Text("Edit album", style = MaterialTheme.typography.titleLarge) CoverArt( diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt index 4fb689a8..7910a3e0 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -43,8 +43,9 @@ 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 androidx.compose.ui.window.DialogWindow import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.dialog.InWindowPanel +import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.toRow import capital.yuri.yuriplayer.core.library.Track @@ -63,7 +64,11 @@ fun PlaylistPage( store: DesktopPlaylistStore, onBack: () -> Unit, onPlay: (List, Int) -> Unit, - onEditTrack: (Track) -> Unit + onEditTrack: (Track) -> Unit, + likedIds: Set = emptySet(), + onToggleLike: (String) -> Unit = {}, + playlistLiked: Boolean = false, + onTogglePlaylistLike: () -> Unit = {} ) { var name by remember(playlist.id, playlist.updatedAtMs) { mutableStateOf(playlist.name) } var description by remember(playlist.id, playlist.updatedAtMs) { @@ -150,13 +155,18 @@ fun PlaylistPage( modifier = Modifier.padding(top = 8.dp) ) } - FilledIconButton( - onClick = { if (tracks.isNotEmpty()) onPlay(tracks, 0) }, - modifier = Modifier.padding(top = 16.dp) + Row( + Modifier.padding(top = 16.dp), + verticalAlignment = Alignment.CenterVertically ) { - Icon(Icons.Default.PlayArrow, contentDescription = "Play") + FilledIconButton( + onClick = { if (tracks.isNotEmpty()) onPlay(tracks, 0) } + ) { + Icon(Icons.Default.PlayArrow, contentDescription = "Play") + } + LikeHeart(liked = playlistLiked, onToggle = onTogglePlaylistLike) + TextButton(onClick = { showAdd = true }) { Text("Add songs") } } - TextButton(onClick = { showAdd = true }) { Text("Add songs") } } } Spacer(Modifier.height(16.dp)) @@ -167,7 +177,9 @@ fun PlaylistPage( onClick = { onPlay(tracks, index) }, onLongClick = { onEditTrack(track) }, showCover = true, - showAlbum = true + showAlbum = true, + liked = track.id in likedIds, + onToggleLike = { onToggleLike(track.id) } ) Row(Modifier.fillMaxWidth().padding(horizontal = 8.dp), horizontalArrangement = Arrangement.End) { TextButton(onClick = { @@ -226,8 +238,8 @@ private fun AddSongsDialog( if (q.isEmpty()) library.take(40) else library.filter { it.matchesQuery(q) }.take(40) } - DialogWindow(onCloseRequest = onDismiss, title = "Add songs") { - Surface(Modifier.size(520.dp, 560.dp)) { + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.size(520.dp, 560.dp)) { + Surface { Column(Modifier.padding(16.dp)) { OutlinedTextField( value = query, @@ -260,8 +272,8 @@ private fun CoverManagerDialog( onDismiss: () -> Unit, onPick: (secret: Boolean) -> Unit ) { - DialogWindow(onCloseRequest = onDismiss, title = "Playlist covers") { - Surface(Modifier.size(560.dp, 420.dp)) { + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.size(560.dp, 420.dp)) { + Surface { Column(Modifier.fillMaxSize().padding(20.dp)) { Text("Covers", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) Text( @@ -364,8 +376,8 @@ fun NewPlaylistDialog( ) { var name by remember { mutableStateOf("") } var description by remember { mutableStateOf("") } - DialogWindow(onCloseRequest = onDismiss, title = "New playlist") { - Surface(Modifier.size(420.dp, 280.dp)) { + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.size(420.dp, 280.dp)) { + Surface { Column(Modifier.padding(20.dp)) { Text("New playlist", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) OutlinedTextField( diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 6429b5c5..66a990dd 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -338,7 +338,11 @@ fun YuriDesktopApp(session: DesktopSession) { val base = queue.indexOfFirst { it.id == cur.id } if (base < 0) return@NowPlayingSidebar session.player.moveQueueItem(base + 1 + from, base + 1 + to) - } + }, + liked = current?.id in liked, + onToggleLike = { current?.id?.let(session.collection::toggleLike) }, + likedIds = liked, + onToggleTrackLike = session.collection::toggleLike ) } } @@ -366,7 +370,9 @@ fun YuriDesktopApp(session: DesktopSession) { onOpenAlbum = ::openAlbum, onOpenPlaylist = { push(Route.Playlist(it.id)) }, onOpenArtist = ::openArtist, - onPlaySongs = { list, i -> session.player.play(list, i) } + onPlaySongs = { list, i -> session.player.play(list, i) }, + likedIds = liked, + onToggleLike = session.collection::toggleLike ) is Route.Album -> { val live = albums.firstOrNull { it.id == r.album.id } ?: r.album @@ -391,7 +397,20 @@ fun YuriDesktopApp(session: DesktopSession) { onEditTrack = { i -> albumTracks.getOrNull(i)?.let { editSong = it } }, - onArtist = ::openArtist + onArtist = ::openArtist, + liked = session.collection.isPinned(DesktopCollection.Kind.ALBUM, live.id), + onToggleLike = { + session.collection.togglePin( + DesktopCollection.Pin( + DesktopCollection.Kind.ALBUM, + live.id, + live.title, + live.artist + ) + ) + }, + likedTrackIds = liked, + onToggleTrackLike = session.collection::toggleLike ) } is Route.Artist -> { @@ -446,7 +465,20 @@ fun YuriDesktopApp(session: DesktopSession) { onFetchHeader = { pickBanner = true }, onClearHeader = { profile = session.artists.clearBanner(r.name) - } + }, + liked = session.collection.isPinned(DesktopCollection.Kind.ARTIST, r.name), + onToggleLike = { + session.collection.togglePin( + DesktopCollection.Pin( + DesktopCollection.Kind.ARTIST, + r.name, + r.name, + "Artist" + ) + ) + }, + likedTrackIds = liked, + onToggleTrackLike = session.collection::toggleLike ) if (pickBanner) { ArtistBannerPicker( @@ -477,7 +509,20 @@ fun YuriDesktopApp(session: DesktopSession) { store = session.playlists, onBack = ::goBack, onPlay = { list, i -> session.player.play(list, i) }, - onEditTrack = { editSong = it } + onEditTrack = { editSong = it }, + likedIds = liked, + onToggleLike = session.collection::toggleLike, + playlistLiked = session.collection.isPinned(DesktopCollection.Kind.PLAYLIST, pl.id), + onTogglePlaylistLike = { + session.collection.togglePin( + DesktopCollection.Pin( + DesktopCollection.Kind.PLAYLIST, + pl.id, + pl.name, + "Playlist" + ) + ) + } ) } } From 7d0ed5ac08a1a9cc9e588ac1b9a92f9196d5a714 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 21:34:52 +0000 Subject: [PATCH 37/69] Add right-click menus on pins and song rows Songs: add to playlist, add to queue, go to album (off album pages), remove from this playlist. Add-to-playlist is an in-window search/checkbox dialog matching mobile. Pins can play, queue, unpin. --- .../yuriplayer/components/album/AlbumPage.kt | 21 +- .../components/artist/ArtistPage.kt | 71 +++--- .../components/layout/DesktopBentoLayout.kt | 7 + .../yuriplayer/components/list/ContextMenu.kt | 90 +++++++ .../yuriplayer/components/list/TrackRow.kt | 29 ++- .../components/player/NowPlayingSidebar.kt | 3 + .../components/player/QueuePanel.kt | 8 +- .../yuriplayer/core/player/PlayerSession.kt | 21 ++ .../core/player/PlayerSessionTest.kt | 10 + .../desktop/DesktopPlaylistStore.kt | 3 + .../desktop/ui/AddToPlaylistDialog.kt | 191 +++++++++++++++ .../yuriplayer/desktop/ui/DesktopExplore.kt | 7 +- .../yuriplayer/desktop/ui/PlaylistPage.kt | 7 +- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 219 +++++++++++++++--- 14 files changed, 599 insertions(+), 88 deletions(-) create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt index 65f50b61..c1e28419 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt @@ -34,9 +34,11 @@ import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.layout.WindowWidthClass import capital.yuri.yuriplayer.components.layout.windowWidthClass +import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.AlbumPageModel +import capital.yuri.yuriplayer.components.model.TrackRowModel import capital.yuri.yuriplayer.components.theme.AlbumArtBackdrop import capital.yuri.yuriplayer.components.theme.rememberCoverColors @@ -54,6 +56,7 @@ fun AlbumPage( onToggleLike: () -> Unit = {}, likedTrackIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, + songMenu: (TrackRowModel) -> List = { emptyList() }, modifier: Modifier = Modifier ) { val coverColors by rememberCoverColors( @@ -72,12 +75,12 @@ fun AlbumPage( when (widthClass) { WindowWidthClass.Compact -> AlbumPageCompact( album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist, - liked, onToggleLike, likedTrackIds, onToggleTrackLike + liked, onToggleLike, likedTrackIds, onToggleTrackLike, songMenu ) WindowWidthClass.Medium, WindowWidthClass.Expanded -> AlbumPageWide( album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist, - liked, onToggleLike, likedTrackIds, onToggleTrackLike + liked, onToggleLike, likedTrackIds, onToggleTrackLike, songMenu ) } } @@ -97,7 +100,8 @@ private fun AlbumPageCompact( liked: Boolean, onToggleLike: () -> Unit, likedTrackIds: Set, - onToggleTrackLike: (String) -> Unit + onToggleTrackLike: (String) -> Unit, + songMenu: (TrackRowModel) -> List ) { Column(Modifier.fillMaxSize()) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -124,7 +128,7 @@ private fun AlbumPageCompact( corner = 12.dp ) AlbumMeta(album, playing, onPlay, onArtist, liked, onToggleLike, Modifier.padding(20.dp)) - TrackList(album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike) + TrackList(album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike, songMenu) } } @@ -141,7 +145,8 @@ private fun AlbumPageWide( liked: Boolean, onToggleLike: () -> Unit, likedTrackIds: Set, - onToggleTrackLike: (String) -> Unit + onToggleTrackLike: (String) -> Unit, + songMenu: (TrackRowModel) -> List ) { Row(Modifier.fillMaxSize().padding(24.dp)) { Column( @@ -171,7 +176,7 @@ private fun AlbumPageWide( Spacer(Modifier.height(16.dp)) AlbumMeta(album, playing, onPlay, onArtist, liked, onToggleLike) } - TrackList(album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike, modifier = Modifier.weight(1f)) + TrackList(album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike, songMenu, modifier = Modifier.weight(1f)) } } @@ -228,6 +233,7 @@ private fun TrackList( onEditTrack: (Int) -> Unit = {}, likedTrackIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, + songMenu: (TrackRowModel) -> List = { emptyList() }, modifier: Modifier = Modifier ) { val discs = album.tracks.groupBy { it.discNumber ?: 1 } @@ -253,7 +259,8 @@ private fun TrackList( showCover = false, showAlbum = false, liked = track.id in likedTrackIds, - onToggleLike = { onToggleTrackLike(track.id) } + onToggleLike = { onToggleTrackLike(track.id) }, + contextItems = songMenu(track) ) } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt index 3e0b7d53..08a71067 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt @@ -61,6 +61,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.ContextAction +import capital.yuri.yuriplayer.components.list.ContextMenuAnchor import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.list.AlbumCard import capital.yuri.yuriplayer.components.list.TrackRow @@ -87,7 +89,8 @@ fun ArtistPage( liked: Boolean = false, onToggleLike: () -> Unit = {}, likedTrackIds: Set = emptySet(), - onToggleTrackLike: (String) -> Unit = {} + onToggleTrackLike: (String) -> Unit = {}, + songMenu: (TrackRowModel) -> List = { emptyList() } ) { val listState = rememberLazyListState() val mini by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } @@ -182,7 +185,8 @@ fun ArtistPage( track = track, liked = track.id in likedTrackIds, onToggleLike = { onToggleTrackLike(track.id) }, - onClick = { onTrack(i) } + onClick = { onTrack(i) }, + menu = songMenu(track) ) } } @@ -453,37 +457,40 @@ private fun PopularRow( track: TrackRowModel, liked: Boolean, onToggleLike: () -> Unit, - onClick: () -> Unit + onClick: () -> Unit, + menu: List = emptyList() ) { - Row( - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(4.dp)) - .clickable(onClick = onClick) - .padding(vertical = 6.dp, horizontal = 4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - index.toString(), - modifier = Modifier.width(28.dp), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) - ) - CoverArt(model = track.artworkUri, size = 40.dp, corner = 4.dp) - Spacer(Modifier.width(12.dp)) - Text( - track.title, - modifier = Modifier.weight(1f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontWeight = FontWeight.Medium - ) - LikeHeart(liked = liked, onToggle = onToggleLike) - Text( - formatTime(track.durationMs ?: 0L), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) - ) + ContextMenuAnchor(items = menu) { openMenu -> + Row( + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(4.dp)) + .clickable(onClick = onClick) + .padding(vertical = 6.dp, horizontal = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + index.toString(), + modifier = Modifier.width(28.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + CoverArt(model = track.artworkUri, size = 40.dp, corner = 4.dp) + Spacer(Modifier.width(12.dp)) + Text( + track.title, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Medium + ) + LikeHeart(liked = liked, onToggle = onToggleLike) + Text( + formatTime(track.durationMs ?: 0L), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt index 45b1baea..164d90db 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt @@ -63,6 +63,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.ContextAction +import capital.yuri.yuriplayer.components.list.ContextMenuAnchor enum class DesktopNav { Home, Search } @@ -94,6 +96,7 @@ fun DesktopBentoLayout( libraryItems: List, selectedLibraryId: String?, onLibraryItem: (LibraryRailItem) -> Unit, + libraryMenu: (LibraryRailItem) -> List = { emptyList() }, bottomBar: @Composable () -> Unit, sidebar: @Composable () -> Unit, modifier: Modifier = Modifier, @@ -171,6 +174,7 @@ fun DesktopBentoLayout( items = libraryItems, selectedId = selectedLibraryId, onItem = onLibraryItem, + itemMenu = libraryMenu, modifier = Modifier.width(with(density) { leftPx.toDp() }).fillMaxHeight() ) ResizeHandle( @@ -371,6 +375,7 @@ private fun LibraryRail( items: List, selectedId: String?, onItem: (LibraryRailItem) -> Unit, + itemMenu: (LibraryRailItem) -> List = { emptyList() }, modifier: Modifier = Modifier ) { Surface( @@ -428,6 +433,7 @@ private fun LibraryRail( LazyColumn(Modifier.weight(1f)) { items(items, key = { it.id }) { item -> val selected = item.id == selectedId + ContextMenuAnchor(items = itemMenu(item)) { openMenu -> Row( Modifier .fillMaxWidth() @@ -472,6 +478,7 @@ private fun LibraryRail( ) } } + } } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt new file mode 100644 index 00000000..4a3cd020 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt @@ -0,0 +1,90 @@ +package capital.yuri.yuriplayer.components.list + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.isSecondaryPressed +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp + +data class ContextAction( + val label: String, + val destructive: Boolean = false, + val enabled: Boolean = true, + val onClick: () -> Unit +) + +@Composable +fun ContextMenuAnchor( + items: List, + modifier: Modifier = Modifier, + onLongPressOpen: Boolean = true, + content: @Composable BoxScope.(openMenu: () -> Unit) -> Unit +) { + var open by remember { mutableStateOf(false) } + var offset by remember { mutableStateOf(DpOffset.Zero) } + val density = LocalDensity.current + val openMenu: () -> Unit = { + offset = DpOffset(12.dp, 8.dp) + open = true + } + Box( + modifier.pointerInput(items) { + if (items.isEmpty()) return@pointerInput + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + if (event.type != PointerEventType.Press) continue + if (!event.buttons.isSecondaryPressed) continue + val change = event.changes.firstOrNull() ?: continue + change.consume() + offset = with(density) { + DpOffset(change.position.x.toDp(), change.position.y.toDp()) + } + open = true + } + } + } + ) { + content(if (onLongPressOpen && items.isNotEmpty()) openMenu else ({ })) + if (items.isNotEmpty()) { + DropdownMenu( + expanded = open, + onDismissRequest = { open = false }, + offset = offset + ) { + items.forEach { action -> + DropdownMenuItem( + text = { + Text( + action.label, + color = if (action.destructive) { + MaterialTheme.colorScheme.error + } else { + MaterialTheme.colorScheme.onSurface + } + ) + }, + enabled = action.enabled, + onClick = { + open = false + action.onClick() + } + ) + } + } + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt index 362a3b0c..6f5de45c 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt @@ -30,17 +30,27 @@ fun TrackRow( showAlbum: Boolean = true, onLongClick: (() -> Unit)? = null, liked: Boolean = false, - onToggleLike: (() -> Unit)? = null + onToggleLike: (() -> Unit)? = null, + contextItems: List = emptyList() ) { val highlight = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) - Row( - modifier = modifier - .fillMaxWidth() - .background(if (track.highlighted) highlight else MaterialTheme.colorScheme.surface.copy(alpha = 0f)) - .combinedClickable(onClick = onClick, onLongClick = onLongClick) - .padding(horizontal = 16.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { + ContextMenuAnchor(items = contextItems) { openMenu -> + Row( + modifier = modifier + .fillMaxWidth() + .background(if (track.highlighted) highlight else MaterialTheme.colorScheme.surface.copy(alpha = 0f)) + .combinedClickable( + onClick = onClick, + onLongClick = { + when { + contextItems.isNotEmpty() -> openMenu() + onLongClick != null -> onLongClick() + } + } + ) + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { if (track.trackNumber != null && !showCover) { Text( track.trackNumber.toString(), @@ -79,6 +89,7 @@ fun TrackRow( color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) ) } + } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index 8b9f5bcf..f6db7d21 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.model.CoverRef import capital.yuri.yuriplayer.components.model.TrackRowModel @@ -38,6 +39,7 @@ fun NowPlayingSidebar( onToggleLike: () -> Unit = {}, likedIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, + songMenu: (TrackRowModel) -> List = { emptyList() }, modifier: Modifier = Modifier ) { Column( @@ -103,6 +105,7 @@ fun NowPlayingSidebar( onMove = onMoveUpcoming, likedIds = likedIds, onToggleLike = onToggleTrackLike, + songMenu = songMenu, modifier = Modifier.weight(1f) ) } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt index fcf55ced..e3f25e68 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.CoverRef import capital.yuri.yuriplayer.components.model.TrackRowModel @@ -51,6 +52,7 @@ fun QueuePanel( onMove: ((from: Int, to: Int) -> Unit)? = null, likedIds: Set = emptySet(), onToggleLike: (String) -> Unit = {}, + songMenu: (TrackRowModel) -> List = { emptyList() }, modifier: Modifier = Modifier ) { var tab by remember { mutableStateOf(QueueTab.Queue) } @@ -135,7 +137,8 @@ fun QueuePanel( showCover = true, showAlbum = false, liked = row.id in likedIds, - onToggleLike = { onToggleLike(row.id) } + onToggleLike = { onToggleLike(row.id) }, + contextItems = songMenu(row) ) } } @@ -166,7 +169,8 @@ fun QueuePanel( showCover = true, showAlbum = true, liked = row.id in likedIds, - onToggleLike = { onToggleLike(row.id) } + onToggleLike = { onToggleLike(row.id) }, + contextItems = songMenu(row) ) } } diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt index a22bd9a9..296eb15a 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -155,6 +155,27 @@ class PlayerSession( warmSuccessor() } + fun enqueue(track: Track) { + if (_queue.value.isEmpty() && _current.value == null) { + play(listOf(track), 0) + return + } + _queue.value = _queue.value + track + linear = linear + track + warmSuccessor() + } + + fun enqueueAll(tracks: List) { + if (tracks.isEmpty()) return + if (_queue.value.isEmpty() && _current.value == null) { + play(tracks, 0) + return + } + _queue.value = _queue.value + tracks + linear = linear + tracks + warmSuccessor() + } + fun moveQueueItem(from: Int, to: Int) { val q = _queue.value.toMutableList() if (from !in q.indices || to !in q.indices || from == to) return diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt index 2f6fbb6e..59d408b8 100644 --- a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt @@ -60,6 +60,16 @@ class PlayerSessionTest { assertEquals("4", session.current.value?.id) assertEquals(list.map { it.id }, session.queue.value.map { it.id }) } + + @Test + fun enqueueAppendsWithoutChangingCurrent() { + val engine = FakeEngine() + val session = PlayerSession(engine) + session.play(tracks(2), 0) + session.enqueue(Track(id = "9", uri = "file://9", title = "t9")) + assertEquals("1", session.current.value?.id) + assertEquals(listOf("1", "2", "9"), session.queue.value.map { it.id }) + } } private class FakeEngine : PlaybackEngine { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt index 7b704ee1..2604d92b 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -67,6 +67,9 @@ class DesktopPlaylistStore(configDir: String) { update(id) { it.copy(trackIds = (it.trackIds + trackIds).distinct()) } } + fun playlistsContaining(trackId: String): Set = + _playlists.value.filter { trackId in it.trackIds }.map { it.id }.toSet() + fun removeTracks(id: String, trackIds: List) { val drop = trackIds.toHashSet() update(id) { it.copy(trackIds = it.trackIds.filterNot { id -> id in drop }) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt new file mode 100644 index 00000000..a638fa80 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt @@ -0,0 +1,191 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Button +import androidx.compose.material3.Checkbox +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.dialog.InWindowPanel +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.desktop.DesktopPlaylistStore + +@Composable +fun AddToPlaylistDialog( + tracks: List, + store: DesktopPlaylistStore, + library: List, + onDismiss: () -> Unit +) { + val playlists by store.playlists.collectAsState() + val ids = remember(tracks) { tracks.map { it.id } } + var initiallyIn by remember { mutableStateOf(emptySet()) } + var selected by remember { mutableStateOf(emptySet()) } + var query by remember { mutableStateOf("") } + var creating by remember { mutableStateOf(false) } + var newName by remember { mutableStateOf("") } + + LaunchedEffect(ids) { + val containing = if (ids.size == 1) { + store.playlistsContaining(ids.first()) + } else { + playlists.filter { pl -> ids.all { it in pl.trackIds } }.map { it.id }.toSet() + } + initiallyIn = containing + selected = containing + } + + val visible = remember(playlists, query, selected) { + val q = query.trim() + val filtered = if (q.isEmpty()) playlists + else playlists.filter { it.name.contains(q, ignoreCase = true) } + filtered.sortedWith( + compareByDescending { it.id in selected } + .thenBy(String.CASE_INSENSITIVE_ORDER) { it.name } + ) + } + + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.width(420.dp).height(560.dp)) { + Column(Modifier.padding(20.dp)) { + Text("Add to playlist", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text( + if (tracks.size == 1) tracks.first().displayTitle else "${tracks.size} tracks", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + modifier = Modifier.padding(top = 4.dp, bottom = 12.dp) + ) + if (creating) { + OutlinedTextField( + value = newName, + onValueChange = { newName = it }, + label = { Text("Playlist name") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + Row(Modifier.fillMaxWidth().padding(top = 8.dp), horizontalArrangement = Arrangement.End) { + TextButton(onClick = { creating = false; newName = "" }) { Text("Cancel") } + Button( + onClick = { + val pl = store.create(newName, trackIds = ids) + selected = selected + pl.id + initiallyIn = initiallyIn + pl.id + creating = false + newName = "" + }, + enabled = newName.isNotBlank() + ) { Text("Create") } + } + } else { + OutlinedButton( + onClick = { creating = true }, + modifier = Modifier.fillMaxWidth() + ) { + Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(18.dp)) + Spacer(Modifier.width(8.dp)) + Text("New playlist") + } + } + OutlinedTextField( + value = query, + onValueChange = { query = it }, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp), + singleLine = true, + placeholder = { Text("Find a playlist") }, + trailingIcon = { + if (query.isNotEmpty()) { + IconButton(onClick = { query = "" }) { + Icon(Icons.Default.Clear, contentDescription = "Clear") + } + } + } + ) + if (visible.isEmpty()) { + Text( + if (playlists.isEmpty()) "No playlists yet — create one above." + else "No playlists match \"$query\".", + modifier = Modifier.padding(vertical = 24.dp), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } else { + LazyColumn(Modifier.weight(1f).padding(top = 8.dp)) { + items(visible, key = { it.id }) { pl -> + val checked = pl.id in selected + Row( + Modifier + .fillMaxWidth() + .clickable { + selected = if (checked) selected - pl.id else selected + pl.id + } + .padding(vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = checked, + onCheckedChange = { + selected = if (it) selected + pl.id else selected - pl.id + } + ) + CoverArt( + model = pl.artworkUri(library), + size = 40.dp, + corner = 6.dp + ) + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f)) { + Text(pl.name, maxLines = 1, overflow = TextOverflow.Ellipsis) + Text( + "${pl.trackIds.size} songs", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + } + } + } + } + Button( + onClick = { + val toAdd = selected - initiallyIn + val toRemove = initiallyIn - selected + toAdd.forEach { store.addTracks(it, ids) } + toRemove.forEach { store.removeTracks(it, ids) } + onDismiss() + }, + modifier = Modifier.fillMaxWidth().padding(top = 12.dp) + ) { + Text("Done") + } + } + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt index 66ccc632..acf283f7 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.model.albums @@ -69,7 +70,8 @@ fun DesktopExplore( onOpenArtist: (String) -> Unit, onPlaySongs: (List, Int) -> Unit, likedIds: Set = emptySet(), - onToggleLike: (String) -> Unit = {} + onToggleLike: (String) -> Unit = {}, + songMenu: (Track) -> List = { emptyList() } ) { val remotes by session.sources.remotes.collectAsState() val enabled = remember(remotes) { remotes.filter { it.enabled } } @@ -351,7 +353,8 @@ fun DesktopExplore( track = track.toRow(), onClick = { onPlaySongs(songHits, index) }, liked = track.id in likedIds, - onToggleLike = { onToggleLike(track.id) } + onToggleLike = { onToggleLike(track.id) }, + contextItems = songMenu(track) ) } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt index 7910a3e0..e57ba0a7 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.dialog.InWindowPanel +import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.model.toRow @@ -68,7 +69,8 @@ fun PlaylistPage( likedIds: Set = emptySet(), onToggleLike: (String) -> Unit = {}, playlistLiked: Boolean = false, - onTogglePlaylistLike: () -> Unit = {} + onTogglePlaylistLike: () -> Unit = {}, + songMenu: (Track) -> List = { emptyList() } ) { var name by remember(playlist.id, playlist.updatedAtMs) { mutableStateOf(playlist.name) } var description by remember(playlist.id, playlist.updatedAtMs) { @@ -179,7 +181,8 @@ fun PlaylistPage( showCover = true, showAlbum = true, liked = track.id in likedIds, - onToggleLike = { onToggleLike(track.id) } + onToggleLike = { onToggleLike(track.id) }, + contextItems = songMenu(track) ) Row(Modifier.fillMaxWidth().padding(horizontal = 8.dp), horizontalArrangement = Arrangement.End) { TextButton(onClick = { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 66a990dd..bc05f869 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -48,6 +48,8 @@ import capital.yuri.yuriplayer.components.layout.DesktopNav import capital.yuri.yuriplayer.components.layout.LibraryFilter import capital.yuri.yuriplayer.components.layout.LibraryRailItem import capital.yuri.yuriplayer.components.list.AlbumCard +import capital.yuri.yuriplayer.components.list.ContextAction +import capital.yuri.yuriplayer.components.list.ContextMenuAnchor import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.model.albums import capital.yuri.yuriplayer.components.model.artistPage @@ -107,6 +109,7 @@ fun YuriDesktopApp(session: DesktopSession) { var showSidebar by remember { mutableStateOf(true) } var editSong by remember { mutableStateOf(null) } var editAlbum by remember { mutableStateOf(null) } + var addToPlaylist by remember { mutableStateOf?>(null) } val route = stack.last() val nav = if (route is Route.Search) DesktopNav.Search else DesktopNav.Home val scope = rememberCoroutineScope() @@ -132,6 +135,46 @@ fun YuriDesktopApp(session: DesktopSession) { fun openAlbum(album: AlbumPageModel) = push(Route.Album(album)) fun openArtist(name: String) = push(Route.Artist(name)) + fun songMenu( + track: Track, + onAlbumPage: Boolean = false, + playlistId: String? = null + ): List = buildList { + add(ContextAction("Add to playlist") { addToPlaylist = listOf(track) }) + add(ContextAction("Add to queue") { session.player.enqueue(track) }) + if (!onAlbumPage) { + add( + ContextAction("Go to album") { + albums.firstOrNull { a -> a.tracks.any { it.id == track.id } }?.let(::openAlbum) + } + ) + } + if (playlistId != null) { + add( + ContextAction("Remove from this playlist", destructive = true) { + session.playlists.removeTracks(playlistId, listOf(track.id)) + } + ) + } + } + + fun pinTracks(item: LibraryRailItem): List = when { + item.id == "liked" -> tracks.filter { it.id in liked } + item.id.startsWith("playlist:") -> { + val id = item.id.removePrefix("playlist:") + playlists.firstOrNull { it.id == id }?.tracks(tracks).orEmpty() + } + item.id.startsWith("artist:") || item.circular -> + tracks.filter { + it.displayArtist.equals(item.title, true) || it.albumArtist.equals(item.title, true) + } + else -> { + val album = albums.firstOrNull { it.id == item.id } + album?.tracks?.mapNotNull { row -> tracks.firstOrNull { it.id == row.id } }.orEmpty() + .ifEmpty { tracks.filter { it.id == item.id }.takeIf { it.isNotEmpty() } ?: emptyList() } + } + } + val recency = remember(history) { history.mapIndexed { i, t -> t.id to i }.toMap() } @@ -273,6 +316,36 @@ fun YuriDesktopApp(session: DesktopSession) { else -> albums.firstOrNull { it.id == item.id }?.let { openAlbum(it) } } }, + libraryMenu = { item -> + if (item.id == "new-playlist") { + emptyList() + } else { + val playable = pinTracks(item) + buildList { + if (playable.isNotEmpty()) { + add(ContextAction("Play") { session.player.play(playable, 0) }) + add(ContextAction("Add to queue") { session.player.enqueueAll(playable) }) + } + if (playable.size == 1) { + add(ContextAction("Add to playlist") { addToPlaylist = playable }) + } + if (item.pinned) { + val kind = when { + item.id.startsWith("playlist:") -> DesktopCollection.Kind.PLAYLIST + item.circular || item.id.startsWith("artist:") -> DesktopCollection.Kind.ARTIST + albums.any { it.id == item.id } -> DesktopCollection.Kind.ALBUM + else -> DesktopCollection.Kind.SONG + } + val rawId = item.id.removePrefix("playlist:").removePrefix("artist:") + add( + ContextAction("Unpin", destructive = true) { + session.collection.unpin(kind, rawId) + } + ) + } + } + } + }, showSidebar = showSidebar, leftFraction = leftFrac, rightFraction = rightFrac, @@ -342,7 +415,10 @@ fun YuriDesktopApp(session: DesktopSession) { liked = current?.id in liked, onToggleLike = { current?.id?.let(session.collection::toggleLike) }, likedIds = liked, - onToggleTrackLike = session.collection::toggleLike + onToggleTrackLike = session.collection::toggleLike, + songMenu = { row -> + tracks.firstOrNull { it.id == row.id }?.let { songMenu(it) }.orEmpty() + } ) } } @@ -359,7 +435,10 @@ fun YuriDesktopApp(session: DesktopSession) { onOpenAlbum = ::openAlbum, onOpenPlaylist = { push(Route.Playlist(it.id)) }, onOpenArtist = ::openArtist, - onPlayTracks = { list, i -> session.player.play(list, i) } + onPlayTracks = { list, i -> session.player.play(list, i) }, + onEnqueue = { session.player.enqueueAll(it) }, + onUnpin = { kind, id -> session.collection.unpin(kind, id) }, + onAddToPlaylist = { addToPlaylist = it } ) Route.Search -> DesktopExplore( session = session, @@ -372,7 +451,8 @@ fun YuriDesktopApp(session: DesktopSession) { onOpenArtist = ::openArtist, onPlaySongs = { list, i -> session.player.play(list, i) }, likedIds = liked, - onToggleLike = session.collection::toggleLike + onToggleLike = session.collection::toggleLike, + songMenu = { songMenu(it) } ) is Route.Album -> { val live = albums.firstOrNull { it.id == r.album.id } ?: r.album @@ -410,7 +490,10 @@ fun YuriDesktopApp(session: DesktopSession) { ) }, likedTrackIds = liked, - onToggleTrackLike = session.collection::toggleLike + onToggleTrackLike = session.collection::toggleLike, + songMenu = { row -> + tracks.firstOrNull { it.id == row.id }?.let { songMenu(it, onAlbumPage = true) }.orEmpty() + } ) } is Route.Artist -> { @@ -478,7 +561,10 @@ fun YuriDesktopApp(session: DesktopSession) { ) }, likedTrackIds = liked, - onToggleTrackLike = session.collection::toggleLike + onToggleTrackLike = session.collection::toggleLike, + songMenu = { row -> + tracks.firstOrNull { it.id == row.id }?.let { songMenu(it) }.orEmpty() + } ) if (pickBanner) { ArtistBannerPicker( @@ -522,7 +608,8 @@ fun YuriDesktopApp(session: DesktopSession) { "Playlist" ) ) - } + }, + songMenu = { songMenu(it, playlistId = pl.id) } ) } } @@ -568,6 +655,14 @@ fun YuriDesktopApp(session: DesktopSession) { onSaved = { session.replaceTracks(it) } ) } + addToPlaylist?.let { songs -> + AddToPlaylistDialog( + tracks = songs, + store = session.playlists, + library = tracks, + onDismiss = { addToPlaylist = null } + ) + } } } @@ -583,7 +678,10 @@ private fun HomeFeed( onOpenAlbum: (AlbumPageModel) -> Unit, onOpenPlaylist: (capital.yuri.yuriplayer.desktop.DesktopPlaylist) -> Unit, onOpenArtist: (String) -> Unit, - onPlayTracks: (List, Int) -> Unit + onPlayTracks: (List, Int) -> Unit, + onEnqueue: (List) -> Unit = {}, + onUnpin: (DesktopCollection.Kind, String) -> Unit = { _, _ -> }, + onAddToPlaylist: (List) -> Unit = {} ) { val recentAlbums = remember(recents, albums) { recents.mapNotNull { t -> @@ -594,7 +692,8 @@ private fun HomeFeed( val shortcuts = remember(pins, liked, likedTracks, albums, playlists, tracks) { buildHomeShortcuts( pins, liked, likedTracks, albums, playlists, tracks, - onOpenAlbum, onOpenPlaylist, onOpenArtist, onPlayTracks + onOpenAlbum, onOpenPlaylist, onOpenArtist, onPlayTracks, + onEnqueue, onUnpin, onAddToPlaylist ) } val greeting = remember { @@ -757,7 +856,8 @@ private data class SpotifyPin( val title: String, val artworkUri: String?, val circular: Boolean = false, - val onClick: () -> Unit + val onClick: () -> Unit, + val menu: List = emptyList() ) private fun buildHomeShortcuts( @@ -770,7 +870,10 @@ private fun buildHomeShortcuts( onOpenAlbum: (AlbumPageModel) -> Unit, onOpenPlaylist: (capital.yuri.yuriplayer.desktop.DesktopPlaylist) -> Unit, onOpenArtist: (String) -> Unit, - onPlayTracks: (List, Int) -> Unit + onPlayTracks: (List, Int) -> Unit, + onEnqueue: (List) -> Unit, + onUnpin: (DesktopCollection.Kind, String) -> Unit, + onAddToPlaylist: (List) -> Unit ): List { val out = ArrayList(8) fun add(pin: SpotifyPin) { @@ -778,13 +881,34 @@ private fun buildHomeShortcuts( if (out.any { it.id == pin.id }) return out += pin } + fun menu( + kind: DesktopCollection.Kind?, + id: String, + playable: List, + play: () -> Unit + ) = buildList { + if (playable.isNotEmpty() || kind == null) { + add(ContextAction("Play", onClick = play)) + } + if (playable.isNotEmpty()) { + add(ContextAction("Add to queue") { onEnqueue(playable) }) + } + if (playable.size == 1) { + add(ContextAction("Add to playlist") { onAddToPlaylist(playable) }) + } + if (kind != null) { + add(ContextAction("Unpin", destructive = true) { onUnpin(kind, id) }) + } + } if (liked.isNotEmpty()) { add( SpotifyPin( id = "liked", title = "Liked Songs", - artworkUri = likedTracks.firstOrNull()?.artworkUri - ) { onPlayTracks(likedTracks, 0) } + artworkUri = likedTracks.firstOrNull()?.artworkUri, + onClick = { onPlayTracks(likedTracks, 0) }, + menu = menu(null, "liked", likedTracks) { onPlayTracks(likedTracks, 0) } + ) ) } for (pin in pins) { @@ -792,34 +916,58 @@ private fun buildHomeShortcuts( DesktopCollection.Kind.ALBUM -> { val album = albums.firstOrNull { it.id == pin.id } ?: albums.firstOrNull { it.title.equals(pin.title, true) } + val playable = album?.tracks?.mapNotNull { row -> tracks.firstOrNull { it.id == row.id } }.orEmpty() add( - SpotifyPin(pin.id, pin.title, album?.artworkUri) { - album?.let(onOpenAlbum) - } + SpotifyPin( + pin.id, pin.title, album?.artworkUri, + onClick = { album?.let(onOpenAlbum) }, + menu = menu(DesktopCollection.Kind.ALBUM, pin.id, playable) { + if (playable.isNotEmpty()) onPlayTracks(playable, 0) + else album?.let(onOpenAlbum) + } + ) + ) + } + DesktopCollection.Kind.ARTIST -> { + val playable = tracks.filter { + it.displayArtist.equals(pin.title, true) || it.albumArtist.equals(pin.title, true) + } + add( + SpotifyPin( + pin.id, + pin.title, + tracks.firstOrNull { it.displayArtist.equals(pin.title, true) }?.artworkUri, + circular = true, + onClick = { onOpenArtist(pin.title) }, + menu = menu(DesktopCollection.Kind.ARTIST, pin.id, playable) { onOpenArtist(pin.title) } + ) ) } - DesktopCollection.Kind.ARTIST -> add( - SpotifyPin( - pin.id, - pin.title, - tracks.firstOrNull { it.displayArtist.equals(pin.title, true) }?.artworkUri, - circular = true - ) { onOpenArtist(pin.title) } - ) DesktopCollection.Kind.SONG -> { val t = tracks.firstOrNull { it.id == pin.id } + val playable = listOfNotNull(t) add( - SpotifyPin(pin.id, pin.title, t?.artworkUri) { - t?.let { onPlayTracks(listOf(it), 0) } - } + SpotifyPin( + pin.id, pin.title, t?.artworkUri, + onClick = { t?.let { onPlayTracks(listOf(it), 0) } }, + menu = menu(DesktopCollection.Kind.SONG, pin.id, playable) { + t?.let { onPlayTracks(listOf(it), 0) } + } + ) ) } DesktopCollection.Kind.PLAYLIST -> { val pl = playlists.firstOrNull { it.id == pin.id } + val playable = pl?.tracks(tracks).orEmpty() add( - SpotifyPin(pin.id, pin.title, pl?.artworkUri(tracks)) { - pl?.let(onOpenPlaylist) - } + SpotifyPin( + pin.id, pin.title, pl?.artworkUri(tracks), + onClick = { pl?.let(onOpenPlaylist) }, + menu = menu(DesktopCollection.Kind.PLAYLIST, pin.id, playable) { + if (playable.isNotEmpty()) onPlayTracks(playable, 0) + else pl?.let(onOpenPlaylist) + } + ) ) } } @@ -829,11 +977,13 @@ private fun buildHomeShortcuts( @Composable private fun SpotifyPinCard(pin: SpotifyPin, modifier: Modifier = Modifier) { - Surface( - modifier = modifier - .height(72.dp) - .clip(RoundedCornerShape(6.dp)) - .clickable(onClick = pin.onClick), + ContextMenuAnchor(items = pin.menu, modifier = modifier) { + Surface( + modifier = Modifier + .fillMaxWidth() + .height(72.dp) + .clip(RoundedCornerShape(6.dp)) + .clickable(onClick = pin.onClick), color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.55f), shape = RoundedCornerShape(6.dp), tonalElevation = 2.dp @@ -859,6 +1009,7 @@ private fun SpotifyPinCard(pin: SpotifyPin, modifier: Modifier = Modifier) { color = MaterialTheme.colorScheme.onSurface ) } + } } } From 2725ae3762ebe79deedbd6b72c812827f8d0c860 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 21:40:39 +0000 Subject: [PATCH 38/69] Add context-menu DSL with prediction-cone submenus No Compose library implements Amazon/macOS safe triangles. Menus are declared as item/submenu/divider; hover uses a prediction cone so the pointer can travel into a nested menu without highlighting siblings. Song rows now nest Go to. --- components/build.gradle.kts | 3 + .../yuriplayer/components/album/AlbumPage.kt | 10 +- .../components/artist/ArtistPage.kt | 6 +- .../components/layout/DesktopBentoLayout.kt | 6 +- .../yuriplayer/components/list/ContextMenu.kt | 57 ++-- .../yuriplayer/components/list/TrackRow.kt | 3 +- .../yuriplayer/components/menu/MenuDsl.kt | 67 +++++ .../components/menu/PredictionCone.kt | 47 ++++ .../components/menu/YuriContextMenu.kt | 253 ++++++++++++++++++ .../components/player/NowPlayingSidebar.kt | 4 +- .../components/player/QueuePanel.kt | 4 +- .../components/menu/PredictionConeTest.kt | 30 +++ .../yuriplayer/desktop/ui/DesktopExplore.kt | 4 +- .../yuriplayer/desktop/ui/PlaylistPage.kt | 4 +- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 28 +- 15 files changed, 459 insertions(+), 67 deletions(-) create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/MenuDsl.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/PredictionCone.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt create mode 100644 components/src/jvmTest/kotlin/capital/yuri/yuriplayer/components/menu/PredictionConeTest.kt diff --git a/components/build.gradle.kts b/components/build.gradle.kts index 7f13cb0c..9e450f23 100644 --- a/components/build.gradle.kts +++ b/components/build.gradle.kts @@ -37,5 +37,8 @@ kotlin { jvmMain.dependencies { implementation(libs.ktor.client.cio) } + jvmTest.dependencies { + implementation(kotlin("test")) + } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt index c1e28419..80da5d45 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt @@ -34,9 +34,9 @@ import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.layout.WindowWidthClass import capital.yuri.yuriplayer.components.layout.windowWidthClass -import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.model.TrackRowModel import capital.yuri.yuriplayer.components.theme.AlbumArtBackdrop @@ -56,7 +56,7 @@ fun AlbumPage( onToggleLike: () -> Unit = {}, likedTrackIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, - songMenu: (TrackRowModel) -> List = { emptyList() }, + songMenu: (TrackRowModel) -> List = { emptyList() }, modifier: Modifier = Modifier ) { val coverColors by rememberCoverColors( @@ -101,7 +101,7 @@ private fun AlbumPageCompact( onToggleLike: () -> Unit, likedTrackIds: Set, onToggleTrackLike: (String) -> Unit, - songMenu: (TrackRowModel) -> List + songMenu: (TrackRowModel) -> List ) { Column(Modifier.fillMaxSize()) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -146,7 +146,7 @@ private fun AlbumPageWide( onToggleLike: () -> Unit, likedTrackIds: Set, onToggleTrackLike: (String) -> Unit, - songMenu: (TrackRowModel) -> List + songMenu: (TrackRowModel) -> List ) { Row(Modifier.fillMaxSize().padding(24.dp)) { Column( @@ -233,7 +233,7 @@ private fun TrackList( onEditTrack: (Int) -> Unit = {}, likedTrackIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, - songMenu: (TrackRowModel) -> List = { emptyList() }, + songMenu: (TrackRowModel) -> List = { emptyList() }, modifier: Modifier = Modifier ) { val discs = album.tracks.groupBy { it.discNumber ?: 1 } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt index 08a71067..cef6eadd 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/artist/ArtistPage.kt @@ -61,9 +61,9 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage import capital.yuri.yuriplayer.components.art.CoverArt -import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.ContextMenuAnchor import capital.yuri.yuriplayer.components.list.LikeHeart +import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.list.AlbumCard import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.list.formatTime @@ -90,7 +90,7 @@ fun ArtistPage( onToggleLike: () -> Unit = {}, likedTrackIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, - songMenu: (TrackRowModel) -> List = { emptyList() } + songMenu: (TrackRowModel) -> List = { emptyList() } ) { val listState = rememberLazyListState() val mini by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } @@ -458,7 +458,7 @@ private fun PopularRow( liked: Boolean, onToggleLike: () -> Unit, onClick: () -> Unit, - menu: List = emptyList() + menu: List = emptyList() ) { ContextMenuAnchor(items = menu) { openMenu -> Row( diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt index 164d90db..3b3f3740 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt @@ -63,8 +63,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt -import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.ContextMenuAnchor +import capital.yuri.yuriplayer.components.menu.MenuEntry enum class DesktopNav { Home, Search } @@ -96,7 +96,7 @@ fun DesktopBentoLayout( libraryItems: List, selectedLibraryId: String?, onLibraryItem: (LibraryRailItem) -> Unit, - libraryMenu: (LibraryRailItem) -> List = { emptyList() }, + libraryMenu: (LibraryRailItem) -> List = { emptyList() }, bottomBar: @Composable () -> Unit, sidebar: @Composable () -> Unit, modifier: Modifier = Modifier, @@ -375,7 +375,7 @@ private fun LibraryRail( items: List, selectedId: String?, onItem: (LibraryRailItem) -> Unit, - itemMenu: (LibraryRailItem) -> List = { emptyList() }, + itemMenu: (LibraryRailItem) -> List = { emptyList() }, modifier: Modifier = Modifier ) { Surface( diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt index 4a3cd020..681ee702 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt @@ -2,35 +2,34 @@ package capital.yuri.yuriplayer.components.list import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.isSecondaryPressed import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import capital.yuri.yuriplayer.components.menu.MenuEntry +import capital.yuri.yuriplayer.components.menu.YuriContextMenu +import kotlin.math.roundToInt -data class ContextAction( - val label: String, - val destructive: Boolean = false, - val enabled: Boolean = true, - val onClick: () -> Unit -) +typealias ContextAction = MenuEntry.Item @Composable fun ContextMenuAnchor( - items: List, + items: List, modifier: Modifier = Modifier, onLongPressOpen: Boolean = true, + showPredictionCone: Boolean = false, content: @Composable BoxScope.(openMenu: () -> Unit) -> Unit ) { var open by remember { mutableStateOf(false) } @@ -59,31 +58,21 @@ fun ContextMenuAnchor( } ) { content(if (onLongPressOpen && items.isNotEmpty()) openMenu else ({ })) - if (items.isNotEmpty()) { - DropdownMenu( - expanded = open, + if (open && items.isNotEmpty()) { + val px = with(density) { + IntOffset(offset.x.toPx().roundToInt(), offset.y.toPx().roundToInt()) + } + Popup( + alignment = Alignment.TopStart, + offset = px, onDismissRequest = { open = false }, - offset = offset + properties = PopupProperties(focusable = true, dismissOnClickOutside = true) ) { - items.forEach { action -> - DropdownMenuItem( - text = { - Text( - action.label, - color = if (action.destructive) { - MaterialTheme.colorScheme.error - } else { - MaterialTheme.colorScheme.onSurface - } - ) - }, - enabled = action.enabled, - onClick = { - open = false - action.onClick() - } - ) - } + YuriContextMenu( + entries = items, + onDismiss = { open = false }, + showPredictionCone = showPredictionCone + ) } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt index 6f5de45c..0a62bd24 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.TrackRowModel @OptIn(ExperimentalFoundationApi::class) @@ -31,7 +32,7 @@ fun TrackRow( onLongClick: (() -> Unit)? = null, liked: Boolean = false, onToggleLike: (() -> Unit)? = null, - contextItems: List = emptyList() + contextItems: List = emptyList() ) { val highlight = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) ContextMenuAnchor(items = contextItems) { openMenu -> diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/MenuDsl.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/MenuDsl.kt new file mode 100644 index 00000000..9cb5073b --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/MenuDsl.kt @@ -0,0 +1,67 @@ +package capital.yuri.yuriplayer.components.menu + +/** + * Hierarchical context menu. Implementors declare options; the host renders + * them with prediction-cone hover for submenus. + * + * ``` + * buildContextMenu { + * item("Add to queue") { enqueue() } + * submenu("Go to") { + * item("Album") { openAlbum() } + * item("Artist") { openArtist() } + * } + * divider() + * item("Delete", destructive = true) { remove() } + * } + * ``` + */ +sealed class MenuEntry { + data class Item( + val label: String, + val shortcut: String? = null, + val destructive: Boolean = false, + val enabled: Boolean = true, + val onClick: () -> Unit + ) : MenuEntry() + + data class Submenu( + val label: String, + val children: List + ) : MenuEntry() + + data object Divider : MenuEntry() +} + +@DslMarker +annotation class ContextMenuDsl + +@ContextMenuDsl +class ContextMenuScope { + private val entries = mutableListOf() + + fun item( + label: String, + shortcut: String? = null, + destructive: Boolean = false, + enabled: Boolean = true, + onClick: () -> Unit + ) { + entries += MenuEntry.Item(label, shortcut, destructive, enabled, onClick) + } + + fun submenu(label: String, block: ContextMenuScope.() -> Unit) { + entries += MenuEntry.Submenu(label, ContextMenuScope().apply(block).build()) + } + + fun divider() { + entries += MenuEntry.Divider + } + + internal fun build(): List = entries.toList() +} + +fun buildContextMenu(block: ContextMenuScope.() -> Unit): List = + ContextMenuScope().apply(block).build() + +typealias ContextAction = MenuEntry.Item diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/PredictionCone.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/PredictionCone.kt new file mode 100644 index 00000000..cca3703a --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/PredictionCone.kt @@ -0,0 +1,47 @@ +package capital.yuri.yuriplayer.components.menu + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import kotlin.math.abs + +/** + * Amazon / macOS prediction cone: while a submenu is open, travel inside the + * triangle (previous pointer → submenu near-edge corners) must not switch the + * highlighted parent item. + */ +fun pointInTriangle(p: Offset, a: Offset, b: Offset, c: Offset): Boolean { + if (!finite(p) || !finite(a) || !finite(b) || !finite(c)) return false + val v0x = c.x - a.x + val v0y = c.y - a.y + val v1x = b.x - a.x + val v1y = b.y - a.y + val v2x = p.x - a.x + val v2y = p.y - a.y + val dot00 = v0x * v0x + v0y * v0y + val dot01 = v0x * v1x + v0y * v1y + val dot02 = v0x * v2x + v0y * v2y + val dot11 = v1x * v1x + v1y * v1y + val dot12 = v1x * v2x + v1y * v2y + val denom = dot00 * dot11 - dot01 * dot01 + if (abs(denom) < 1e-6f) return false + val inv = 1f / denom + val u = (dot11 * dot02 - dot01 * dot12) * inv + val v = (dot00 * dot12 - dot01 * dot02) * inv + return u >= -0.02f && v >= -0.02f && u + v <= 1.02f +} + +fun predictionCone(origin: Offset, submenu: Rect): Triple? { + if (!finite(origin) || submenu.isEmpty) return null + return Triple( + origin, + Offset(submenu.left, submenu.top), + Offset(submenu.left, submenu.bottom) + ) +} + +fun pointerInPredictionCone(current: Offset, origin: Offset, submenu: Rect): Boolean { + val cone = predictionCone(origin, submenu) ?: return false + return pointInTriangle(current, cone.first, cone.second, cone.third) +} + +private fun finite(o: Offset): Boolean = o.x.isFinite() && o.y.isFinite() diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt new file mode 100644 index 00000000..70b34fb5 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt @@ -0,0 +1,253 @@ +package capital.yuri.yuriplayer.components.menu + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.boundsInParent +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.unit.dp + +/** + * Material 3 menu with nested submenus and a prediction cone so the pointer + * can travel diagonally into a submenu without highlighting siblings. + */ +@Composable +fun YuriContextMenu( + entries: List, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + showPredictionCone: Boolean = false +) { + var openSubmenu by remember { mutableIntStateOf(-1) } + var pointer by remember { mutableStateOf(Offset.Unspecified) } + var coneOrigin by remember { mutableStateOf(Offset.Unspecified) } + var submenuRect by remember { mutableStateOf(Rect.Zero) } + + Box( + modifier.pointerInput(entries) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent(PointerEventPass.Initial) + val change = event.changes.lastOrNull() ?: continue + val next = change.position + if (pointer != Offset.Unspecified) coneOrigin = pointer + pointer = next + } + } + } + ) { + Row(verticalAlignment = Alignment.Top) { + MenuColumn( + entries = entries, + openSubmenu = openSubmenu, + onHover = { index -> + val origin = if (coneOrigin != Offset.Unspecified) coneOrigin else pointer + val blocked = openSubmenu >= 0 && + index != openSubmenu && + pointerInPredictionCone(pointer, origin, submenuRect) + if (!blocked) openSubmenu = index + }, + onDismiss = onDismiss + ) + val sub = entries.getOrNull(openSubmenu) as? MenuEntry.Submenu + if (sub != null) { + MenuColumn( + entries = sub.children, + openSubmenu = -1, + onHover = {}, + onDismiss = onDismiss, + modifier = Modifier.onGloballyPositioned { + submenuRect = it.boundsInParent() + } + ) + } + } + if (showPredictionCone && openSubmenu >= 0) { + val cone = predictionCone(pointer, submenuRect) + if (cone != null) { + Canvas(Modifier.matchParentSize()) { + val path = Path().apply { + moveTo(cone.first.x, cone.first.y) + lineTo(cone.second.x, cone.second.y) + lineTo(cone.third.x, cone.third.y) + close() + } + drawPath(path, Color(0x402196F3)) + drawPath( + path, + Color(0xFF1565C0), + style = Stroke( + width = 1.5.dp.toPx(), + pathEffect = PathEffect.dashPathEffect(floatArrayOf(8f, 6f)) + ) + ) + drawCircle(Color(0xFF1565C0), radius = 3.dp.toPx(), center = cone.first) + drawCircle(Color(0xFF1565C0), radius = 3.dp.toPx(), center = cone.second) + drawCircle(Color(0xFF1565C0), radius = 3.dp.toPx(), center = cone.third) + } + } + } + } +} + +@Composable +private fun MenuColumn( + entries: List, + openSubmenu: Int, + onHover: (Int) -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier.padding(4.dp), + shape = RoundedCornerShape(12.dp), + tonalElevation = 6.dp, + shadowElevation = 8.dp, + color = MaterialTheme.colorScheme.surface + ) { + Column( + Modifier + .width(IntrinsicSize.Max) + .widthIn(min = 180.dp, max = 280.dp) + .padding(vertical = 6.dp) + ) { + entries.forEachIndexed { index, entry -> + when (entry) { + is MenuEntry.Divider -> HorizontalDivider( + Modifier.padding(vertical = 4.dp, horizontal = 8.dp) + ) + is MenuEntry.Item -> MenuItemRow( + label = entry.label, + shortcut = entry.shortcut, + destructive = entry.destructive, + enabled = entry.enabled, + selected = false, + hasSubmenu = false, + onHover = { onHover(-1) }, + onClick = { + if (entry.enabled) { + onDismiss() + entry.onClick() + } + } + ) + is MenuEntry.Submenu -> MenuItemRow( + label = entry.label, + shortcut = null, + destructive = false, + enabled = true, + selected = openSubmenu == index, + hasSubmenu = true, + onHover = { onHover(index) }, + onClick = { onHover(index) } + ) + } + } + } + } +} + +@Composable +private fun MenuItemRow( + label: String, + shortcut: String?, + destructive: Boolean, + enabled: Boolean, + selected: Boolean, + hasSubmenu: Boolean, + onHover: () -> Unit, + onClick: () -> Unit +) { + val interaction = remember { MutableInteractionSource() } + val hovered by interaction.collectIsHoveredAsState() + val highlight = selected || hovered + val color = when { + !enabled -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) + destructive -> MaterialTheme.colorScheme.error + else -> MaterialTheme.colorScheme.onSurface + } + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 6.dp) + .clip(RoundedCornerShape(8.dp)) + .background( + if (highlight && enabled) MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) + else Color.Transparent + ) + .hoverable(interaction) + .pointerInput(label) { + awaitPointerEventScope { + while (true) { + val e = awaitPointerEvent() + if (e.type == PointerEventType.Enter || e.type == PointerEventType.Move) { + onHover() + } + } + } + } + .clickable(enabled = enabled, onClick = onClick) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + label, + style = MaterialTheme.typography.bodyMedium, + color = color, + modifier = Modifier.weight(1f) + ) + if (shortcut != null) { + Text( + shortcut, + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f), + modifier = Modifier.padding(start = 16.dp) + ) + } + if (hasSubmenu) { + Icon( + Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.45f), + modifier = Modifier.padding(start = 8.dp) + ) + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index f6db7d21..671ac893 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -20,8 +20,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt -import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.LikeHeart +import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.CoverRef import capital.yuri.yuriplayer.components.model.TrackRowModel @@ -39,7 +39,7 @@ fun NowPlayingSidebar( onToggleLike: () -> Unit = {}, likedIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, - songMenu: (TrackRowModel) -> List = { emptyList() }, + songMenu: (TrackRowModel) -> List = { emptyList() }, modifier: Modifier = Modifier ) { Column( diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt index e3f25e68..c4d9b5dd 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt @@ -29,8 +29,8 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.CoverRef import capital.yuri.yuriplayer.components.model.TrackRowModel import kotlin.math.roundToInt @@ -52,7 +52,7 @@ fun QueuePanel( onMove: ((from: Int, to: Int) -> Unit)? = null, likedIds: Set = emptySet(), onToggleLike: (String) -> Unit = {}, - songMenu: (TrackRowModel) -> List = { emptyList() }, + songMenu: (TrackRowModel) -> List = { emptyList() }, modifier: Modifier = Modifier ) { var tab by remember { mutableStateOf(QueueTab.Queue) } diff --git a/components/src/jvmTest/kotlin/capital/yuri/yuriplayer/components/menu/PredictionConeTest.kt b/components/src/jvmTest/kotlin/capital/yuri/yuriplayer/components/menu/PredictionConeTest.kt new file mode 100644 index 00000000..77fb1866 --- /dev/null +++ b/components/src/jvmTest/kotlin/capital/yuri/yuriplayer/components/menu/PredictionConeTest.kt @@ -0,0 +1,30 @@ +package capital.yuri.yuriplayer.components.menu + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class PredictionConeTest { + private val submenu = Rect(200f, 40f, 360f, 200f) + private val origin = Offset(80f, 80f) + + @Test + fun pointInsideTriangleCounts() { + assertTrue(pointInTriangle(Offset(10f, 10f), Offset(0f, 0f), Offset(20f, 0f), Offset(0f, 20f))) + assertFalse(pointInTriangle(Offset(20f, 20f), Offset(0f, 0f), Offset(20f, 0f), Offset(0f, 20f))) + } + + @Test + fun diagonalTowardSubmenuIsSafe() { + assertTrue(pointerInPredictionCone(Offset(180f, 100f), origin, submenu)) + assertTrue(pointerInPredictionCone(Offset(150f, 90f), origin, submenu)) + } + + @Test + fun siblingItemBelowIsNotSafe() { + assertFalse(pointerInPredictionCone(Offset(80f, 280f), origin, submenu)) + assertFalse(pointerInPredictionCone(Offset(40f, 300f), origin, submenu)) + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt index acf283f7..42112113 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt @@ -38,8 +38,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt -import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.model.albums import capital.yuri.yuriplayer.components.model.toRow @@ -71,7 +71,7 @@ fun DesktopExplore( onPlaySongs: (List, Int) -> Unit, likedIds: Set = emptySet(), onToggleLike: (String) -> Unit = {}, - songMenu: (Track) -> List = { emptyList() } + songMenu: (Track) -> List = { emptyList() } ) { val remotes by session.sources.remotes.collectAsState() val enabled = remember(remotes) { remotes.filter { it.enabled } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt index e57ba0a7..b5e2c705 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -45,9 +45,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.dialog.InWindowPanel -import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.list.TrackRow +import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.toRow import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.library.matchesQuery @@ -70,7 +70,7 @@ fun PlaylistPage( onToggleLike: (String) -> Unit = {}, playlistLiked: Boolean = false, onTogglePlaylistLike: () -> Unit = {}, - songMenu: (Track) -> List = { emptyList() } + songMenu: (Track) -> List = { emptyList() } ) { var name by remember(playlist.id, playlist.updatedAtMs) { mutableStateOf(playlist.name) } var description by remember(playlist.id, playlist.updatedAtMs) { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index bc05f869..0d7f6b1b 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -50,6 +50,8 @@ import capital.yuri.yuriplayer.components.layout.LibraryRailItem import capital.yuri.yuriplayer.components.list.AlbumCard import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.ContextMenuAnchor +import capital.yuri.yuriplayer.components.menu.MenuEntry +import capital.yuri.yuriplayer.components.menu.buildContextMenu import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.model.albums import capital.yuri.yuriplayer.components.model.artistPage @@ -139,22 +141,22 @@ fun YuriDesktopApp(session: DesktopSession) { track: Track, onAlbumPage: Boolean = false, playlistId: String? = null - ): List = buildList { - add(ContextAction("Add to playlist") { addToPlaylist = listOf(track) }) - add(ContextAction("Add to queue") { session.player.enqueue(track) }) - if (!onAlbumPage) { - add( - ContextAction("Go to album") { + ): List = buildContextMenu { + item("Add to playlist") { addToPlaylist = listOf(track) } + item("Add to queue") { session.player.enqueue(track) } + submenu("Go to") { + if (!onAlbumPage) { + item("Album") { albums.firstOrNull { a -> a.tracks.any { it.id == track.id } }?.let(::openAlbum) } - ) + } + item("Artist") { openArtist(track.displayArtist) } } if (playlistId != null) { - add( - ContextAction("Remove from this playlist", destructive = true) { - session.playlists.removeTracks(playlistId, listOf(track.id)) - } - ) + divider() + item("Remove from this playlist", destructive = true) { + session.playlists.removeTracks(playlistId, listOf(track.id)) + } } } @@ -857,7 +859,7 @@ private data class SpotifyPin( val artworkUri: String?, val circular: Boolean = false, val onClick: () -> Unit, - val menu: List = emptyList() + val menu: List = emptyList() ) private fun buildHomeShortcuts( From bd1d57fc17d19fef9c11e818ff45df938587f57d Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 21:46:25 +0000 Subject: [PATCH 39/69] Right-click Add to playlist for a quick-add submenu Primary click still opens the picker. Alternate lists only playlists the song is not already on. --- .../yuriplayer/components/menu/MenuDsl.kt | 9 +++- .../components/menu/YuriContextMenu.kt | 41 +++++++++++---- .../yuriplayer/components/menu/MenuDslTest.kt | 21 ++++++++ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 50 +++++++++++++++---- 4 files changed, 100 insertions(+), 21 deletions(-) create mode 100644 components/src/jvmTest/kotlin/capital/yuri/yuriplayer/components/menu/MenuDslTest.kt diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/MenuDsl.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/MenuDsl.kt index 9cb5073b..ce9a3855 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/MenuDsl.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/MenuDsl.kt @@ -6,7 +6,9 @@ package capital.yuri.yuriplayer.components.menu * * ``` * buildContextMenu { - * item("Add to queue") { enqueue() } + * item("Add to playlist", alternate = { + * item("Chill") { addTo("chill") } + * }) { openPicker() } * submenu("Go to") { * item("Album") { openAlbum() } * item("Artist") { openArtist() } @@ -22,6 +24,7 @@ sealed class MenuEntry { val shortcut: String? = null, val destructive: Boolean = false, val enabled: Boolean = true, + val alternate: List = emptyList(), val onClick: () -> Unit ) : MenuEntry() @@ -45,9 +48,11 @@ class ContextMenuScope { shortcut: String? = null, destructive: Boolean = false, enabled: Boolean = true, + alternate: (ContextMenuScope.() -> Unit)? = null, onClick: () -> Unit ) { - entries += MenuEntry.Item(label, shortcut, destructive, enabled, onClick) + val alt = alternate?.let { ContextMenuScope().apply(it).build() }.orEmpty() + entries += MenuEntry.Item(label, shortcut, destructive, enabled, alt, onClick) } fun submenu(label: String, block: ContextMenuScope.() -> Unit) { diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt index 70b34fb5..7e0a843c 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.PathEffect import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.isSecondaryPressed import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.boundsInParent import androidx.compose.ui.layout.onGloballyPositioned @@ -84,14 +85,16 @@ fun YuriContextMenu( pointerInPredictionCone(pointer, origin, submenuRect) if (!blocked) openSubmenu = index }, + onOpenSubmenu = { openSubmenu = it }, onDismiss = onDismiss ) - val sub = entries.getOrNull(openSubmenu) as? MenuEntry.Submenu - if (sub != null) { + val nested = entries.getOrNull(openSubmenu)?.nestedChildren() + if (!nested.isNullOrEmpty()) { MenuColumn( - entries = sub.children, + entries = nested, openSubmenu = -1, onHover = {}, + onOpenSubmenu = {}, onDismiss = onDismiss, modifier = Modifier.onGloballyPositioned { submenuRect = it.boundsInParent() @@ -127,11 +130,18 @@ fun YuriContextMenu( } } +private fun MenuEntry.nestedChildren(): List? = when (this) { + is MenuEntry.Submenu -> children + is MenuEntry.Item -> alternate.takeIf { it.isNotEmpty() } + is MenuEntry.Divider -> null +} + @Composable private fun MenuColumn( - entries: List, + entries: List, openSubmenu: Int, onHover: (Int) -> Unit, + onOpenSubmenu: (Int) -> Unit, onDismiss: () -> Unit, modifier: Modifier = Modifier ) { @@ -158,15 +168,18 @@ private fun MenuColumn( shortcut = entry.shortcut, destructive = entry.destructive, enabled = entry.enabled, - selected = false, - hasSubmenu = false, + selected = openSubmenu == index && entry.alternate.isNotEmpty(), + hasSubmenu = entry.alternate.isNotEmpty(), onHover = { onHover(-1) }, onClick = { if (entry.enabled) { onDismiss() entry.onClick() } - } + }, + onAltClick = if (entry.alternate.isNotEmpty()) { + { onOpenSubmenu(index) } + } else null ) is MenuEntry.Submenu -> MenuItemRow( label = entry.label, @@ -193,7 +206,8 @@ private fun MenuItemRow( selected: Boolean, hasSubmenu: Boolean, onHover: () -> Unit, - onClick: () -> Unit + onClick: () -> Unit, + onAltClick: (() -> Unit)? = null ) { val interaction = remember { MutableInteractionSource() } val hovered by interaction.collectIsHoveredAsState() @@ -213,12 +227,19 @@ private fun MenuItemRow( else Color.Transparent ) .hoverable(interaction) - .pointerInput(label) { + .pointerInput(label, onAltClick) { awaitPointerEventScope { while (true) { val e = awaitPointerEvent() if (e.type == PointerEventType.Enter || e.type == PointerEventType.Move) { - onHover() + if (!e.buttons.isSecondaryPressed) onHover() + } + if (e.type == PointerEventType.Press && e.buttons.isSecondaryPressed) { + val alt = onAltClick + if (alt != null) { + e.changes.forEach { it.consume() } + alt() + } } } } diff --git a/components/src/jvmTest/kotlin/capital/yuri/yuriplayer/components/menu/MenuDslTest.kt b/components/src/jvmTest/kotlin/capital/yuri/yuriplayer/components/menu/MenuDslTest.kt new file mode 100644 index 00000000..6749219f --- /dev/null +++ b/components/src/jvmTest/kotlin/capital/yuri/yuriplayer/components/menu/MenuDslTest.kt @@ -0,0 +1,21 @@ +package capital.yuri.yuriplayer.components.menu + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class MenuDslTest { + @Test + fun itemAlternateIsNestedNotPrimary() { + val menu = buildContextMenu { + item("Add to playlist", alternate = { + item("Late night") {} + item("Gym") {} + }) {} + } + val item = menu.single() as MenuEntry.Item + assertEquals("Add to playlist", item.label) + assertEquals(listOf("Late night", "Gym"), item.alternate.map { (it as MenuEntry.Item).label }) + assertTrue(item.alternate.none { it is MenuEntry.Submenu }) + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 0d7f6b1b..76d1e4de 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -50,6 +50,7 @@ import capital.yuri.yuriplayer.components.layout.LibraryRailItem import capital.yuri.yuriplayer.components.list.AlbumCard import capital.yuri.yuriplayer.components.list.ContextAction import capital.yuri.yuriplayer.components.list.ContextMenuAnchor +import capital.yuri.yuriplayer.components.menu.ContextMenuScope import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.menu.buildContextMenu import capital.yuri.yuriplayer.components.model.AlbumPageModel @@ -137,12 +138,28 @@ fun YuriDesktopApp(session: DesktopSession) { fun openAlbum(album: AlbumPageModel) = push(Route.Album(album)) fun openArtist(name: String) = push(Route.Artist(name)) + fun playlistAddAlternate(songs: List): ContextMenuScope.() -> Unit = { + val ids = songs.map { it.id } + val candidates = playlists + .filter { pl -> ids.any { it !in pl.trackIds } } + .sortedBy { it.name.lowercase() } + if (candidates.isEmpty()) { + item("Already in every playlist", enabled = false) {} + } else { + candidates.forEach { pl -> + item(pl.name) { session.playlists.addTracks(pl.id, ids) } + } + } + } + fun songMenu( track: Track, onAlbumPage: Boolean = false, playlistId: String? = null ): List = buildContextMenu { - item("Add to playlist") { addToPlaylist = listOf(track) } + item("Add to playlist", alternate = playlistAddAlternate(listOf(track))) { + addToPlaylist = listOf(track) + } item("Add to queue") { session.player.enqueue(track) } submenu("Go to") { if (!onAlbumPage) { @@ -328,8 +345,14 @@ fun YuriDesktopApp(session: DesktopSession) { add(ContextAction("Play") { session.player.play(playable, 0) }) add(ContextAction("Add to queue") { session.player.enqueueAll(playable) }) } - if (playable.size == 1) { - add(ContextAction("Add to playlist") { addToPlaylist = playable }) + if (playable.isNotEmpty()) { + add( + ContextAction( + label = "Add to playlist", + alternate = buildContextMenu(playlistAddAlternate(playable)), + onClick = { addToPlaylist = playable } + ) + ) } if (item.pinned) { val kind = when { @@ -440,7 +463,8 @@ fun YuriDesktopApp(session: DesktopSession) { onPlayTracks = { list, i -> session.player.play(list, i) }, onEnqueue = { session.player.enqueueAll(it) }, onUnpin = { kind, id -> session.collection.unpin(kind, id) }, - onAddToPlaylist = { addToPlaylist = it } + onAddToPlaylist = { addToPlaylist = it }, + playlistQuickAdd = { songs -> buildContextMenu(playlistAddAlternate(songs)) } ) Route.Search -> DesktopExplore( session = session, @@ -683,7 +707,8 @@ private fun HomeFeed( onPlayTracks: (List, Int) -> Unit, onEnqueue: (List) -> Unit = {}, onUnpin: (DesktopCollection.Kind, String) -> Unit = { _, _ -> }, - onAddToPlaylist: (List) -> Unit = {} + onAddToPlaylist: (List) -> Unit = {}, + playlistQuickAdd: (List) -> List = { emptyList() } ) { val recentAlbums = remember(recents, albums) { recents.mapNotNull { t -> @@ -695,7 +720,7 @@ private fun HomeFeed( buildHomeShortcuts( pins, liked, likedTracks, albums, playlists, tracks, onOpenAlbum, onOpenPlaylist, onOpenArtist, onPlayTracks, - onEnqueue, onUnpin, onAddToPlaylist + onEnqueue, onUnpin, onAddToPlaylist, playlistQuickAdd ) } val greeting = remember { @@ -875,7 +900,8 @@ private fun buildHomeShortcuts( onPlayTracks: (List, Int) -> Unit, onEnqueue: (List) -> Unit, onUnpin: (DesktopCollection.Kind, String) -> Unit, - onAddToPlaylist: (List) -> Unit + onAddToPlaylist: (List) -> Unit, + playlistQuickAdd: (List) -> List ): List { val out = ArrayList(8) fun add(pin: SpotifyPin) { @@ -895,8 +921,14 @@ private fun buildHomeShortcuts( if (playable.isNotEmpty()) { add(ContextAction("Add to queue") { onEnqueue(playable) }) } - if (playable.size == 1) { - add(ContextAction("Add to playlist") { onAddToPlaylist(playable) }) + if (playable.isNotEmpty()) { + add( + ContextAction( + label = "Add to playlist", + alternate = playlistQuickAdd(playable), + onClick = { onAddToPlaylist(playable) } + ) + ) } if (kind != null) { add(ContextAction("Unpin", destructive = true) { onUnpin(kind, id) }) From 028d596abbe88c51d798444c5b17f45d8f4beb12 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 21:53:14 +0000 Subject: [PATCH 40/69] Crop every desktop image pick, including headers Covers stay square; artist headers use a 3:1 frame. File picks and fetched banners both go through the same pan/zoom crop. --- .../yuriplayer/desktop/ui/DesktopFiles.kt | 9 + .../yuriplayer/desktop/ui/ImageCropDialog.kt | 159 ++++++++++++------ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 21 ++- 3 files changed, 131 insertions(+), 58 deletions(-) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt index 11cc4edc..cdf13077 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt @@ -3,6 +3,7 @@ package capital.yuri.yuriplayer.desktop.ui import java.awt.FileDialog import java.awt.Frame import java.io.File +import java.net.URI import javax.swing.SwingUtilities object DesktopFiles { @@ -27,4 +28,12 @@ object DesktopFiles { else SwingUtilities.invokeAndWait(task) return result?.toList().orEmpty() } + + fun downloadImage(url: String): File? = runCatching { + val out = File.createTempFile("yuri-img-", ".img") + URI(url).toURL().openStream().use { input -> + out.outputStream().use { input.copyTo(it) } + } + out.takeIf { it.length() > 32 } + }.getOrNull() } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt index 9cdb883a..8fccfbb0 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -29,21 +30,28 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.dialog.InWindowPanel +import java.awt.RenderingHints import java.awt.image.BufferedImage import java.io.File import javax.imageio.ImageIO import kotlin.math.max import kotlin.math.min +import kotlin.math.roundToInt /** - * Pan/zoom square crop. Source is scaled to cover the frame; wheel zooms, drag pans. + * Pan/zoom crop for any aspect. `aspect` is width / height (1 = square cover, + * 3 = Twitter-style header). Source is scaled to cover the frame. */ @Composable fun ImageCropDialog( source: File, - title: String = "Crop cover", + title: String = "Crop", + aspect: Float = 1f, onCancel: () -> Unit, onCropped: (File) -> Unit ) { @@ -55,8 +63,11 @@ fun ImageCropDialog( val bitmap = remember(image) { image.toComposeImageBitmap() } var zoom by remember { mutableFloatStateOf(1f) } var pan by remember { mutableStateOf(Offset.Zero) } + var stage by remember { mutableStateOf(Size.Zero) } + val wide = aspect >= 1.4f + val panel = if (wide) Modifier.size(760.dp, 520.dp) else Modifier.size(560.dp, 640.dp) - InWindowPanel(onDismiss = onCancel, modifier = Modifier.size(560.dp, 640.dp)) { + InWindowPanel(onDismiss = onCancel, modifier = panel) { Surface(color = MaterialTheme.colorScheme.surface) { Column(Modifier.fillMaxSize().padding(16.dp)) { Text(title, style = MaterialTheme.typography.titleLarge) @@ -71,6 +82,7 @@ fun ImageCropDialog( .fillMaxWidth() .weight(1f) .background(Color.Black) + .onSizeChanged { stage = Size(it.width.toFloat(), it.height.toFloat()) } .pointerInput(image) { awaitPointerEventScope { while (true) { @@ -91,55 +103,50 @@ fun ImageCropDialog( contentAlignment = Alignment.Center ) { Canvas(Modifier.fillMaxSize()) { - val frame = min(size.width, size.height) * 0.82f - val frameLeft = (size.width - frame) / 2f - val frameTop = (size.height - frame) / 2f - val imgW = image.width.toFloat() - val imgH = image.height.toFloat() - val cover = max(frame / imgW, frame / imgH) * zoom - val drawW = imgW * cover - val drawH = imgH * cover - val maxPanX = max(0f, (drawW - frame) / 2f) - val maxPanY = max(0f, (drawH - frame) / 2f) - val px = pan.x.coerceIn(-maxPanX, maxPanX) - val py = pan.y.coerceIn(-maxPanY, maxPanY) - val left = frameLeft + (frame - drawW) / 2f + px - val top = frameTop + (frame - drawH) / 2f + py + val layout = cropLayout(size, image.width, image.height, aspect, zoom, pan) drawImage( image = bitmap, - dstOffset = androidx.compose.ui.unit.IntOffset(left.toInt(), top.toInt()), - dstSize = androidx.compose.ui.unit.IntSize(drawW.toInt(), drawH.toInt()) + dstOffset = IntOffset(layout.drawLeft.roundToInt(), layout.drawTop.roundToInt()), + dstSize = IntSize(layout.drawW.roundToInt().coerceAtLeast(1), layout.drawH.roundToInt().coerceAtLeast(1)) ) - drawRect(Color.Black.copy(alpha = 0.45f), size = Size(size.width, frameTop)) + val dim = Color.Black.copy(alpha = 0.5f) + drawRect(dim, size = Size(size.width, layout.frameTop)) drawRect( - Color.Black.copy(alpha = 0.45f), - topLeft = Offset(0f, frameTop + frame), - size = Size(size.width, size.height - frameTop - frame) + dim, + topLeft = Offset(0f, layout.frameTop + layout.frameH), + size = Size(size.width, size.height - layout.frameTop - layout.frameH) ) drawRect( - Color.Black.copy(alpha = 0.45f), - topLeft = Offset(0f, frameTop), - size = Size(frameLeft, frame) + dim, + topLeft = Offset(0f, layout.frameTop), + size = Size(layout.frameLeft, layout.frameH) ) drawRect( - Color.Black.copy(alpha = 0.45f), - topLeft = Offset(frameLeft + frame, frameTop), - size = Size(size.width - frameLeft - frame, frame) + dim, + topLeft = Offset(layout.frameLeft + layout.frameW, layout.frameTop), + size = Size(size.width - layout.frameLeft - layout.frameW, layout.frameH) ) + val grid = Color.White.copy(alpha = 0.28f) + for (i in 1..2) { + val x = layout.frameLeft + layout.frameW * i / 3f + val y = layout.frameTop + layout.frameH * i / 3f + drawLine(grid, Offset(x, layout.frameTop), Offset(x, layout.frameTop + layout.frameH), 1f) + drawLine(grid, Offset(layout.frameLeft, y), Offset(layout.frameLeft + layout.frameW, y), 1f) + } drawRect( Color.White, - topLeft = Offset(frameLeft, frameTop), - size = Size(frame, frame), + topLeft = Offset(layout.frameLeft, layout.frameTop), + size = Size(layout.frameW, layout.frameH), style = Stroke(width = 2.dp.toPx()) ) } } Row(Modifier.fillMaxWidth().padding(top = 12.dp), verticalAlignment = Alignment.CenterVertically) { TextButton(onClick = onCancel) { Text("Cancel") } - androidx.compose.foundation.layout.Spacer(Modifier.weight(1f)) + Spacer(Modifier.weight(1f)) TextButton( onClick = { - val out = cropToSquare(image, zoom, pan) + val out = cropToAspect(image, aspect, zoom, pan, stage) if (out != null) onCropped(out) else onCancel() } ) { Text("Use photo") } @@ -149,30 +156,78 @@ fun ImageCropDialog( } } -private fun cropToSquare(src: BufferedImage, zoom: Float, pan: Offset): File? { - val imgW = src.width.toFloat() - val imgH = src.height.toFloat() - val frame = 1024f - val cover = max(frame / imgW, frame / imgH) * zoom.coerceAtLeast(1f) +private data class CropLayout( + val frameLeft: Float, + val frameTop: Float, + val frameW: Float, + val frameH: Float, + val drawLeft: Float, + val drawTop: Float, + val drawW: Float, + val drawH: Float, + val cover: Float, + val panX: Float, + val panY: Float +) + +private fun cropLayout( + canvas: Size, + imgWpx: Int, + imgHpx: Int, + aspect: Float, + zoom: Float, + pan: Offset +): CropLayout { + val maxW = canvas.width * 0.9f + val maxH = canvas.height * 0.88f + val ratio = aspect.coerceAtLeast(0.2f) + val (frameW, frameH) = if (maxW / maxH > ratio) { + maxH * ratio to maxH + } else { + maxW to maxW / ratio + } + val frameLeft = (canvas.width - frameW) / 2f + val frameTop = (canvas.height - frameH) / 2f + val imgW = imgWpx.toFloat() + val imgH = imgHpx.toFloat() + val cover = max(frameW / imgW, frameH / imgH) * zoom.coerceAtLeast(1f) val drawW = imgW * cover val drawH = imgH * cover - val maxPanX = max(0f, (drawW - frame) / 2f) - val maxPanY = max(0f, (drawH - frame) / 2f) + val maxPanX = max(0f, (drawW - frameW) / 2f) + val maxPanY = max(0f, (drawH - frameH) / 2f) val px = pan.x.coerceIn(-maxPanX, maxPanX) val py = pan.y.coerceIn(-maxPanY, maxPanY) - val left = (drawW - frame) / 2f - px - val top = (drawH - frame) / 2f - py - val srcX = (left / cover).toInt().coerceIn(0, src.width - 1) - val srcY = (top / cover).toInt().coerceIn(0, src.height - 1) - val srcSide = (frame / cover).toInt().coerceAtLeast(1) - val w = min(srcSide, src.width - srcX) - val h = min(srcSide, src.height - srcY) - val cropped = src.getSubimage(srcX, srcY, w, h) - val square = BufferedImage(1024, 1024, BufferedImage.TYPE_INT_RGB) - val g = square.createGraphics() - g.drawImage(cropped, 0, 0, 1024, 1024, null) + val drawLeft = frameLeft + (frameW - drawW) / 2f + px + val drawTop = frameTop + (frameH - drawH) / 2f + py + return CropLayout(frameLeft, frameTop, frameW, frameH, drawLeft, drawTop, drawW, drawH, cover, px, py) +} + +internal fun cropToAspect( + src: BufferedImage, + aspect: Float, + zoom: Float, + pan: Offset, + canvas: Size, + longEdge: Int = 1536 +): File? { + if (canvas.width <= 1f || canvas.height <= 1f) return null + val layout = cropLayout(canvas, src.width, src.height, aspect.coerceAtLeast(0.2f), zoom, pan) + val left = (layout.drawW - layout.frameW) / 2f - layout.panX + val top = (layout.drawH - layout.frameH) / 2f - layout.panY + val srcX = (left / layout.cover).toInt().coerceIn(0, src.width - 1) + val srcY = (top / layout.cover).toInt().coerceIn(0, src.height - 1) + val srcW = (layout.frameW / layout.cover).toInt().coerceAtLeast(1).coerceAtMost(src.width - srcX) + val srcH = (layout.frameH / layout.cover).toInt().coerceAtLeast(1).coerceAtMost(src.height - srcY) + val cropped = src.getSubimage(srcX, srcY, srcW, srcH) + val ratio = aspect.coerceAtLeast(0.2f) + val outW = if (ratio >= 1f) longEdge else (longEdge * ratio).roundToInt().coerceAtLeast(1) + val outH = if (ratio >= 1f) (longEdge / ratio).roundToInt().coerceAtLeast(1) else longEdge + val dest = BufferedImage(outW, outH, BufferedImage.TYPE_INT_RGB) + val g = dest.createGraphics() + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR) + g.drawImage(cropped, 0, 0, outW, outH, null) g.dispose() val out = File.createTempFile("yuri-crop-", ".jpg") - ImageIO.write(square, "jpg", out) + ImageIO.write(dest, "jpg", out) return out } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 76d1e4de..76a9a4bb 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -525,6 +525,7 @@ fun YuriDesktopApp(session: DesktopSession) { is Route.Artist -> { var profile by remember(r.name) { mutableStateOf(null) } var pickBanner by remember { mutableStateOf(false) } + var cropBanner by remember { mutableStateOf(null) } LaunchedEffect(r.name) { profile = runCatching { session.artists.resolve(r.name) }.getOrNull() } @@ -567,9 +568,7 @@ fun YuriDesktopApp(session: DesktopSession) { }, onOpenAlbum = ::openAlbum, onChangeHeader = { - DesktopFiles.pickImage("Choose header")?.let { file -> - profile = session.artists.applyLocalBanner(r.name, file) - } + DesktopFiles.pickImage("Choose header")?.let { cropBanner = it } }, onFetchHeader = { pickBanner = true }, onClearHeader = { @@ -600,13 +599,23 @@ fun YuriDesktopApp(session: DesktopSession) { onPicked = { c -> pickBanner = false scope.launch { - profile = runCatching { - session.artists.applyBannerUrl(r.name, c.url) - }.getOrNull() ?: profile + cropBanner = DesktopFiles.downloadImage(c.url) } } ) } + cropBanner?.let { src -> + ImageCropDialog( + source = src, + title = "Crop header", + aspect = 3f, + onCancel = { cropBanner = null }, + onCropped = { cropped -> + cropBanner = null + profile = session.artists.applyLocalBanner(r.name, cropped) + } + ) + } } is Route.Playlist -> { val pl = playlists.firstOrNull { it.id == r.id } From 2bde2578067081bc839d1ea9a961795773e96829 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 22:12:00 +0000 Subject: [PATCH 41/69] Use the OS file picker for desktop images, then crop in-app Linux prefers zenity/kdialog so we get a portal dialog instead of Motif AWT. macOS/Windows keep a native FileDialog parented to the app window. Crop still happens after a file is chosen. --- .../yuriplayer/desktop/ui/DesktopFiles.kt | 117 +++++++++++++++--- 1 file changed, 102 insertions(+), 15 deletions(-) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt index cdf13077..1b09cbda 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopFiles.kt @@ -1,39 +1,126 @@ package capital.yuri.yuriplayer.desktop.ui +import java.awt.Dialog import java.awt.FileDialog import java.awt.Frame +import java.awt.KeyboardFocusManager +import java.awt.Window import java.io.File +import java.io.IOException import java.net.URI import javax.swing.SwingUtilities object DesktopFiles { - fun pickImage(title: String = "Choose image"): File? { - val files = pickImages(title, multiple = false) - return files.firstOrNull() - } + private val imageExt = setOf("jpg", "jpeg", "png", "webp", "gif", "bmp") + + fun pickImage(title: String = "Choose image"): File? = + pickImages(title, multiple = false).firstOrNull() fun pickImages(title: String = "Choose images", multiple: Boolean = true): List { + val picked = if (isLinux()) linuxNativePicker(title, multiple) else null + val files = picked ?: awtPicker(title, multiple) + return files.filter { it.isFile && it.extension.lowercase() in imageExt } + } + + fun downloadImage(url: String): File? = runCatching { + val out = File.createTempFile("yuri-img-", ".img") + URI(url).toURL().openStream().use { input -> + out.outputStream().use { input.copyTo(it) } + } + out.takeIf { it.length() > 32 } + }.getOrNull() + + private fun isLinux(): Boolean { + val os = System.getProperty("os.name").orEmpty().lowercase() + return os.contains("linux") || os.contains("bsd") + } + + /** + * System portal pickers (zenity / kdialog). Returning empty means the user + * cancelled; null means no native tool was available. + */ + private fun linuxNativePicker(title: String, multiple: Boolean): List? { + val desktop = System.getenv("XDG_CURRENT_DESKTOP").orEmpty().lowercase() + val kdeFirst = "kde" in desktop || "plasma" in desktop + val tools = if (kdeFirst) listOf(::kdialogCmd, ::zenityCmd) else listOf(::zenityCmd, ::kdialogCmd) + for (tool in tools) { + val cmd = tool(title, multiple) + val result = runPicker(cmd) ?: continue + return result + } + return null + } + + private fun zenityCmd(title: String, multiple: Boolean): List = buildList { + add("zenity") + add("--file-selection") + add("--title=$title") + add("--file-filter=Images | *.jpg *.jpeg *.png *.webp *.gif *.bmp *.JPG *.JPEG *.PNG *.WEBP") + if (multiple) { + add("--multiple") + add("--separator=\n") + } + } + + private fun kdialogCmd(title: String, multiple: Boolean): List = buildList { + add("kdialog") + add("--title") + add(title) + if (multiple) add("--multiple") + add("--getopenfilename") + add(System.getProperty("user.home") ?: ".") + add("Images (*.jpg *.jpeg *.png *.webp *.gif *.bmp)") + } + + private fun runPicker(cmd: List): List? { + val process = try { + ProcessBuilder(cmd) + .redirectError(ProcessBuilder.Redirect.DISCARD) + .start() + } catch (_: IOException) { + return null + } + val text = process.inputStream.bufferedReader().readText().trim() + val code = process.waitFor() + if (code != 0 || text.isEmpty()) return emptyList() + return text.split('\n', '\u0000') + .flatMap { line -> if (':' in line && !line.startsWith('/')) line.split(':') else listOf(line) } + .map { it.trim().removeSurrounding("\"") } + .filter { it.isNotEmpty() } + .map { File(it) } + } + + private fun awtPicker(title: String, multiple: Boolean): List { var result: Array? = null val task = Runnable { - val dialog = FileDialog(null as Frame?, title, FileDialog.LOAD) - dialog.isMultipleMode = multiple - dialog.setFilenameFilter { _, name -> - val ext = name.substringAfterLast('.', "").lowercase() - ext in setOf("jpg", "jpeg", "png", "webp", "gif", "bmp") + val owner = ownerFrame() + val dialog = if (owner != null) { + FileDialog(owner, title, FileDialog.LOAD) + } else { + FileDialog(null as Frame?, title, FileDialog.LOAD) } + dialog.isMultipleMode = multiple + dialog.file = "*.jpg;*.jpeg;*.png;*.webp;*.gif;*.bmp" + dialog.modalityType = Dialog.ModalityType.APPLICATION_MODAL + dialog.isAlwaysOnTop = true dialog.isVisible = true result = dialog.files + dialog.dispose() } if (SwingUtilities.isEventDispatchThread()) task.run() else SwingUtilities.invokeAndWait(task) return result?.toList().orEmpty() } - fun downloadImage(url: String): File? = runCatching { - val out = File.createTempFile("yuri-img-", ".img") - URI(url).toURL().openStream().use { input -> - out.outputStream().use { input.copyTo(it) } + private fun ownerFrame(): Frame? { + val focus = KeyboardFocusManager.getCurrentKeyboardFocusManager() + fun Window.toFrame(): Frame? = when (this) { + is Frame -> this + is Dialog -> owner as? Frame + else -> null } - out.takeIf { it.length() > 32 } - }.getOrNull() + focus.activeWindow?.toFrame()?.let { return it } + focus.focusedWindow?.toFrame()?.let { return it } + return Frame.getFrames().firstOrNull { it.isShowing } + } } From a6e8c16c5d7b75d449193b6d01f040bea8f24a4f Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 22:26:46 +0000 Subject: [PATCH 42/69] Fix header picker overlap, crop clipping, and banner save Source tiles are grouped and clipped instead of stacked 1:1 in a 3:1 grid. The crop canvas stays inside the panel. Cropped headers write a unique .jpg so Coil actually reloads them. --- .../components/dialog/InWindowDialog.kt | 4 +- .../core/artist/ArtistProfileStore.kt | 23 ++- .../desktop/ui/ArtistBannerPicker.kt | 194 ++++++++++++------ .../yuriplayer/desktop/ui/ImageCropDialog.kt | 162 ++++++++------- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 4 +- 5 files changed, 241 insertions(+), 146 deletions(-) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/dialog/InWindowDialog.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/dialog/InWindowDialog.kt index f3093af9..547967d0 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/dialog/InWindowDialog.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/dialog/InWindowDialog.kt @@ -12,6 +12,7 @@ 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.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex @@ -60,7 +61,8 @@ fun InWindowPanel( shape = RoundedCornerShape(16.dp), tonalElevation = 6.dp, color = MaterialTheme.colorScheme.surface, - contentColor = MaterialTheme.colorScheme.onSurface + contentColor = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.clip(RoundedCornerShape(16.dp)) ) { content() } diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfileStore.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfileStore.kt index eb5cae04..c79bba5f 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfileStore.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/artist/ArtistProfileStore.kt @@ -23,29 +23,38 @@ class ArtistProfileStore(configDir: String, cacheDir: String) { } fun bannerFile(name: String): File? { - val key = artistKey(name) ?: return null - return File(bannerDir, key) + val prefix = filePrefix(name) ?: return null + return bannerDir.listFiles()?.filter { it.name.startsWith(prefix) }?.maxByOrNull { it.lastModified() } } fun setLocalBanner(name: String, file: File): String? { - val key = artistKey(name) ?: return null - val dest = File(bannerDir, key) + val prefix = filePrefix(name) ?: return null return runCatching { + clearBannerFiles(prefix) + val dest = File(bannerDir, "$prefix-${System.currentTimeMillis()}.jpg") file.copyTo(dest, overwrite = true) dest.toURI().toString() }.getOrNull() } fun writeBannerBytes(name: String, bytes: ByteArray): String? { - val key = artistKey(name) ?: return null - val dest = File(bannerDir, key) + val prefix = filePrefix(name) ?: return null return runCatching { + clearBannerFiles(prefix) + val dest = File(bannerDir, "$prefix-${System.currentTimeMillis()}.jpg") dest.writeBytes(bytes) dest.toURI().toString() }.getOrNull() } fun clearBanner(name: String) { - bannerFile(name)?.delete() + filePrefix(name)?.let { clearBannerFiles(it) } + } + + private fun filePrefix(name: String): String? = + artistKey(name)?.replace(Regex("[^a-z0-9]+"), "-")?.trim('-')?.takeIf { it.isNotEmpty() } + + private fun clearBannerFiles(prefix: String) { + bannerDir.listFiles()?.filter { it.name.startsWith(prefix) }?.forEach { it.delete() } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt index 93572f6e..7c82b7ce 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt @@ -7,18 +7,19 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -30,7 +31,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.dialog.InWindowPanel @@ -57,69 +60,138 @@ fun ArtistBannerPicker( loading = false } - InWindowPanel(onDismiss = onDismiss, modifier = Modifier.width(640.dp)) { - Surface( - shape = RoundedCornerShape(16.dp), - tonalElevation = 0.dp - ) { - Column(Modifier.padding(20.dp)) { - Text("Choose a header", style = MaterialTheme.typography.titleLarge) - Text( - "Wikipedia and TheAudioDB · $artistName", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), - modifier = Modifier.padding(top = 4.dp, bottom = 12.dp) + val sections = remember(candidates) { groupCandidates(candidates) } + + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.width(680.dp).heightIn(max = 560.dp)) { + Column(Modifier.padding(20.dp)) { + Text("Choose a header", style = MaterialTheme.typography.titleLarge) + Text( + "Wide shots first · $artistName", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(top = 4.dp, bottom = 12.dp) + ) + when { + loading -> Box( + Modifier.fillMaxWidth().height(180.dp), + contentAlignment = Alignment.Center + ) { CircularProgressIndicator() } + error != null -> Text(error!!, color = MaterialTheme.colorScheme.error) + candidates.isEmpty() -> Text( + "Couldn't find headers for $artistName", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) ) - when { - loading -> Box( - Modifier.fillMaxWidth().height(180.dp), - contentAlignment = Alignment.Center - ) { CircularProgressIndicator() } - error != null -> Text(error!!, color = MaterialTheme.colorScheme.error) - candidates.isEmpty() -> Text( - "Couldn't find headers for $artistName", - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) - ) - else -> LazyVerticalGrid( - columns = GridCells.Fixed(3), - modifier = Modifier.height(360.dp), - contentPadding = PaddingValues(bottom = 8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(candidates, key = { it.url }) { c -> - Column { - CoverArt( - model = c.url, - modifier = Modifier - .fillMaxWidth() - .aspectRatio(3f) - .clip(RoundedCornerShape(8.dp)) - .border( - 1.dp, - MaterialTheme.colorScheme.outline.copy(alpha = 0.4f), - RoundedCornerShape(8.dp) - ) - .clickable { onPicked(c) } - .background(MaterialTheme.colorScheme.surfaceVariant), - corner = 8.dp, - contentScale = ContentScale.Crop - ) - Text( - c.label, - style = MaterialTheme.typography.labelSmall, - maxLines = 1, - modifier = Modifier.padding(top = 4.dp), - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) - ) + else -> LazyColumn( + modifier = Modifier.heightIn(max = 420.dp).fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(14.dp), + contentPadding = PaddingValues(bottom = 8.dp) + ) { + sections.forEach { section -> + item(key = "h-${section.title}") { + Text( + section.title, + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), + modifier = Modifier.padding(bottom = 2.dp) + ) + } + items(section.rows, key = { row -> row.joinToString { it.url } }) { row -> + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + row.forEach { c -> + BannerTile( + candidate = c, + wide = section.wide, + onClick = { onPicked(c) }, + modifier = Modifier.weight(1f) + ) + } + if (row.size == 1) Box(Modifier.weight(1f)) } } } } - TextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.End)) { - Text("Cancel") - } + } + TextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.End)) { + Text("Cancel") } } } } + +@Composable +private fun BannerTile( + candidate: ArtistImageCandidate, + wide: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val shape = RoundedCornerShape(10.dp) + Column(modifier) { + Box( + Modifier + .fillMaxWidth() + .aspectRatio(if (wide) 3f else 1f) + .clip(shape) + .clipToBounds() + .border(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.35f), shape) + .background(MaterialTheme.colorScheme.surfaceVariant) + .clickable(onClick = onClick) + ) { + CoverArt( + model = candidate.url, + modifier = Modifier.fillMaxSize(), + square = false, + corner = 0.dp, + contentScale = ContentScale.Crop + ) + } + Text( + candidate.label, + style = MaterialTheme.typography.labelSmall, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(top = 4.dp) + ) + } +} + +private data class BannerSection( + val title: String, + val wide: Boolean, + val rows: List> +) + +private fun groupCandidates(list: List): List { + val wide = list.filter { it.isWide() } + val rest = list.filterNot { it.isWide() } + return buildList { + if (wide.isNotEmpty()) add(BannerSection("Headers", true, wide.chunked(2))) + rest.groupBy { sourceLabel(it.sourceId) }.forEach { (title, items) -> + add(BannerSection(title, false, items.chunked(2))) + } + } +} + +private fun ArtistImageCandidate.isWide(): Boolean { + val w = width ?: 0 + val h = height ?: 0 + val ratio = if (h > 0) w.toFloat() / h else 0f + if (ratio >= 1.6f) return true + val l = label.lowercase() + return "banner" in l || "fanart" in l || "backdrop" in l || "wide" in l +} + +private fun sourceLabel(id: String) = when (id.lowercase()) { + "wikipedia" -> "Wikipedia" + "theaudiodb", "audiodb" -> "TheAudioDB" + "deezer" -> "Deezer" + "musicbrainz" -> "MusicBrainz" + "wikidata" -> "Wikidata" + "jellyfin" -> "Jellyfin" + "navidrome", "subsonic" -> "Navidrome" + else -> id.replaceFirstChar { it.uppercase() } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt index 8fccfbb0..e756055b 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ImageCropDialog.kt @@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -23,6 +23,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color @@ -68,89 +70,97 @@ fun ImageCropDialog( val panel = if (wide) Modifier.size(760.dp, 520.dp) else Modifier.size(560.dp, 640.dp) InWindowPanel(onDismiss = onCancel, modifier = panel) { - Surface(color = MaterialTheme.colorScheme.surface) { - Column(Modifier.fillMaxSize().padding(16.dp)) { - Text(title, style = MaterialTheme.typography.titleLarge) - Text( - "Scroll to zoom · drag to pan", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), - modifier = Modifier.padding(bottom = 12.dp) - ) - Box( - Modifier - .fillMaxWidth() - .weight(1f) - .background(Color.Black) - .onSizeChanged { stage = Size(it.width.toFloat(), it.height.toFloat()) } - .pointerInput(image) { - awaitPointerEventScope { - while (true) { - val event = awaitPointerEvent() - if (event.type == PointerEventType.Scroll) { - val dy = event.changes.firstOrNull()?.scrollDelta?.y ?: 0f - zoom = (zoom * (1f - dy * 0.08f)).coerceIn(1f, 8f) - } + Column(Modifier.fillMaxSize().padding(16.dp)) { + Text(title, style = MaterialTheme.typography.titleLarge) + Text( + "Scroll to zoom · drag to pan", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(bottom = 12.dp) + ) + Box( + Modifier + .fillMaxWidth() + .weight(1f) + .clip(RoundedCornerShape(8.dp)) + .clipToBounds() + .background(Color.Black) + .onSizeChanged { stage = Size(it.width.toFloat(), it.height.toFloat()) } + .pointerInput(image) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + if (event.type == PointerEventType.Scroll) { + val dy = event.changes.firstOrNull()?.scrollDelta?.y ?: 0f + zoom = (zoom * (1f - dy * 0.08f)).coerceIn(1f, 8f) } } } - .pointerInput(image) { - detectDragGestures { change, drag -> - change.consume() - pan += drag - } - }, - contentAlignment = Alignment.Center - ) { - Canvas(Modifier.fillMaxSize()) { - val layout = cropLayout(size, image.width, image.height, aspect, zoom, pan) - drawImage( - image = bitmap, - dstOffset = IntOffset(layout.drawLeft.roundToInt(), layout.drawTop.roundToInt()), - dstSize = IntSize(layout.drawW.roundToInt().coerceAtLeast(1), layout.drawH.roundToInt().coerceAtLeast(1)) - ) - val dim = Color.Black.copy(alpha = 0.5f) - drawRect(dim, size = Size(size.width, layout.frameTop)) - drawRect( - dim, - topLeft = Offset(0f, layout.frameTop + layout.frameH), - size = Size(size.width, size.height - layout.frameTop - layout.frameH) - ) - drawRect( - dim, - topLeft = Offset(0f, layout.frameTop), - size = Size(layout.frameLeft, layout.frameH) - ) - drawRect( - dim, - topLeft = Offset(layout.frameLeft + layout.frameW, layout.frameTop), - size = Size(size.width - layout.frameLeft - layout.frameW, layout.frameH) - ) - val grid = Color.White.copy(alpha = 0.28f) - for (i in 1..2) { - val x = layout.frameLeft + layout.frameW * i / 3f - val y = layout.frameTop + layout.frameH * i / 3f - drawLine(grid, Offset(x, layout.frameTop), Offset(x, layout.frameTop + layout.frameH), 1f) - drawLine(grid, Offset(layout.frameLeft, y), Offset(layout.frameLeft + layout.frameW, y), 1f) + } + .pointerInput(image) { + detectDragGestures { change, drag -> + change.consume() + pan += drag } - drawRect( - Color.White, - topLeft = Offset(layout.frameLeft, layout.frameTop), - size = Size(layout.frameW, layout.frameH), - style = Stroke(width = 2.dp.toPx()) + }, + contentAlignment = Alignment.Center + ) { + Canvas(Modifier.fillMaxSize().clipToBounds()) { + val layout = cropLayout(size, image.width, image.height, aspect, zoom, pan) + drawImage( + image = bitmap, + dstOffset = IntOffset(layout.drawLeft.roundToInt(), layout.drawTop.roundToInt()), + dstSize = IntSize( + layout.drawW.roundToInt().coerceAtLeast(1), + layout.drawH.roundToInt().coerceAtLeast(1) ) + ) + val dim = Color.Black.copy(alpha = 0.5f) + drawRect(dim, size = Size(size.width, layout.frameTop)) + drawRect( + dim, + topLeft = Offset(0f, layout.frameTop + layout.frameH), + size = Size(size.width, size.height - layout.frameTop - layout.frameH) + ) + drawRect( + dim, + topLeft = Offset(0f, layout.frameTop), + size = Size(layout.frameLeft, layout.frameH) + ) + drawRect( + dim, + topLeft = Offset(layout.frameLeft + layout.frameW, layout.frameTop), + size = Size(size.width - layout.frameLeft - layout.frameW, layout.frameH) + ) + val grid = Color.White.copy(alpha = 0.28f) + for (i in 1..2) { + val x = layout.frameLeft + layout.frameW * i / 3f + val y = layout.frameTop + layout.frameH * i / 3f + drawLine(grid, Offset(x, layout.frameTop), Offset(x, layout.frameTop + layout.frameH), 1f) + drawLine(grid, Offset(layout.frameLeft, y), Offset(layout.frameLeft + layout.frameW, y), 1f) } + drawRect( + Color.White, + topLeft = Offset(layout.frameLeft, layout.frameTop), + size = Size(layout.frameW, layout.frameH), + style = Stroke(width = 2.dp.toPx()) + ) } - Row(Modifier.fillMaxWidth().padding(top = 12.dp), verticalAlignment = Alignment.CenterVertically) { - TextButton(onClick = onCancel) { Text("Cancel") } - Spacer(Modifier.weight(1f)) - TextButton( - onClick = { - val out = cropToAspect(image, aspect, zoom, pan, stage) - if (out != null) onCropped(out) else onCancel() + } + Row(Modifier.fillMaxWidth().padding(top = 12.dp), verticalAlignment = Alignment.CenterVertically) { + TextButton(onClick = onCancel) { Text("Cancel") } + Spacer(Modifier.weight(1f)) + TextButton( + onClick = { + val canvas = if (stage.width > 1f && stage.height > 1f) { + stage + } else { + Size(760f, 400f) } - ) { Text("Use photo") } - } + val out = cropToAspect(image, aspect, zoom, pan, canvas) + if (out != null) onCropped(out) else onCancel() + } + ) { Text("Use photo") } } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 76a9a4bb..1c4928cc 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -611,8 +611,10 @@ fun YuriDesktopApp(session: DesktopSession) { aspect = 3f, onCancel = { cropBanner = null }, onCropped = { cropped -> + runCatching { + profile = session.artists.applyLocalBanner(r.name, cropped) + } cropBanner = null - profile = session.artists.applyLocalBanner(r.name, cropped) } ) } From 330f0c50610a4a54212a42cb3d31329b2bfa8934 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 22:27:21 +0000 Subject: [PATCH 43/69] Show header candidates in a uniform clipped grid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aspect no longer drives layout — every tile is the same size and cropped to fit, grouped by source so they cannot overlap. --- .../desktop/ui/ArtistBannerPicker.kt | 80 +++++-------------- 1 file changed, 22 insertions(+), 58 deletions(-) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt index 7c82b7ce..0ce87e73 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/ArtistBannerPicker.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -15,8 +14,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme @@ -60,13 +61,15 @@ fun ArtistBannerPicker( loading = false } - val sections = remember(candidates) { groupCandidates(candidates) } + val sections = remember(candidates) { + candidates.groupBy { sourceLabel(it.sourceId) }.toList() + } - InWindowPanel(onDismiss = onDismiss, modifier = Modifier.width(680.dp).heightIn(max = 560.dp)) { + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.width(640.dp).heightIn(max = 560.dp)) { Column(Modifier.padding(20.dp)) { Text("Choose a header", style = MaterialTheme.typography.titleLarge) Text( - "Wide shots first · $artistName", + artistName, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), modifier = Modifier.padding(top = 4.dp, bottom = 12.dp) @@ -81,35 +84,24 @@ fun ArtistBannerPicker( "Couldn't find headers for $artistName", color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) ) - else -> LazyColumn( + else -> LazyVerticalGrid( + columns = GridCells.Fixed(3), modifier = Modifier.heightIn(max = 420.dp).fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(14.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), contentPadding = PaddingValues(bottom = 8.dp) ) { - sections.forEach { section -> - item(key = "h-${section.title}") { + sections.forEach { (title, items) -> + item(span = { GridItemSpan(3) }, key = "h-$title") { Text( - section.title, + title, style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), - modifier = Modifier.padding(bottom = 2.dp) + modifier = Modifier.padding(top = 4.dp) ) } - items(section.rows, key = { row -> row.joinToString { it.url } }) { row -> - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(10.dp) - ) { - row.forEach { c -> - BannerTile( - candidate = c, - wide = section.wide, - onClick = { onPicked(c) }, - modifier = Modifier.weight(1f) - ) - } - if (row.size == 1) Box(Modifier.weight(1f)) - } + items(items, key = { it.url }) { c -> + BannerTile(c) { onPicked(c) } } } } @@ -124,16 +116,14 @@ fun ArtistBannerPicker( @Composable private fun BannerTile( candidate: ArtistImageCandidate, - wide: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier + onClick: () -> Unit ) { val shape = RoundedCornerShape(10.dp) - Column(modifier) { + Column { Box( Modifier .fillMaxWidth() - .aspectRatio(if (wide) 3f else 1f) + .aspectRatio(1f) .clip(shape) .clipToBounds() .border(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.35f), shape) @@ -159,32 +149,6 @@ private fun BannerTile( } } -private data class BannerSection( - val title: String, - val wide: Boolean, - val rows: List> -) - -private fun groupCandidates(list: List): List { - val wide = list.filter { it.isWide() } - val rest = list.filterNot { it.isWide() } - return buildList { - if (wide.isNotEmpty()) add(BannerSection("Headers", true, wide.chunked(2))) - rest.groupBy { sourceLabel(it.sourceId) }.forEach { (title, items) -> - add(BannerSection(title, false, items.chunked(2))) - } - } -} - -private fun ArtistImageCandidate.isWide(): Boolean { - val w = width ?: 0 - val h = height ?: 0 - val ratio = if (h > 0) w.toFloat() / h else 0f - if (ratio >= 1.6f) return true - val l = label.lowercase() - return "banner" in l || "fanart" in l || "backdrop" in l || "wide" in l -} - private fun sourceLabel(id: String) = when (id.lowercase()) { "wikipedia" -> "Wikipedia" "theaudiodb", "audiodb" -> "TheAudioDB" From 68232a973754b16b2c4ae4df73a621156985f85e Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 22:39:55 +0000 Subject: [PATCH 44/69] Resolve playlist tracks by catalog identity Search hits (Subsonic/Jellyfin) are upserted into the library like mobile ensureTracksPresent, stored under a cross-source catalog key, and kept across rescans so Add to playlist actually shows. --- .../yuri/yuriplayer/core/library/Track.kt | 15 +++++++ .../core/library/TrackCatalogKeyTest.kt | 32 +++++++++++++++ .../desktop/DesktopPlaylistStore.kt | 40 +++++++++++++++---- .../yuri/yuriplayer/desktop/DesktopSession.kt | 19 +++++++-- .../desktop/ui/AddToPlaylistDialog.kt | 25 +++++++----- .../yuriplayer/desktop/ui/PlaylistPage.kt | 14 +++---- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 13 +++--- 7 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackCatalogKeyTest.kt diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt index 815c706f..9c6e4460 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt @@ -36,4 +36,19 @@ data class Track( val displayAlbum: String get() = album?.takeIf { it.isNotBlank() } ?: "Unknown Album" + + /** + * Cross-source identity (local + Jellyfin + Subsonic of the same recording). + * Playlists store this so a Navidrome search hit resolves to the library row. + */ + fun catalogKey(): String { + val t = foldSearch(title ?: displayTitle) + if (t.isEmpty()) return id + val a = foldSearch(albumArtist ?: artist ?: "") + val al = foldSearch(album ?: "") + val n = trackNumber ?: 0 + return "k:$t|$a|$al|$n" + } + + fun playlistKeys(): Set = setOf(id, catalogKey()) } diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackCatalogKeyTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackCatalogKeyTest.kt new file mode 100644 index 00000000..84f676cf --- /dev/null +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackCatalogKeyTest.kt @@ -0,0 +1,32 @@ +package capital.yuri.yuriplayer.core.library + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class TrackCatalogKeyTest { + @Test + fun localAndSubsonicShareIdentity() { + val local = Track( + id = "/music/BIRDBRAIN.flac", + uri = "file:///music/BIRDBRAIN.flac", + title = "BIRDBRAIN", + artist = "Jamie Paige feat. OK Glass", + album = "BIRDBRAIN", + trackNumber = 1, + path = "/music/BIRDBRAIN.flac", + sourceId = "local" + ) + val remote = Track( + id = "subsonic:abc", + uri = "https://navi.example/stream/abc", + title = "BIRDBRAIN", + artist = "Jamie Paige feat. OK Glass", + album = "BIRDBRAIN", + trackNumber = 1, + sourceId = "navidrome-1" + ) + assertEquals(local.catalogKey(), remote.catalogKey()) + assertTrue(local.catalogKey() in remote.playlistKeys()) + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt index 2604d92b..e4b3fd43 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -63,16 +63,33 @@ class DesktopPlaylistStore(configDir: String) { update(id) { it.copy(trackIds = trackIds.distinct()) } } - fun addTracks(id: String, trackIds: List) { - update(id) { it.copy(trackIds = (it.trackIds + trackIds).distinct()) } + fun addTracks(id: String, tracks: List) { + if (tracks.isEmpty()) return + update(id) { pl -> + val existing = pl.trackIds.toMutableList() + val seen = existing.toHashSet() + for (t in tracks) { + val already = t.playlistKeys().any { it in seen } + if (!already) { + existing += t.catalogKey() + seen += t.playlistKeys() + } + } + pl.copy(trackIds = existing) + } } fun playlistsContaining(trackId: String): Set = _playlists.value.filter { trackId in it.trackIds }.map { it.id }.toSet() - fun removeTracks(id: String, trackIds: List) { - val drop = trackIds.toHashSet() - update(id) { it.copy(trackIds = it.trackIds.filterNot { id -> id in drop }) } + fun playlistsContaining(track: Track): Set { + val keys = track.playlistKeys() + return _playlists.value.filter { pl -> pl.trackIds.any { it in keys } }.map { it.id }.toSet() + } + + fun removeTracks(id: String, tracks: List) { + val drop = tracks.flatMap { it.playlistKeys() }.toHashSet() + update(id) { it.copy(trackIds = it.trackIds.filterNot { key -> key in drop }) } } fun moveTrack(id: String, from: Int, to: Int) { @@ -203,12 +220,19 @@ data class DesktopPlaylist( fun artworkUri(library: List): String? { val active = covers.firstOrNull { it.id == activeCoverId } ?: covers.firstOrNull { !it.isSecret } if (active != null) return File(active.path).toURI().toString() - return trackIds.firstNotNullOfOrNull { id -> library.firstOrNull { it.id == id }?.artworkUri } + return trackIds.firstNotNullOfOrNull { id -> + library.firstOrNull { it.id == id || it.catalogKey() == id }?.artworkUri + } } fun tracks(library: List): List { - val byId = library.associateBy { it.id } - return trackIds.mapNotNull { byId[it] } + if (trackIds.isEmpty() || library.isEmpty()) return emptyList() + val byKey = HashMap(library.size * 2) + for (t in library) { + byKey.putIfAbsent(t.id, t) + byKey.putIfAbsent(t.catalogKey(), t) + } + return trackIds.mapNotNull { byKey[it] } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 154567e1..01af2773 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -323,11 +323,22 @@ class DesktopSession { _tracks.value = (_tracks.value + incoming).distinctBy { it.id } } + fun ensureTracks(incoming: List) { + if (incoming.isEmpty()) return + mergeTracks(incoming) + persistIndex() + } + private fun replaceSourceTracks(sourceId: String, incoming: List) { - val rest = _tracks.value.filterNot { - (it.sourceId ?: LOCAL_SCAN_ID) == sourceId - } - _tracks.value = (rest + incoming).distinctBy { it.id } + val referenced = playlists.playlists.value.flatMap { it.trackIds }.toHashSet() + val existing = _tracks.value + val orphans = existing.filter { t -> + (t.sourceId ?: LOCAL_SCAN_ID) == sourceId && + incoming.none { n -> n.id == t.id } && + (t.id in referenced || t.catalogKey() in referenced) + } + val rest = existing.filterNot { (it.sourceId ?: LOCAL_SCAN_ID) == sourceId } + _tracks.value = (rest + incoming + orphans).distinctBy { it.id } } private fun persistIndex() { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt index a638fa80..0f6a1a6f 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt @@ -46,21 +46,23 @@ fun AddToPlaylistDialog( tracks: List, store: DesktopPlaylistStore, library: List, - onDismiss: () -> Unit + onDismiss: () -> Unit, + onEnsureTracks: (List) -> Unit = {} ) { val playlists by store.playlists.collectAsState() - val ids = remember(tracks) { tracks.map { it.id } } var initiallyIn by remember { mutableStateOf(emptySet()) } var selected by remember { mutableStateOf(emptySet()) } var query by remember { mutableStateOf("") } var creating by remember { mutableStateOf(false) } var newName by remember { mutableStateOf("") } - LaunchedEffect(ids) { - val containing = if (ids.size == 1) { - store.playlistsContaining(ids.first()) + LaunchedEffect(tracks.map { it.id }) { + val containing = if (tracks.size == 1) { + store.playlistsContaining(tracks.first()) } else { - playlists.filter { pl -> ids.all { it in pl.trackIds } }.map { it.id }.toSet() + playlists.filter { pl -> + tracks.all { t -> pl.trackIds.any { it in t.playlistKeys() } } + }.map { it.id }.toSet() } initiallyIn = containing selected = containing @@ -97,7 +99,9 @@ fun AddToPlaylistDialog( TextButton(onClick = { creating = false; newName = "" }) { Text("Cancel") } Button( onClick = { - val pl = store.create(newName, trackIds = ids) + val pl = store.create(newName, trackIds = emptyList()) + store.addTracks(pl.id, tracks) + onEnsureTracks(tracks) selected = selected + pl.id initiallyIn = initiallyIn + pl.id creating = false @@ -165,7 +169,7 @@ fun AddToPlaylistDialog( Column(Modifier.weight(1f)) { Text(pl.name, maxLines = 1, overflow = TextOverflow.Ellipsis) Text( - "${pl.trackIds.size} songs", + "${pl.tracks(library).size.coerceAtLeast(pl.trackIds.size)} songs", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) ) @@ -178,8 +182,9 @@ fun AddToPlaylistDialog( onClick = { val toAdd = selected - initiallyIn val toRemove = initiallyIn - selected - toAdd.forEach { store.addTracks(it, ids) } - toRemove.forEach { store.removeTracks(it, ids) } + if (toAdd.isNotEmpty()) onEnsureTracks(tracks) + toAdd.forEach { store.addTracks(it, tracks) } + toRemove.forEach { store.removeTracks(it, tracks) } onDismiss() }, modifier = Modifier.fillMaxWidth().padding(top = 12.dp) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt index b5e2c705..99da6611 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -191,7 +191,7 @@ fun PlaylistPage( TextButton(onClick = { if (index < tracks.lastIndex) store.moveTrack(playlist.id, index, index + 1) }, enabled = index < tracks.lastIndex) { Text("Down") } - IconButton(onClick = { store.removeTracks(playlist.id, listOf(track.id)) }) { + IconButton(onClick = { store.removeTracks(playlist.id, listOf(track)) }) { Icon(Icons.Default.Delete, contentDescription = "Remove") } } @@ -202,10 +202,10 @@ fun PlaylistPage( if (showAdd) { AddSongsDialog( library = library, - existing = playlist.trackIds.toSet(), + existing = (playlist.trackIds + playlist.tracks(library).flatMap { it.playlistKeys() }).toSet(), onDismiss = { showAdd = false }, - onAdd = { ids -> - store.addTracks(playlist.id, ids) + onAdd = { added -> + store.addTracks(playlist.id, added) showAdd = false } ) @@ -233,7 +233,7 @@ private fun AddSongsDialog( library: List, existing: Set, onDismiss: () -> Unit, - onAdd: (List) -> Unit + onAdd: (List) -> Unit ) { var query by remember { mutableStateOf("") } val hits = remember(query, library) { @@ -253,10 +253,10 @@ private fun AddSongsDialog( ) LazyColumn(Modifier.weight(1f).padding(top = 8.dp)) { itemsIndexed(hits, key = { _, t -> t.id }) { _, track -> - val inPl = track.id in existing + val inPl = track.playlistKeys().any { it in existing } TrackRow( track = track.toRow(), - onClick = { if (!inPl) onAdd(listOf(track.id)) }, + onClick = { if (!inPl) onAdd(listOf(track)) }, showCover = true, showAlbum = true ) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 1c4928cc..28a7c428 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -139,15 +139,17 @@ fun YuriDesktopApp(session: DesktopSession) { fun openArtist(name: String) = push(Route.Artist(name)) fun playlistAddAlternate(songs: List): ContextMenuScope.() -> Unit = { - val ids = songs.map { it.id } val candidates = playlists - .filter { pl -> ids.any { it !in pl.trackIds } } + .filter { pl -> songs.any { t -> pl.trackIds.none { it in t.playlistKeys() } } } .sortedBy { it.name.lowercase() } if (candidates.isEmpty()) { item("Already in every playlist", enabled = false) {} } else { candidates.forEach { pl -> - item(pl.name) { session.playlists.addTracks(pl.id, ids) } + item(pl.name) { + session.ensureTracks(songs) + session.playlists.addTracks(pl.id, songs) + } } } } @@ -172,7 +174,7 @@ fun YuriDesktopApp(session: DesktopSession) { if (playlistId != null) { divider() item("Remove from this playlist", destructive = true) { - session.playlists.removeTracks(playlistId, listOf(track.id)) + session.playlists.removeTracks(playlistId, listOf(track)) } } } @@ -697,7 +699,8 @@ fun YuriDesktopApp(session: DesktopSession) { tracks = songs, store = session.playlists, library = tracks, - onDismiss = { addToPlaylist = null } + onDismiss = { addToPlaylist = null }, + onEnsureTracks = { session.ensureTracks(it) } ) } } From 71f7d2b20fe5b9303830e4abb3cbe3c976f525ae Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 22:45:06 +0000 Subject: [PATCH 45/69] Drop duplicate now-playing art; expand cover from the queue Sidebar no longer stacks extra covers and a second heart. Queue keeps the now-playing row; its art arrow grows a 1:1 cover above the details. Bottom tray spacing matches the sidebar gutters. --- .../components/layout/DesktopBentoLayout.kt | 2 +- .../components/player/NowPlayingSidebar.kt | 57 ++--------- .../components/player/QueuePanel.kt | 98 +++++++++++++++++-- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 2 - 4 files changed, 99 insertions(+), 60 deletions(-) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt index 3b3f3740..91417230 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/layout/DesktopBentoLayout.kt @@ -227,7 +227,7 @@ fun DesktopBentoLayout( } } } - Box(Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp)) { bottomBar() } + Box(Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 8.dp)) { bottomBar() } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index 671ac893..e6685853 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -2,25 +2,20 @@ package capital.yuri.yuriplayer.components.player import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import capital.yuri.yuriplayer.components.art.CoverArt -import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.CoverRef import capital.yuri.yuriplayer.components.model.TrackRowModel @@ -35,13 +30,12 @@ fun NowPlayingSidebar( onClearQueue: () -> Unit = {}, onClearHistory: () -> Unit = {}, onMoveUpcoming: ((from: Int, to: Int) -> Unit)? = null, - liked: Boolean = false, - onToggleLike: () -> Unit = {}, likedIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, songMenu: (TrackRowModel) -> List = { emptyList() }, modifier: Modifier = Modifier ) { + var artExpanded by remember { mutableStateOf(false) } Column( modifier .fillMaxSize() @@ -54,44 +48,7 @@ fun NowPlayingSidebar( fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface ) - Spacer(Modifier.height(12.dp)) - if (track != null) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { - CoverArt(model = track.artworkUri, size = 48.dp, corner = 8.dp) - Spacer(Modifier.width(12.dp)) - Column(Modifier.weight(1f)) { - Text( - "Now playing", - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.primary - ) - Text( - track.title, - fontWeight = FontWeight.SemiBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.primary - ) - Text( - track.subtitle, - style = MaterialTheme.typography.bodySmall, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) - ) - } - LikeHeart(liked = liked, onToggle = onToggleLike) - } - Spacer(Modifier.height(8.dp)) - } - CoverArt( - model = track?.artworkUri, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp) - .aspectRatio(1f), - corner = 16.dp - ) + Spacer(Modifier.height(8.dp)) QueuePanel( nowPlaying = track, upcoming = queue.dropWhile { it.id != track?.id }.drop(1), @@ -106,6 +63,8 @@ fun NowPlayingSidebar( likedIds = likedIds, onToggleLike = onToggleTrackLike, songMenu = songMenu, + artExpanded = artExpanded, + onToggleArt = { artExpanded = !artExpanded }, modifier = Modifier.weight(1f) ) } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt index c4d9b5dd..013a467e 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt @@ -1,19 +1,34 @@ package capital.yuri.yuriplayer.components.player +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -25,10 +40,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.CoverRef @@ -53,6 +71,8 @@ fun QueuePanel( likedIds: Set = emptySet(), onToggleLike: (String) -> Unit = {}, songMenu: (TrackRowModel) -> List = { emptyList() }, + artExpanded: Boolean = false, + onToggleArt: (() -> Unit)? = null, modifier: Modifier = Modifier ) { var tab by remember { mutableStateOf(QueueTab.Queue) } @@ -92,7 +112,28 @@ fun QueuePanel( } when (tab) { QueueTab.Queue -> { - nowPlaying?.let { NowPlayingQueueCard(it) } + nowPlaying?.let { np -> + AnimatedVisibility( + visible = artExpanded, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + ExpandableCover( + artworkUri = np.artworkUri, + expanded = true, + onToggle = onToggleArt, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 8.dp) + .aspectRatio(1f) + ) + } + NowPlayingQueueCard( + track = np, + showCover = !artExpanded, + onToggleArt = onToggleArt + ) + } if (upcoming.isEmpty()) { Text( "Nothing up next.", @@ -181,19 +222,26 @@ fun QueuePanel( } @Composable -private fun NowPlayingQueueCard(track: CoverRef) { +private fun NowPlayingQueueCard( + track: CoverRef, + showCover: Boolean, + onToggleArt: (() -> Unit)? +) { Row( Modifier .fillMaxWidth() .padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - capital.yuri.yuriplayer.components.art.CoverArt( - model = track.artworkUri, - size = 48.dp, - corner = 8.dp - ) - Spacer(Modifier.width(12.dp)) + if (showCover) { + ExpandableCover( + artworkUri = track.artworkUri, + expanded = false, + onToggle = onToggleArt, + modifier = Modifier.size(48.dp) + ) + Spacer(Modifier.width(12.dp)) + } Column(Modifier.weight(1f)) { Text( "Now playing", @@ -209,4 +257,38 @@ private fun NowPlayingQueueCard(track: CoverRef) { ) } } +} + +@Composable +private fun ExpandableCover( + artworkUri: String?, + expanded: Boolean, + onToggle: (() -> Unit)?, + modifier: Modifier = Modifier +) { + Box(modifier.clip(MaterialTheme.shapes.medium)) { + CoverArt( + model = artworkUri, + modifier = Modifier.fillMaxSize(), + corner = if (expanded) 16.dp else 8.dp + ) + if (onToggle != null) { + IconButton( + onClick = onToggle, + modifier = Modifier + .align(if (expanded) Alignment.TopEnd else Alignment.BottomEnd) + .padding(if (expanded) 8.dp else 2.dp) + .size(if (expanded) 32.dp else 22.dp) + .clip(CircleShape) + .background(Color.Black.copy(alpha = 0.45f)), + colors = IconButtonDefaults.iconButtonColors(contentColor = Color.White) + ) { + Icon( + if (expanded) Icons.Default.KeyboardArrowDown else Icons.Default.KeyboardArrowUp, + contentDescription = if (expanded) "Shrink cover" else "Expand cover", + modifier = Modifier.size(if (expanded) 20.dp else 16.dp) + ) + } + } + } } \ No newline at end of file diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 28a7c428..a017780c 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -439,8 +439,6 @@ fun YuriDesktopApp(session: DesktopSession) { if (base < 0) return@NowPlayingSidebar session.player.moveQueueItem(base + 1 + from, base + 1 + to) }, - liked = current?.id in liked, - onToggleLike = { current?.id?.let(session.collection::toggleLike) }, likedIds = liked, onToggleTrackLike = session.collection::toggleLike, songMenu = { row -> From 3e00abb4c756fc96d17ef90aff93c0da30c7c9d1 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 22:50:38 +0000 Subject: [PATCH 46/69] Persist desktop playback across restarts; snapshot playlist tracks Queue, position, shuffle, and repeat save like Android and restore paused. Playlists keep a copy of each track so Subsonic/search adds still render after a restart even if the index missed them. --- .../yuriplayer/core/player/PlayerSession.kt | 40 ++++++++++ .../core/player/PlayerSessionTest.kt | 25 +++++- .../desktop/DesktopPlaybackStore.kt | 80 +++++++++++++++++++ .../desktop/DesktopPlaylistStore.kt | 50 +++++++++--- .../yuri/yuriplayer/desktop/DesktopSession.kt | 45 ++++++++++- .../desktop/player/VlcjPlaybackEngine.kt | 7 +- 6 files changed, 232 insertions(+), 15 deletions(-) create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt index 296eb15a..ad28ee67 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -210,6 +210,35 @@ class PlayerSession( fun positionMs(): Long = engine.getPositionMs() fun durationMs(): Long = engine.getDurationMs() + fun snapshot(): PlaybackSnapshot = PlaybackSnapshot( + queue = _queue.value, + linear = linear, + index = _index.value, + history = _history.value, + shuffle = _shuffle.value, + repeat = _repeat.value, + volume = _volume.value, + positionMs = engine.getPositionMs() + ) + + fun restore(snap: PlaybackSnapshot, play: Boolean = false) { + if (snap.queue.isEmpty()) return + linear = snap.linear.ifEmpty { snap.queue } + _shuffle.value = snap.shuffle + _repeat.value = snap.repeat + setVolume(snap.volume) + _queue.value = snap.queue + _history.value = snap.history + val i = snap.index.coerceIn(snap.queue.indices) + _index.value = i + _current.value = snap.queue[i] + val nextIdx = peekNextIndex(fromUser = false) + val next = nextIdx?.let { snap.queue.getOrNull(it) }?.takeIf { it.id != snap.queue[i].id } + engine.load(snap.queue[i].toPlaybackMedia(), next?.toPlaybackMedia(), snap.positionMs.coerceAtLeast(0L)) + if (play) engine.play() else engine.pause() + warmSuccessor() + } + fun release() { engine.removeListener(listener) engine.release() @@ -290,3 +319,14 @@ class PlayerSession( private const val HISTORY_CAP = 50 } } + +data class PlaybackSnapshot( + val queue: List = emptyList(), + val linear: List = emptyList(), + val index: Int = 0, + val history: List = emptyList(), + val shuffle: Boolean = false, + val repeat: RepeatMode = RepeatMode.OFF, + val volume: Float = 1f, + val positionMs: Long = 0L +) diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt index 59d408b8..7d209271 100644 --- a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt @@ -70,6 +70,23 @@ class PlayerSessionTest { assertEquals("1", session.current.value?.id) assertEquals(listOf("1", "2", "9"), session.queue.value.map { it.id }) } + + @Test + fun restoreKeepsQueueAndDoesNotPlay() { + val engine = FakeEngine() + val session = PlayerSession(engine) + val list = tracks(4) + session.play(list, 2) + session.cycleRepeat() + val snap = session.snapshot().copy(positionMs = 12_000L) + val other = PlayerSession(FakeEngine()) + other.restore(snap, play = false) + assertEquals("3", other.current.value?.id) + assertEquals(12_000L, other.positionMs()) + assertEquals(listOf("1", "2", "3", "4"), other.queue.value.map { it.id }) + assertEquals(RepeatMode.ALL, other.repeat.value) + assertFalse(other.isPlaying.value) + } } private class FakeEngine : PlaybackEngine { @@ -81,11 +98,14 @@ private class FakeEngine : PlaybackEngine { var lastSeek = -1L var played = false private var next: PlaybackMedia? = null + private var pos = 0L override fun load(current: PlaybackMedia, successor: PlaybackMedia?, startPositionMs: Long) { _uri.value = current.uri next = successor - _playing.value = true + lastSeek = startPositionMs + pos = startPositionMs + _playing.value = false } override fun play() { @@ -103,9 +123,10 @@ private class FakeEngine : PlaybackEngine { override fun seekTo(positionMs: Long) { lastSeek = positionMs + pos = positionMs } - override fun getPositionMs(): Long = 0 + override fun getPositionMs(): Long = pos override fun getDurationMs(): Long = 180_000 override fun hasPreparedNext(): Boolean = next != null override fun playPreparedNext(): Boolean = false diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt new file mode 100644 index 00000000..4b3128ff --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt @@ -0,0 +1,80 @@ +package capital.yuri.yuriplayer.desktop + +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.player.PlaybackSnapshot +import capital.yuri.yuriplayer.core.player.RepeatMode +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File + +/** + * Same job as Android [capital.yuri.yuriplayer.player.PlaybackStateStore]: + * queue + position survive restart. Restore is always paused. + */ +class DesktopPlaybackStore(configDir: String) { + private val file = File(configDir, "playback_state.json") + private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true } + + fun save(snap: PlaybackSnapshot) { + if (snap.queue.isEmpty()) { + clear() + return + } + runCatching { + file.parentFile?.mkdirs() + val dto = Dto( + positionMs = snap.positionMs.coerceAtLeast(0L), + index = snap.index, + shuffle = snap.shuffle, + repeat = snap.repeat.name, + volume = snap.volume, + queue = snap.queue, + linear = snap.linear, + history = snap.history, + savedAt = System.currentTimeMillis() + ) + val tmp = File(file.parentFile, "playback_state.json.tmp") + tmp.writeText(json.encodeToString(dto)) + if (!tmp.renameTo(file)) { + tmp.copyTo(file, overwrite = true) + tmp.delete() + } + } + } + + fun load(): PlaybackSnapshot? { + if (!file.isFile) return null + return runCatching { + val dto = json.decodeFromString(file.readText()) + if (dto.queue.isEmpty()) return@runCatching null + PlaybackSnapshot( + queue = dto.queue, + linear = dto.linear.ifEmpty { dto.queue }, + index = dto.index, + history = dto.history, + shuffle = dto.shuffle, + repeat = runCatching { RepeatMode.valueOf(dto.repeat) }.getOrDefault(RepeatMode.OFF), + volume = dto.volume.coerceIn(0f, 1f), + positionMs = dto.positionMs + ) + }.getOrNull() + } + + fun clear() { + runCatching { if (file.exists()) file.delete() } + } + + @Serializable + private data class Dto( + val positionMs: Long = 0L, + val index: Int = 0, + val shuffle: Boolean = false, + val repeat: String = "OFF", + val volume: Float = 1f, + val queue: List = emptyList(), + val linear: List = emptyList(), + val history: List = emptyList(), + val savedAt: Long = 0L + ) +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt index e4b3fd43..f740bcb3 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -68,14 +68,38 @@ class DesktopPlaylistStore(configDir: String) { update(id) { pl -> val existing = pl.trackIds.toMutableList() val seen = existing.toHashSet() + val snaps = pl.snapshots.toMutableList() for (t in tracks) { val already = t.playlistKeys().any { it in seen } if (!already) { - existing += t.catalogKey() + existing += t.id seen += t.playlistKeys() } + val i = snaps.indexOfFirst { s -> + s.id == t.id || s.catalogKey() == t.catalogKey() || t.id in s.playlistKeys() + } + if (i < 0) snaps += t else snaps[i] = t } - pl.copy(trackIds = existing) + pl.copy(trackIds = existing, snapshots = snaps) + } + } + + fun remember(tracks: List) { + if (tracks.isEmpty()) return + var changed = false + val next = _playlists.value.map { pl -> + var snaps = pl.snapshots + for (t in tracks) { + if (pl.trackIds.none { it in t.playlistKeys() }) continue + val i = snaps.indexOfFirst { s -> s.id == t.id || s.catalogKey() == t.catalogKey() } + snaps = if (i < 0) snaps + t else snaps.toMutableList().also { it[i] = t } + changed = true + } + if (snaps === pl.snapshots) pl else pl.copy(snapshots = snaps) + } + if (changed) { + _playlists.value = next + persist() } } @@ -89,7 +113,12 @@ class DesktopPlaylistStore(configDir: String) { fun removeTracks(id: String, tracks: List) { val drop = tracks.flatMap { it.playlistKeys() }.toHashSet() - update(id) { it.copy(trackIds = it.trackIds.filterNot { key -> key in drop }) } + update(id) { + it.copy( + trackIds = it.trackIds.filterNot { key -> key in drop }, + snapshots = it.snapshots.filterNot { t -> t.playlistKeys().any { k -> k in drop } } + ) + } } fun moveTrack(id: String, from: Int, to: Int) { @@ -215,23 +244,26 @@ data class DesktopPlaylist( val covers: List = emptyList(), val activeCoverId: String? = null, val createdAtMs: Long = 0L, - val updatedAtMs: Long = 0L + val updatedAtMs: Long = 0L, + val snapshots: List = emptyList() ) { fun artworkUri(library: List): String? { val active = covers.firstOrNull { it.id == activeCoverId } ?: covers.firstOrNull { !it.isSecret } if (active != null) return File(active.path).toURI().toString() - return trackIds.firstNotNullOfOrNull { id -> - library.firstOrNull { it.id == id || it.catalogKey() == id }?.artworkUri - } + return tracks(library).firstOrNull()?.artworkUri } fun tracks(library: List): List { - if (trackIds.isEmpty() || library.isEmpty()) return emptyList() - val byKey = HashMap(library.size * 2) + if (trackIds.isEmpty()) return emptyList() + val byKey = HashMap() for (t in library) { byKey.putIfAbsent(t.id, t) byKey.putIfAbsent(t.catalogKey(), t) } + for (t in snapshots) { + byKey.putIfAbsent(t.id, t) + byKey.putIfAbsent(t.catalogKey(), t) + } return trackIds.mapNotNull { byKey[it] } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 01af2773..bcffa969 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -9,6 +9,7 @@ import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.os.OsMediaControls import capital.yuri.yuriplayer.core.platform.appDirectories import capital.yuri.yuriplayer.core.player.PlayerSession +import capital.yuri.yuriplayer.core.player.PlaybackSnapshot import capital.yuri.yuriplayer.core.source.JellyfinCatalog import capital.yuri.yuriplayer.core.source.LibrarySourceStore import capital.yuri.yuriplayer.core.source.RemoteAccount @@ -92,6 +93,7 @@ class DesktopSession { val playlists = DesktopPlaylistStore(dirs.configDir) val sources = LibrarySourceStore(dirs.configDir) private val index = DesktopIndexStore(dirs.cacheDir) + private val playbackStore = DesktopPlaybackStore(dirs.configDir) val jellyfin = JellyfinCatalog(http, sources.deviceId) val subsonic = SubsonicCatalog(http) val artists = ArtistInfoClient( @@ -100,6 +102,7 @@ class DesktopSession { ) { name -> libraryArtistImages(name) } private var ticker: Job? = null + private var persistJob: Job? = null private var scanJob: Job? = null private var sourceJob: Job? = null private val scanGate = Mutex() @@ -149,6 +152,8 @@ class DesktopSession { sources.remotes.collect { refreshScanSources() } } hydrateIndex() + restorePlayback() + startPersist() } private fun hydrateIndex() { @@ -174,10 +179,45 @@ class DesktopSession { cached.isEmpty() -> "Indexing…" else -> "${cached.size} tracks" } - // Same as mobile: warm index → partial; empty → first full walk. + val snaps = playlists.playlists.value.flatMap { it.snapshots } + if (snaps.isNotEmpty()) mergeTracks(snaps) requestScan(force = cached.isEmpty()) } + private fun restorePlayback() { + val snap = playbackStore.load() ?: return + player.restore(resolveSnap(snap), play = false) + _positionMs.value = player.positionMs() + _durationMs.value = player.durationMs() + } + + private fun resolveSnap(snap: PlaybackSnapshot): PlaybackSnapshot { + val lib = _tracks.value + if (lib.isEmpty()) return snap + val byKey = HashMap(lib.size * 2) + for (t in lib) { + byKey.putIfAbsent(t.id, t) + byKey.putIfAbsent(t.catalogKey(), t) + } + fun resolve(list: List) = list.map { t -> + byKey[t.id] ?: byKey[t.catalogKey()] ?: t + } + return snap.copy( + queue = resolve(snap.queue), + linear = resolve(snap.linear), + history = resolve(snap.history) + ) + } + + private fun startPersist() { + persistJob = scope.launch { + while (isActive) { + delay(5_000) + playbackStore.save(player.snapshot()) + } + } + } + fun playTrack(track: Track) { val list = _tracks.value val i = list.indexOfFirst { it.id == track.id }.coerceAtLeast(0) @@ -327,6 +367,7 @@ class DesktopSession { if (incoming.isEmpty()) return mergeTracks(incoming) persistIndex() + playlists.remember(incoming) } private fun replaceSourceTracks(sourceId: String, incoming: List) { @@ -780,7 +821,9 @@ class DesktopSession { } fun release() { + persistJob?.cancel() ticker?.cancel() + playbackStore.save(player.snapshot()) media.release() player.release() runCatching { http.close() } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index 98b7a66c..18281cbc 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -112,8 +112,8 @@ class VlcjPlaybackEngine : PlaybackEngine { return@onVlc } val mrl = toMrl(current.uri) - System.err.println("Vlcj play $mrl") - val ok = p.media().play(mrl, *mediaOptions(current)) + System.err.println("Vlcj load $mrl") + val ok = p.media().prepare(mrl, *mediaOptions(current)) if (!ok) { listeners.forEach { it.onError("Could not open ${current.title}", recoverable = true) } return@onVlc @@ -121,8 +121,9 @@ class VlcjPlaybackEngine : PlaybackEngine { p.audio().setVolume(100) p.audio().setMute(false) if (startPositionMs > 0) p.controls().setTime(startPositionMs) + p.controls().setPause(true) _currentUri.value = current.uri - _isPlaying.value = true + _isPlaying.value = false } } From 63b3e1078ac82a700c37471acee4dd23437f2df3 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 22:55:23 +0000 Subject: [PATCH 47/69] Collapse multi-source album tracks like mobile Clancy (and any local+Jellyfin+Navidrome album) is one row per song. Cloud and explicit badges open a sources picker; the chosen source is saved and used for playback, same as Android. --- .../yuriplayer/components/album/AlbumPage.kt | 22 ++- .../components/list/SongBadgeRow.kt | 68 ++++++++ .../yuriplayer/components/list/TrackRow.kt | 28 +++- .../yuriplayer/components/model/UiModels.kt | 52 +++++-- .../yuriplayer/core/library/TrackIdentity.kt | 99 ++++++++++++ .../core/library/AlbumCollapseTest.kt | 48 ++++++ .../yuri/yuriplayer/desktop/DesktopSession.kt | 1 + .../yuriplayer/desktop/DesktopSourcePrefs.kt | 52 +++++++ .../desktop/ui/SourcesPickerDialog.kt | 145 ++++++++++++++++++ .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 52 ++++++- 10 files changed, 532 insertions(+), 35 deletions(-) create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/SongBadgeRow.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/TrackIdentity.kt create mode 100644 core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/AlbumCollapseTest.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSourcePrefs.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt index 80da5d45..7314211c 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/album/AlbumPage.kt @@ -57,6 +57,7 @@ fun AlbumPage( likedTrackIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, songMenu: (TrackRowModel) -> List = { emptyList() }, + onSources: (TrackRowModel) -> Unit = {}, modifier: Modifier = Modifier ) { val coverColors by rememberCoverColors( @@ -75,12 +76,12 @@ fun AlbumPage( when (widthClass) { WindowWidthClass.Compact -> AlbumPageCompact( album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist, - liked, onToggleLike, likedTrackIds, onToggleTrackLike, songMenu + liked, onToggleLike, likedTrackIds, onToggleTrackLike, songMenu, onSources ) WindowWidthClass.Medium, WindowWidthClass.Expanded -> AlbumPageWide( album, playing, onBack, onPlay, onTrack, onEdit, onEditTrack, onArtist, - liked, onToggleLike, likedTrackIds, onToggleTrackLike, songMenu + liked, onToggleLike, likedTrackIds, onToggleTrackLike, songMenu, onSources ) } } @@ -101,7 +102,8 @@ private fun AlbumPageCompact( onToggleLike: () -> Unit, likedTrackIds: Set, onToggleTrackLike: (String) -> Unit, - songMenu: (TrackRowModel) -> List + songMenu: (TrackRowModel) -> List, + onSources: (TrackRowModel) -> Unit ) { Column(Modifier.fillMaxSize()) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -128,7 +130,7 @@ private fun AlbumPageCompact( corner = 12.dp ) AlbumMeta(album, playing, onPlay, onArtist, liked, onToggleLike, Modifier.padding(20.dp)) - TrackList(album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike, songMenu) + TrackList(album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike, songMenu, onSources) } } @@ -146,7 +148,8 @@ private fun AlbumPageWide( onToggleLike: () -> Unit, likedTrackIds: Set, onToggleTrackLike: (String) -> Unit, - songMenu: (TrackRowModel) -> List + songMenu: (TrackRowModel) -> List, + onSources: (TrackRowModel) -> Unit ) { Row(Modifier.fillMaxSize().padding(24.dp)) { Column( @@ -176,7 +179,10 @@ private fun AlbumPageWide( Spacer(Modifier.height(16.dp)) AlbumMeta(album, playing, onPlay, onArtist, liked, onToggleLike) } - TrackList(album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike, songMenu, modifier = Modifier.weight(1f)) + TrackList( + album, onTrack, onEditTrack, likedTrackIds, onToggleTrackLike, songMenu, onSources, + modifier = Modifier.weight(1f) + ) } } @@ -234,6 +240,7 @@ private fun TrackList( likedTrackIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, songMenu: (TrackRowModel) -> List = { emptyList() }, + onSources: (TrackRowModel) -> Unit = {}, modifier: Modifier = Modifier ) { val discs = album.tracks.groupBy { it.discNumber ?: 1 } @@ -260,7 +267,8 @@ private fun TrackList( showAlbum = false, liked = track.id in likedTrackIds, onToggleLike = { onToggleTrackLike(track.id) }, - contextItems = songMenu(track) + contextItems = songMenu(track), + onSources = { onSources(track) } ) } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/SongBadgeRow.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/SongBadgeRow.kt new file mode 100644 index 00000000..77587725 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/SongBadgeRow.kt @@ -0,0 +1,68 @@ +package capital.yuri.yuriplayer.components.list + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Cloud +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun SongBadgeRow( + explicit: Boolean, + multiSource: Boolean, + modifier: Modifier = Modifier, + onSources: (() -> Unit)? = null +) { + if (!explicit && !multiSource) return + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (explicit) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(2.dp)) + .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f)) + .padding(horizontal = 3.dp, vertical = 1.dp), + contentAlignment = Alignment.Center + ) { + Text( + "E", + fontSize = 9.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.surface, + lineHeight = 10.sp + ) + } + } + if (multiSource) { + Icon( + Icons.Default.Cloud, + contentDescription = "Sources", + modifier = Modifier + .size(14.dp) + .then( + if (onSources != null) Modifier.clickable(onClick = onSources) + else Modifier + ), + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + } +} diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt index 0a62bd24..26847597 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/TrackRow.kt @@ -32,7 +32,8 @@ fun TrackRow( onLongClick: (() -> Unit)? = null, liked: Boolean = false, onToggleLike: (() -> Unit)? = null, - contextItems: List = emptyList() + contextItems: List = emptyList(), + onSources: (() -> Unit)? = null ) { val highlight = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f) ContextMenuAnchor(items = contextItems) { openMenu -> @@ -64,13 +65,24 @@ fun TrackRow( Spacer(Modifier.width(12.dp)) } Column(Modifier.weight(1f)) { - Text( - track.title, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontWeight = if (track.highlighted) FontWeight.SemiBold else FontWeight.Normal, - color = MaterialTheme.colorScheme.onSurface - ) + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + track.title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = if (track.highlighted) FontWeight.SemiBold else FontWeight.Normal, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.weight(1f, fill = false) + ) + if (track.explicit || track.multiSource) { + Spacer(Modifier.width(6.dp)) + SongBadgeRow( + explicit = track.explicit, + multiSource = track.multiSource, + onSources = onSources + ) + } + } val sub = if (showAlbum) "${track.artist} · ${track.album}" else track.artist Text( sub, diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt index 2e8019f3..1d177b9d 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt @@ -1,6 +1,9 @@ package capital.yuri.yuriplayer.components.model import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.albumGroupKey +import capital.yuri.yuriplayer.core.library.collapseAlbumTracks +import capital.yuri.yuriplayer.core.library.isExplicit import capital.yuri.yuriplayer.core.library.matchesSearch data class CoverRef( @@ -19,7 +22,10 @@ data class TrackRowModel( val trackNumber: Int? = null, val discNumber: Int? = null, val artworkUri: String? = null, - val highlighted: Boolean = false + val highlighted: Boolean = false, + val explicit: Boolean = false, + val multiSource: Boolean = false, + val sourceIds: List = emptyList() ) data class AlbumPageModel( @@ -55,7 +61,10 @@ fun Track.toRow(highlighted: Boolean = false) = TrackRowModel( trackNumber = trackNumber, discNumber = discNumber, artworkUri = artworkUri, - highlighted = highlighted + highlighted = highlighted, + explicit = isExplicit(), + multiSource = false, + sourceIds = listOf(id) ) fun Track.toCover() = CoverRef( @@ -65,23 +74,38 @@ fun Track.toCover() = CoverRef( artworkUri = artworkUri ) -fun List.albums(): List = - groupBy { (it.album ?: "").trim().ifBlank { "Unknown Album" } to (it.albumArtist ?: it.artist ?: "") } - .map { (key, tracks) -> - val sorted = tracks.sortedWith( - compareBy { it.discNumber ?: 1 }.thenBy { it.trackNumber ?: Int.MAX_VALUE } - ) +fun List.albums( + preferredIds: Map = emptyMap() +): List = + groupBy { albumGroupKey(it) } + .mapNotNull { (_, tracks) -> + val collapsed = collapseAlbumTracks(tracks, preferredIds) + val first = collapsed.firstOrNull()?.preferred ?: return@mapNotNull null AlbumPageModel( - id = "${key.second}::${key.first}", - title = key.first, - artist = key.second.ifBlank { sorted.first().displayArtist }, - artworkUri = sorted.firstNotNullOfOrNull { it.artworkUri }, - year = sorted.mapNotNull { it.year }.maxOrNull(), - tracks = sorted.map { it.toRow() } + id = albumGroupKey(first), + title = first.displayAlbum, + artist = first.albumArtist?.takeIf { it.isNotBlank() } ?: first.displayArtist, + artworkUri = collapsed.firstNotNullOfOrNull { it.preferred.artworkUri }, + year = collapsed.mapNotNull { it.preferred.year }.maxOrNull(), + tracks = collapsed.map { it.toRow() } ) } .sortedBy { it.title.lowercase() } +private fun capital.yuri.yuriplayer.core.library.CollapsedTrack.toRow() = TrackRowModel( + id = preferred.id, + title = preferred.displayTitle, + artist = preferred.displayArtist, + album = preferred.displayAlbum, + durationMs = preferred.durationMs, + trackNumber = preferred.trackNumber, + discNumber = preferred.discNumber, + artworkUri = preferred.artworkUri, + explicit = explicit, + multiSource = multiSource, + sourceIds = sources.map { it.id } +) + fun List.artistPage( name: String, likedIds: Set = emptySet(), diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/TrackIdentity.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/TrackIdentity.kt new file mode 100644 index 00000000..53b16151 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/TrackIdentity.kt @@ -0,0 +1,99 @@ +package capital.yuri.yuriplayer.core.library + +/** + * Cross-source collapse for one release. Same rules as mobile album pages: + * disc + track + normalized title, never squash a whole album onto one row. + */ +private val FEAT_IN_PARENS = Regex( + """(?i)[\(\[\{]\s*(feat\.?|ft\.?|featuring)\s+[^\)\]\}]+[\)\]\}]""" +) +private val FEAT_SUFFIX = Regex( + """(?i)\s+(feat\.?|ft\.?|featuring)\s+.+$""" +) + +fun normalizeTitle(title: String?): String { + if (title.isNullOrBlank()) return "" + var t = title.trim() + t = FEAT_IN_PARENS.replace(t, "") + t = FEAT_SUFFIX.replace(t, "") + return foldSearch(t) +} + +fun albumGroupKey(track: Track): String { + val album = foldSearch(track.album ?: track.displayAlbum) + val artist = foldSearch(track.albumArtist ?: track.artist ?: "") + return "$album::$artist" +} + +fun albumPageIdentity(track: Track): String { + val disc = track.discNumber ?: 1 + val tn = track.trackNumber + val title = normalizeTitle(track.title ?: track.displayTitle) + return if (tn != null && tn > 0 && title.isNotEmpty()) { + "n:$disc|$tn|$title" + } else if (title.isNotEmpty()) { + "t:$title|${foldSearch(track.album ?: "")}" + } else { + "id:${track.id}" + } +} + +fun Track.sourceRank(): Int { + val sid = sourceId.orEmpty() + val uri = uri + return when { + sid == "local" || uri.startsWith("file:") -> 0 + id.startsWith("jellyfin:") || sid.contains("jelly", true) -> 10 + id.startsWith("subsonic:") || sid.contains("navi", true) || sid.contains("subsonic", true) -> 20 + else -> 50 + } +} + +fun Track.isExplicit(): Boolean { + val t = title.orEmpty() + val g = genre.orEmpty() + if (t.contains("explicit", ignoreCase = true)) return true + if (g.contains("explicit", ignoreCase = true)) return true + if (Regex("""(?i)[\(\[]\s*e\s*[\)\]]""").containsMatchIn(t)) return true + return false +} + +data class CollapsedTrack( + val preferred: Track, + val sources: List +) { + val identity: String get() = albumPageIdentity(preferred) + val multiSource: Boolean get() = sources.size > 1 + val explicit: Boolean get() = sources.any { it.isExplicit() } +} + +fun collapseAlbumTracks( + tracks: List, + preferredIds: Map = emptyMap() +): List { + if (tracks.isEmpty()) return emptyList() + val groups = LinkedHashMap>() + for (t in tracks) { + groups.getOrPut(albumPageIdentity(t)) { mutableListOf() }.add(t) + } + return groups.values.map { group -> + val identity = albumPageIdentity(group.first()) + val prefId = preferredIds[identity] + val preferred = prefId?.let { id -> + group.firstOrNull { it.id == id || it.sourceId == id } + } ?: group.minByOrNull { it.sourceRank() } ?: group.first() + CollapsedTrack(preferred, group.distinctBy { it.id }) + }.sortedWith( + compareBy { it.preferred.discNumber ?: 1 } + .thenBy { it.preferred.trackNumber ?: Int.MAX_VALUE } + .thenBy { it.preferred.displayTitle.lowercase() } + ) +} + +fun pickPreferred(sources: List, preferredId: String?): Track { + if (sources.isEmpty()) error("empty sources") + preferredId?.let { id -> + sources.firstOrNull { it.id == id || it.sourceId == id }?.let { return it } + } + return sources.minByOrNull { it.sourceRank() } ?: sources.first() +} diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/AlbumCollapseTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/AlbumCollapseTest.kt new file mode 100644 index 00000000..da154c18 --- /dev/null +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/AlbumCollapseTest.kt @@ -0,0 +1,48 @@ +package capital.yuri.yuriplayer.core.library + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class AlbumCollapseTest { + private fun clancy(source: String, prefix: String) = (1..14).map { n -> + Track( + id = "$prefix-$n", + uri = if (source == "local") "file:///Clancy/$n.flac" else "https://navi/stream/$n", + title = titles[n - 1], + artist = "twenty one pilots", + albumArtist = "twenty one pilots", + album = "Clancy", + trackNumber = n, + discNumber = 1, + sourceId = source + ) + } + + @Test + fun localAndNavidromeCollapseToFourteen() { + val mixed = clancy("local", "file") + clancy("navidrome", "subsonic") + val collapsed = collapseAlbumTracks(mixed) + assertEquals(14, collapsed.size) + assertTrue(collapsed.all { it.multiSource }) + assertEquals("Overcompensate", collapsed.first().preferred.title) + assertEquals(1, collapsed.first().preferred.trackNumber) + assertEquals("Paladin Strait", collapsed.last().preferred.title) + assertTrue(collapsed.first().preferred.uri.startsWith("file:")) + } + + @Test + fun preferredSourceWins() { + val mixed = clancy("local", "file") + clancy("navidrome", "subsonic") + val id = albumPageIdentity(mixed.first()) + val collapsed = collapseAlbumTracks(mixed, mapOf(id to "subsonic-1")) + assertEquals("subsonic-1", collapsed.first().preferred.id) + } + + private val titles = listOf( + "Overcompensate", "Next Semester", "Backslide", "Midwest Indigo", + "Routines In The Night", "Vignette", "The Craving (Jenna's Version)", + "Lavish", "Navigating", "Snap Back", "Oldies Station", + "At The Risk Of Feeling Dumb", "The Craving (single)", "Paladin Strait" + ) +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index bcffa969..d04bf47d 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -91,6 +91,7 @@ class DesktopSession { val layout = DesktopLayoutStore(dirs.configDir) val collection = DesktopCollection(dirs.configDir) val playlists = DesktopPlaylistStore(dirs.configDir) + val sourcePrefs = DesktopSourcePrefs(dirs.configDir) val sources = LibrarySourceStore(dirs.configDir) private val index = DesktopIndexStore(dirs.cacheDir) private val playbackStore = DesktopPlaybackStore(dirs.configDir) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSourcePrefs.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSourcePrefs.kt new file mode 100644 index 00000000..544f9259 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSourcePrefs.kt @@ -0,0 +1,52 @@ +package capital.yuri.yuriplayer.desktop + +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File + +/** + * Preferred playback source per collapsed track identity — same role as + * Android SourceResolver overrides. + */ +class DesktopSourcePrefs(configDir: String) { + private val file = File(configDir, "source_prefs.json") + private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true } + private val lock = Any() + private var map: MutableMap = load() + + fun snapshot(): Map = synchronized(lock) { map.toMap() } + + fun get(identity: String): String? = synchronized(lock) { map[identity] } + + fun set(identity: String, trackId: String) { + synchronized(lock) { + map[identity] = trackId + persist() + } + } + + fun clear(identity: String) { + synchronized(lock) { + map.remove(identity) + persist() + } + } + + private fun load(): MutableMap { + if (!file.isFile) return mutableMapOf() + return runCatching { + json.decodeFromString(file.readText()).preferred.toMutableMap() + }.getOrDefault(mutableMapOf()) + } + + private fun persist() { + runCatching { + file.parentFile?.mkdirs() + file.writeText(json.encodeToString(Dto(map))) + } + } + + @Serializable + private data class Dto(val preferred: Map = emptyMap()) +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt new file mode 100644 index 00000000..a03545df --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt @@ -0,0 +1,145 @@ +package capital.yuri.yuriplayer.desktop.ui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Cloud +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.SdStorage +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.dialog.InWindowPanel +import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.source.RemoteAccount +import capital.yuri.yuriplayer.core.source.SourceKind +import capital.yuri.yuriplayer.core.library.LocalLibraryScanner + +data class SourceChoice( + val track: Track, + val label: String, + val typeLabel: String, + val writable: Boolean, + val preferred: Boolean +) + +fun sourceChoices( + sources: List, + remotes: List, + preferredId: String? +): List { + val byId = remotes.associateBy { it.id } + return sources.map { t -> + val remote = t.sourceId?.let { byId[it] } + val local = t.sourceId == LocalLibraryScanner.SOURCE_LOCAL || t.uri.startsWith("file:") + val writable = local + val typeLabel = when { + local -> "Local" + remote?.kind == SourceKind.JELLYFIN -> "Jellyfin" + remote?.kind == SourceKind.SUBSONIC -> "Navidrome" + t.id.startsWith("jellyfin:") -> "Jellyfin" + t.id.startsWith("subsonic:") -> "Subsonic" + else -> "Source" + } + val label = when { + local -> "On this device" + remote != null && remote.name.isNotBlank() -> remote.name + else -> typeLabel + } + SourceChoice( + track = t, + label = label, + typeLabel = typeLabel, + writable = writable, + preferred = preferredId != null && (t.id == preferredId || t.sourceId == preferredId) + ) + } +} + +@Composable +fun SourcesPickerDialog( + title: String, + choices: List, + onDismiss: () -> Unit, + onPick: (Track) -> Unit +) { + InWindowPanel(onDismiss = onDismiss, modifier = Modifier.width(420.dp).height(480.dp)) { + Column(Modifier.padding(20.dp)) { + Text("Sources", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text( + title, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + modifier = Modifier.padding(top = 4.dp) + ) + Text( + "Tap a source to prefer it for playback.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.45f), + modifier = Modifier.padding(top = 4.dp, bottom = 8.dp) + ) + HorizontalDivider() + LazyColumn(Modifier.weight(1f).padding(top = 8.dp)) { + items(choices, key = { it.track.id }) { c -> + Row( + Modifier + .fillMaxWidth() + .clickable { onPick(c.track) } + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = when { + !c.writable -> Icons.Default.Lock + c.writable && c.typeLabel == "Local" -> Icons.Default.SdStorage + else -> Icons.Default.Cloud + }, + contentDescription = null, + modifier = Modifier.size(22.dp), + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) + ) + Spacer(Modifier.width(14.dp)) + Column(Modifier.weight(1f)) { + Text( + c.label, + fontWeight = if (c.preferred) FontWeight.SemiBold else FontWeight.Normal + ) + Text( + "${c.typeLabel} · ${if (c.writable) "Writable" else "Read-only"}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + if (c.preferred) { + Icon( + Icons.Default.Check, + contentDescription = "Preferred", + tint = MaterialTheme.colorScheme.primary + ) + } + } + } + } + TextButton(onClick = onDismiss, modifier = Modifier.align(Alignment.End)) { + Text("Done") + } + } + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index a017780c..b74498fa 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -30,6 +30,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -65,6 +66,9 @@ import capital.yuri.yuriplayer.components.theme.YuriTheme import capital.yuri.yuriplayer.components.theme.playerColorsFromPixels import capital.yuri.yuriplayer.core.artist.ArtistProfile import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.albumPageIdentity +import capital.yuri.yuriplayer.core.library.pickPreferred +import capital.yuri.yuriplayer.core.library.sourceRank import capital.yuri.yuriplayer.desktop.DesktopCollection import capital.yuri.yuriplayer.desktop.DesktopSession import kotlinx.coroutines.launch @@ -100,9 +104,11 @@ fun YuriDesktopApp(session: DesktopSession) { val liked by session.collection.liked.collectAsState() val pins by session.collection.pinned.collectAsState() val playlists by session.playlists.playlists.collectAsState() + val remotes by session.sources.remotes.collectAsState() val leftFrac by session.layout.leftFraction.collectAsState() val rightFrac by session.layout.rightFraction.collectAsState() - val albums = remember(tracks) { tracks.albums() } + var prefRev by remember { mutableIntStateOf(0) } + val albums = remember(tracks, prefRev) { tracks.albums(session.sourcePrefs.snapshot()) } var stack by remember { mutableStateOf(listOf(Route.Home)) } var forward by remember { mutableStateOf(listOf()) } var query by remember { mutableStateOf("") } @@ -113,6 +119,7 @@ fun YuriDesktopApp(session: DesktopSession) { var editSong by remember { mutableStateOf(null) } var editAlbum by remember { mutableStateOf(null) } var addToPlaylist by remember { mutableStateOf?>(null) } + var sourcesFor by remember { mutableStateOf?>(null) } val route = stack.last() val nav = if (route is Route.Search) DesktopNav.Search else DesktopNav.Home val scope = rememberCoroutineScope() @@ -163,10 +170,19 @@ fun YuriDesktopApp(session: DesktopSession) { addToPlaylist = listOf(track) } item("Add to queue") { session.player.enqueue(track) } + item("Sources") { + val group = tracks.filter { t -> + t.id == track.id || t.catalogKey() == track.catalogKey() || + albums.any { a -> a.tracks.any { row -> track.id in row.sourceIds && t.id in row.sourceIds } } + }.ifEmpty { listOf(track) } + sourcesFor = group + } submenu("Go to") { if (!onAlbumPage) { item("Album") { - albums.firstOrNull { a -> a.tracks.any { it.id == track.id } }?.let(::openAlbum) + albums.firstOrNull { a -> + a.tracks.any { track.id in it.sourceIds || it.id == track.id } + }?.let(::openAlbum) } } item("Artist") { openArtist(track.displayArtist) } @@ -191,7 +207,10 @@ fun YuriDesktopApp(session: DesktopSession) { } else -> { val album = albums.firstOrNull { it.id == item.id } - album?.tracks?.mapNotNull { row -> tracks.firstOrNull { it.id == row.id } }.orEmpty() + album?.tracks?.mapNotNull { row -> + val group = tracks.filter { it.id in row.sourceIds } + pickPreferred(group, session.sourcePrefs.get(albumPageIdentity(group.firstOrNull() ?: return@mapNotNull null))) + }.orEmpty() .ifEmpty { tracks.filter { it.id == item.id }.takeIf { it.isNotEmpty() } ?: emptyList() } } } @@ -482,13 +501,17 @@ fun YuriDesktopApp(session: DesktopSession) { ) is Route.Album -> { val live = albums.firstOrNull { it.id == r.album.id } ?: r.album - val albumTracks = live.tracks.mapNotNull { row -> - tracks.firstOrNull { it.id == row.id } + fun resolveRow(row: capital.yuri.yuriplayer.components.model.TrackRowModel): Track? { + val group = tracks.filter { it.id in row.sourceIds.ifEmpty { listOf(row.id) } } + if (group.isEmpty()) return tracks.firstOrNull { it.id == row.id } + val identity = albumPageIdentity(group.first()) + return pickPreferred(group, session.sourcePrefs.get(identity)) } + val albumTracks = live.tracks.mapNotNull(::resolveRow) AlbumPage( album = live.copy( tracks = live.tracks.map { - it.copy(highlighted = it.id == current?.id) + it.copy(highlighted = current?.id == it.id || current?.id in it.sourceIds) } ), playing = playing && albumTracks.any { it.id == current?.id }, @@ -519,6 +542,9 @@ fun YuriDesktopApp(session: DesktopSession) { onToggleTrackLike = session.collection::toggleLike, songMenu = { row -> tracks.firstOrNull { it.id == row.id }?.let { songMenu(it, onAlbumPage = true) }.orEmpty() + }, + onSources = { row -> + sourcesFor = tracks.filter { it.id in row.sourceIds.ifEmpty { listOf(row.id) } } } ) } @@ -701,6 +727,20 @@ fun YuriDesktopApp(session: DesktopSession) { onEnsureTracks = { session.ensureTracks(it) } ) } + sourcesFor?.let { group -> + val preferredId = group.firstOrNull()?.let { session.sourcePrefs.get(albumPageIdentity(it)) } + ?: group.minByOrNull { it.sourceRank() }?.id + SourcesPickerDialog( + title = group.first().displayTitle, + choices = sourceChoices(group, remotes, preferredId), + onDismiss = { sourcesFor = null }, + onPick = { picked -> + session.sourcePrefs.set(albumPageIdentity(picked), picked.id) + prefRev++ + sourcesFor = null + } + ) + } } } From 302d80e7b1a2638f5c4ad778fc7820e906d9b16a Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 22:58:57 +0000 Subject: [PATCH 48/69] Sync app volume with the slider and OS media keys VLC no longer resets to 100% on every load. The tray slider is the app volume, MPRIS Volume get/set matches it, and play/pause/ next/prev/volume keys work from the OS (playerctl, media keys). --- .../yuriplayer/core/os/OsMediaControls.kt | 12 ++- .../yuri/yuriplayer/desktop/DesktopSession.kt | 7 +- .../capital/yuri/yuriplayer/desktop/Main.kt | 43 +++++++- .../desktop/os/linux/MprisMediaControls.kt | 98 +++++++++++++++++-- .../desktop/os/mac/MacNowPlayingControls.kt | 2 +- .../desktop/os/win/WindowsSmtcControls.kt | 2 +- .../desktop/player/VlcjPlaybackEngine.kt | 11 ++- 7 files changed, 158 insertions(+), 17 deletions(-) diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/os/OsMediaControls.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/os/OsMediaControls.kt index ef33fd8a..fb69f5e5 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/os/OsMediaControls.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/os/OsMediaControls.kt @@ -8,7 +8,13 @@ import capital.yuri.yuriplayer.core.library.Track */ interface OsMediaControls { fun attach(callbacks: Callbacks) - fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long) + fun update( + track: Track?, + playing: Boolean, + positionMs: Long, + durationMs: Long, + volume: Float = 1f + ) fun release() interface Callbacks { @@ -19,6 +25,7 @@ interface OsMediaControls { fun onNext() fun onPrevious() fun onSeek(positionMs: Long) + fun onVolume(value: Float) {} fun onRaise() {} fun onQuit() {} } @@ -30,7 +37,8 @@ object NoOpMediaControls : OsMediaControls { track: Track?, playing: Boolean, positionMs: Long, - durationMs: Long + durationMs: Long, + volume: Float ) {} override fun release() {} } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index d04bf47d..5924f745 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -113,6 +113,8 @@ class DesktopSession { private val stoppedIds = ConcurrentHashMap.newKeySet() private val currentSourceId = AtomicReference(null) + var onRaise: (() -> Unit)? = null + init { engine.addListener(object : capital.yuri.yuriplayer.core.player.PlaybackEngine.Listener { override fun onError(message: String, recoverable: Boolean) { @@ -129,6 +131,8 @@ class DesktopSession { override fun onNext() = player.next() override fun onPrevious() = player.previous() override fun onSeek(positionMs: Long) = player.seekTo(positionMs) + override fun onVolume(value: Float) = player.setVolume(value) + override fun onRaise() { onRaise?.invoke() } override fun onQuit() = release() }) ticker = scope.launch { @@ -139,7 +143,8 @@ class DesktopSession { track = player.current.value, playing = player.isPlaying.value, positionMs = _positionMs.value, - durationMs = _durationMs.value + durationMs = _durationMs.value, + volume = player.volume.value ) delay(250) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt index 45c8003b..34e4ed14 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt @@ -5,6 +5,11 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.input.key.type import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application @@ -44,9 +49,45 @@ fun main() { exitApplication() }, title = "Yuri Player", - state = state + state = state, + onPreviewKeyEvent = { event -> + if (event.type != KeyEventType.KeyDown) return@Window false + when (event.key) { + Key.MediaPlayPause, Key.MediaPlay -> { + session.player.togglePlay(); true + } + Key.MediaPause -> { + session.player.pause(); true + } + Key.MediaStop -> { + session.player.stop(); true + } + Key.MediaNext, Key.MediaSkipForward -> { + session.player.next(); true + } + Key.MediaPrevious, Key.MediaSkipBackward -> { + session.player.previous(); true + } + Key.VolumeUp -> { + session.player.setVolume(session.player.volume.value + 0.05f); true + } + Key.VolumeDown -> { + session.player.setVolume(session.player.volume.value - 0.05f); true + } + Key.VolumeMute -> { + val v = session.player.volume.value + session.player.setVolume(if (v > 0f) 0f else 1f); true + } + else -> false + } + } ) { window.background = java.awt.Color(0x12, 0x10, 0x18) + session.onRaise = { + window.isMinimized = false + window.toFront() + window.requestFocus() + } DisposableEffect(Unit) { onDispose { session.release() } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt index 489d7d75..e8ddbd6b 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt @@ -7,6 +7,7 @@ import org.freedesktop.dbus.annotations.DBusInterfaceName import org.freedesktop.dbus.connections.impl.DBusConnection import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder import org.freedesktop.dbus.interfaces.DBusInterface +import org.freedesktop.dbus.interfaces.Introspectable import org.freedesktop.dbus.interfaces.Properties import org.freedesktop.dbus.types.Variant import java.net.URLEncoder @@ -37,11 +38,14 @@ class MprisMediaControls : OsMediaControls { } } - override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long) { - val next = NowPlaying(track, playing, positionMs, durationMs) + override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { + val next = NowPlaying(track, playing, positionMs, durationMs, volume.coerceIn(0f, 1f)) val prev = state.getAndSet(next) val conn = connection ?: return - if (prev.playing != next.playing || prev.track?.id != next.track?.id) { + if (prev.playing != next.playing || + prev.track?.id != next.track?.id || + kotlin.math.abs(prev.volume - next.volume) > 0.01f + ) { runCatching { conn.sendMessage( Properties.PropertiesChanged( @@ -75,7 +79,8 @@ internal data class NowPlaying( val track: Track? = null, val playing: Boolean = false, val positionMs: Long = 0L, - val durationMs: Long = 0L + val durationMs: Long = 0L, + val volume: Float = 1f ) @DBusInterfaceName("org.mpris.MediaPlayer2") @@ -100,7 +105,7 @@ interface MprisPlayer : DBusInterface { internal class MprisObject( private val callbacks: OsMediaControls.Callbacks, private val state: AtomicReference -) : MediaPlayer2, MprisPlayer, Properties { +) : MediaPlayer2, MprisPlayer, Properties, Introspectable { override fun getObjectPath(): String = MprisMediaControls.OBJECT_PATH @@ -124,17 +129,26 @@ internal class MprisObject( override fun OpenUri(uri: String) {} override fun Get(interface_name: String, property_name: String): A { + val variant = propertiesFor(interface_name, state.get())[property_name] @Suppress("UNCHECKED_CAST") - return (propertiesFor(interface_name, state.get())[property_name] ?: Variant("")) as A + return (variant?.value ?: "") as A } override fun GetAll(interface_name: String): Map> = propertiesFor(interface_name, state.get()) override fun Set(interface_name: String, property_name: String, value: A) { - if (property_name == "Volume") return + if (property_name != "Volume") return + val raw = when (value) { + is Variant<*> -> value.value + else -> value + } + val v = (raw as? Number)?.toDouble() ?: return + callbacks.onVolume(v.coerceIn(0.0, 1.0).toFloat()) } + override fun Introspect(): String = INTROSPECT_XML + fun properties(now: NowPlaying): Map> = propertiesFor(MprisMediaControls.PLAYER_IFACE, now) @@ -167,7 +181,7 @@ internal class MprisObject( "Rate" to Variant(1.0), "Shuffle" to Variant(false), "Metadata" to Variant(metadata(now)), - "Volume" to Variant(1.0), + "Volume" to Variant(now.volume.toDouble()), "Position" to Variant(now.positionMs * 1000), "MinimumRate" to Variant(1.0), "MaximumRate" to Variant(1.0), @@ -200,4 +214,72 @@ internal class MprisObject( track.artworkUri?.let { map["mpris:artUrl"] = Variant(it) } return map } + + companion object { + private val INTROSPECT_XML = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """.trimIndent() + } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt index 7c81d504..9cd2524c 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt @@ -24,7 +24,7 @@ class MacNowPlayingControls : OsMediaControls { .onFailure { System.err.println("macOS media keys: ${it.message}") } } - override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long) { + override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { MacNowPlayingStore.track = track MacNowPlayingStore.playing = playing MacNowPlayingStore.positionMs = positionMs diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt index 9b81b247..023ffc0a 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt @@ -29,7 +29,7 @@ class WindowsSmtcControls : OsMediaControls { } } - override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long) { + override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { WindowsNowPlaying.track = track WindowsNowPlaying.playing = playing WindowsNowPlaying.positionMs = positionMs diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index 18281cbc..fb03f79c 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -13,6 +13,7 @@ import java.io.File import java.net.URI import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicReference @@ -34,6 +35,7 @@ class VlcjPlaybackEngine : PlaybackEngine { private val _currentUri = MutableStateFlow(null) private val positionMs = AtomicLong(0) private val durationMs = AtomicLong(0) + private val volumePercent = AtomicInteger(100) private val preparedNext = AtomicReference(null) val nativeError: String? @@ -99,7 +101,7 @@ class VlcjPlaybackEngine : PlaybackEngine { } }) onVlc { - player?.audio()?.setVolume(100) + player?.audio()?.setVolume(volumePercent.get()) player?.audio()?.setMute(false) } } @@ -118,7 +120,7 @@ class VlcjPlaybackEngine : PlaybackEngine { listeners.forEach { it.onError("Could not open ${current.title}", recoverable = true) } return@onVlc } - p.audio().setVolume(100) + p.audio().setVolume(volumePercent.get()) p.audio().setMute(false) if (startPositionMs > 0) p.controls().setTime(startPositionMs) p.controls().setPause(true) @@ -155,9 +157,12 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun getDurationMs(): Long = durationMs.get() override fun setVolume(percent: Int) { - onVlc { player?.audio()?.setVolume(percent.coerceIn(0, 100)) } + volumePercent.set(percent.coerceIn(0, 100)) + onVlc { player?.audio()?.setVolume(volumePercent.get()) } } + override fun getVolume(): Int = volumePercent.get() + override fun setNext(item: PlaybackMedia?) { preparedNext.set(item) } From e55ce8dfe2fcf267154b588d540fbf72702e362b Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 23:04:46 +0000 Subject: [PATCH 49/69] Fix seek restore, playlist members, and sources freeze Save and restore the playhead (VLC ignored setTime until play). Playlist pages include snapshot tracks even when stored ids drifted. Opening sources no longer scans the whole library on the UI thread. --- .../core/library/AlbumCollapseTest.kt | 38 +++++++++++++++++++ .../core/player/PlayerSessionTest.kt | 2 + .../desktop/DesktopPlaylistStore.kt | 22 ++++++++--- .../yuri/yuriplayer/desktop/DesktopSession.kt | 10 +++-- .../desktop/player/VlcjPlaybackEngine.kt | 28 ++++++++++++-- .../desktop/ui/SourcesPickerDialog.kt | 3 +- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 29 ++++++++++---- 7 files changed, 112 insertions(+), 20 deletions(-) diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/AlbumCollapseTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/AlbumCollapseTest.kt index da154c18..52c599d3 100644 --- a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/AlbumCollapseTest.kt +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/AlbumCollapseTest.kt @@ -39,6 +39,44 @@ class AlbumCollapseTest { assertEquals("subsonic-1", collapsed.first().preferred.id) } + @Test + fun playlistKeepsSnapshotWhenKeyMissing() { + val snap = Track( + id = "subsonic:old", + uri = "https://navi/stream/old", + title = "BIRDBRAIN", + artist = "Jamie Paige feat. OK Glass", + album = "BIRDBRAIN", + trackNumber = 1 + ) + val other = Track( + id = "subsonic:new", + uri = "https://navi/stream/new", + title = "Sahara", + artist = "Hoshimachi Suisei", + album = "Still Still Stellar", + trackNumber = 1 + ) + val byKey = HashMap() + byKey[other.id] = other + byKey.putIfAbsent(snap.id, snap) + byKey.putIfAbsent(snap.catalogKey(), snap) + val trackIds = listOf("k:missing", other.id) + val snapshots = listOf(snap, other) + val seen = HashSet() + val out = ArrayList() + fun add(t: Track) { + if (t.playlistKeys().any { it in seen }) return + seen += t.playlistKeys() + out += t + } + for (key in trackIds) byKey[key]?.let(::add) + for (s in snapshots) add(byKey[s.id] ?: byKey[s.catalogKey()] ?: s) + assertEquals(2, out.size) + assertTrue(out.any { it.displayTitle == "BIRDBRAIN" }) + assertTrue(out.any { it.displayTitle == "Sahara" }) + } + private val titles = listOf( "Overcompensate", "Next Semester", "Backslide", "Midwest Indigo", "Routines In The Night", "Vignette", "The Craving (Jenna's Version)", diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt index 7d209271..13facc9c 100644 --- a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt @@ -86,6 +86,8 @@ class PlayerSessionTest { assertEquals(listOf("1", "2", "3", "4"), other.queue.value.map { it.id }) assertEquals(RepeatMode.ALL, other.repeat.value) assertFalse(other.isPlaying.value) + other.seekTo(45_000L) + assertEquals(45_000L, other.snapshot().positionMs) } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt index f740bcb3..10e9ff90 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -254,17 +254,27 @@ data class DesktopPlaylist( } fun tracks(library: List): List { - if (trackIds.isEmpty()) return emptyList() val byKey = HashMap() - for (t in library) { + fun index(t: Track) { byKey.putIfAbsent(t.id, t) byKey.putIfAbsent(t.catalogKey(), t) } - for (t in snapshots) { - byKey.putIfAbsent(t.id, t) - byKey.putIfAbsent(t.catalogKey(), t) + for (t in library) index(t) + for (t in snapshots) index(t) + val seen = HashSet() + val out = ArrayList() + fun add(t: Track) { + if (t.playlistKeys().any { it in seen }) return + seen += t.playlistKeys() + out += t + } + for (key in trackIds) { + byKey[key]?.let(::add) + } + for (snap in snapshots) { + add(byKey[snap.id] ?: byKey[snap.catalogKey()] ?: snap) } - return trackIds.mapNotNull { byKey[it] } + return out } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 5924f745..b9cad853 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -218,12 +218,16 @@ class DesktopSession { private fun startPersist() { persistJob = scope.launch { while (isActive) { - delay(5_000) - playbackStore.save(player.snapshot()) + delay(2_000) + persistPlayback() } } } + fun persistPlayback() { + playbackStore.save(player.snapshot()) + } + fun playTrack(track: Track) { val list = _tracks.value val i = list.indexOfFirst { it.id == track.id }.coerceAtLeast(0) @@ -829,7 +833,7 @@ class DesktopSession { fun release() { persistJob?.cancel() ticker?.cancel() - playbackStore.save(player.snapshot()) + persistPlayback() media.release() player.release() runCatching { http.close() } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index fb03f79c..d8bb1273 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -36,6 +36,7 @@ class VlcjPlaybackEngine : PlaybackEngine { private val positionMs = AtomicLong(0) private val durationMs = AtomicLong(0) private val volumePercent = AtomicInteger(100) + private val pendingStartMs = AtomicLong(-1L) private val preparedNext = AtomicReference(null) val nativeError: String? @@ -68,6 +69,13 @@ class VlcjPlaybackEngine : PlaybackEngine { } player?.events()?.addMediaPlayerEventListener(object : MediaPlayerEventAdapter() { override fun playing(mediaPlayer: MediaPlayer) { + val pending = pendingStartMs.getAndSet(-1L) + if (pending > 0L) { + mediaPlayer.controls().setTime(pending) + positionMs.set(pending) + } else { + positionMs.set(mediaPlayer.status().time().coerceAtLeast(0L)) + } _isPlaying.value = true listeners.forEach { it.onIsPlayingChanged(true) } } @@ -108,6 +116,8 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun load(current: PlaybackMedia, successor: PlaybackMedia?, startPositionMs: Long) { preparedNext.set(successor) + pendingStartMs.set(if (startPositionMs > 0) startPositionMs else -1L) + positionMs.set(startPositionMs.coerceAtLeast(0L)) onVlc { val p = player ?: run { listeners.forEach { it.onError(nativeError ?: "No audio engine", false) } @@ -115,7 +125,12 @@ class VlcjPlaybackEngine : PlaybackEngine { } val mrl = toMrl(current.uri) System.err.println("Vlcj load $mrl") - val ok = p.media().prepare(mrl, *mediaOptions(current)) + val extra = if (startPositionMs > 0) { + arrayOf(":start-time=${startPositionMs / 1000.0}") + } else { + emptyArray() + } + val ok = p.media().prepare(mrl, *(mediaOptions(current) + extra)) if (!ok) { listeners.forEach { it.onError("Could not open ${current.title}", recoverable = true) } return@onVlc @@ -149,10 +164,17 @@ class VlcjPlaybackEngine : PlaybackEngine { } override fun seekTo(positionMs: Long) { - onVlc { player?.controls()?.setTime(positionMs) } + val ms = positionMs.coerceAtLeast(0L) + this.positionMs.set(ms) + pendingStartMs.set(-1L) + onVlc { player?.controls()?.setTime(ms) } } - override fun getPositionMs(): Long = positionMs.get() + override fun getPositionMs(): Long { + val pending = pendingStartMs.get() + if (pending > 0L) return pending + return positionMs.get() + } override fun getDurationMs(): Long = durationMs.get() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt index a03545df..5bcfdcc9 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -81,7 +82,7 @@ fun SourcesPickerDialog( onPick: (Track) -> Unit ) { InWindowPanel(onDismiss = onDismiss, modifier = Modifier.width(420.dp).height(480.dp)) { - Column(Modifier.padding(20.dp)) { + Column(Modifier.fillMaxSize().padding(20.dp)) { Text("Sources", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) Text( title, diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index b74498fa..6d708f19 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -171,11 +171,20 @@ fun YuriDesktopApp(session: DesktopSession) { } item("Add to queue") { session.player.enqueue(track) } item("Sources") { - val group = tracks.filter { t -> - t.id == track.id || t.catalogKey() == track.catalogKey() || - albums.any { a -> a.tracks.any { row -> track.id in row.sourceIds && t.id in row.sourceIds } } - }.ifEmpty { listOf(track) } - sourcesFor = group + val ids = HashSet() + ids += track.id + ids += track.catalogKey() + val row = albums.asSequence() + .mapNotNull { a -> a.tracks.firstOrNull { track.id in it.sourceIds || it.id == track.id } } + .firstOrNull() + if (row != null) ids += row.sourceIds + val want = ids + sourcesFor = tracks.asSequence() + .filter { it.id in want || it.catalogKey() in want } + .distinctBy { it.id } + .take(16) + .toList() + .ifEmpty { listOf(track) } } submenu("Go to") { if (!onAlbumPage) { @@ -406,7 +415,10 @@ fun YuriDesktopApp(session: DesktopSession) { onPrev = session.player::previous, onToggle = session.player::togglePlay, onNext = session.player::next, - onSeek = session.player::seekTo, + onSeek = { ms -> + session.player.seekTo(ms) + session.persistPlayback() + }, accent = playerColors?.accent ?: MaterialTheme.colorScheme.primary, shuffle = shuffle, repeat = repeat, @@ -544,7 +556,10 @@ fun YuriDesktopApp(session: DesktopSession) { tracks.firstOrNull { it.id == row.id }?.let { songMenu(it, onAlbumPage = true) }.orEmpty() }, onSources = { row -> - sourcesFor = tracks.filter { it.id in row.sourceIds.ifEmpty { listOf(row.id) } } + val ids = row.sourceIds.ifEmpty { listOf(row.id) }.toHashSet() + sourcesFor = tracks.filter { it.id in ids } + .ifEmpty { tracks.filter { it.catalogKey() == row.id } } + .ifEmpty { listOfNotNull(tracks.firstOrNull { it.id == row.id }) } } ) } From 4db1b7c8a5059ff607f1dd7d61c2803304e07aec Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 23:05:17 +0000 Subject: [PATCH 50/69] Drop playlist up/down/delete buttons Reorder is drag, remove is the context menu. --- .../yuri/yuriplayer/desktop/ui/PlaylistPage.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt index 99da6611..57860e4a 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.PlayArrow @@ -184,17 +183,6 @@ fun PlaylistPage( onToggleLike = { onToggleLike(track.id) }, contextItems = songMenu(track) ) - Row(Modifier.fillMaxWidth().padding(horizontal = 8.dp), horizontalArrangement = Arrangement.End) { - TextButton(onClick = { - if (index > 0) store.moveTrack(playlist.id, index, index - 1) - }, enabled = index > 0) { Text("Up") } - TextButton(onClick = { - if (index < tracks.lastIndex) store.moveTrack(playlist.id, index, index + 1) - }, enabled = index < tracks.lastIndex) { Text("Down") } - IconButton(onClick = { store.removeTracks(playlist.id, listOf(track)) }) { - Icon(Icons.Default.Delete, contentDescription = "Remove") - } - } } } } From 56ee3f5fd25a61ddc1ea4a1880738073baf2cf3c Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Fri, 21 Aug 2026 23:15:48 +0000 Subject: [PATCH 51/69] Keep Navidrome playlist tracks in the index Search vs scan rows now share a loose title+album key, playlist snapshots are re-seeded after every source replace, and Done always upserts the live Track. Grep stderr for YuriPlayer.Playlist or YuriPlayer.Index. --- .../yuri/yuriplayer/core/library/Track.kt | 26 ++++++- .../core/library/TrackCatalogKeyTest.kt | 26 +++++++ .../yuriplayer/desktop/DesktopIndexStore.kt | 3 + .../desktop/DesktopPlaylistStore.kt | 67 +++++++++++++------ .../yuri/yuriplayer/desktop/DesktopSession.kt | 25 ++++++- .../yuri/yuriplayer/desktop/PlaylistLog.kt | 31 +++++++++ .../desktop/ui/AddToPlaylistDialog.kt | 5 +- 7 files changed, 156 insertions(+), 27 deletions(-) create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt index 9c6e4460..c6f3908b 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/Track.kt @@ -50,5 +50,29 @@ data class Track( return "k:$t|$a|$al|$n" } - fun playlistKeys(): Set = setOf(id, catalogKey()) + /** + * Artist-agnostic key so Navidrome search (`feat. X` on artist) matches + * a scanned row (album artist only). + */ + fun looseKey(): String { + val t = normalizeTitle(title ?: displayTitle) + val al = foldSearch(album ?: "") + val n = trackNumber ?: 0 + return "p:$t|$al|$n" + } + + fun rawSourceId(): String? = when { + id.startsWith("subsonic:") -> id.removePrefix("subsonic:") + id.startsWith("jellyfin:") -> id.removePrefix("jellyfin:") + else -> null + } + + fun indexKeys(): Set = buildSet { + add(id) + add(catalogKey()) + add(looseKey()) + rawSourceId()?.let { add(it) } + } + + fun playlistKeys(): Set = indexKeys() } diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackCatalogKeyTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackCatalogKeyTest.kt index 84f676cf..c1eae8a2 100644 --- a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackCatalogKeyTest.kt +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/TrackCatalogKeyTest.kt @@ -29,4 +29,30 @@ class TrackCatalogKeyTest { assertEquals(local.catalogKey(), remote.catalogKey()) assertTrue(local.catalogKey() in remote.playlistKeys()) } + + @Test + fun navidromeSearchAndScanShareLooseKey() { + val search = Track( + id = "subsonic:abc", + uri = "https://navi/stream/abc", + title = "BIRDBRAIN", + artist = "Jamie Paige feat. OK Glass", + album = "BIRDBRAIN", + trackNumber = 1, + sourceId = "navi-1" + ) + val scanned = Track( + id = "subsonic:abc", + uri = "https://navi/stream/abc", + title = "BIRDBRAIN", + artist = "Jamie Paige", + albumArtist = "Jamie Paige", + album = "BIRDBRAIN", + trackNumber = 1, + sourceId = "navi-1" + ) + assertEquals(search.looseKey(), scanned.looseKey()) + assertTrue(search.looseKey() in scanned.indexKeys()) + assertTrue("abc" in scanned.indexKeys()) + } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt index 5fbaa8f4..7eaeffbc 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt @@ -32,6 +32,9 @@ class DesktopIndexStore(cacheDir: String) { tmp.copyTo(tracksFile, overwrite = true) tmp.delete() } + PlaylistLog.index("saved ${tracks.size} tracks → ${tracksFile.absolutePath}") + }.onFailure { + PlaylistLog.index("save FAILED: ${it.message}") } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt index 10e9ff90..6ffb65e3 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -1,6 +1,8 @@ package capital.yuri.yuriplayer.desktop import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.foldSearch +import capital.yuri.yuriplayer.core.library.normalizeTitle import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -70,13 +72,14 @@ class DesktopPlaylistStore(configDir: String) { val seen = existing.toHashSet() val snaps = pl.snapshots.toMutableList() for (t in tracks) { - val already = t.playlistKeys().any { it in seen } + PlaylistLog.add(pl.name, t) + val already = t.indexKeys().any { it in seen } if (!already) { existing += t.id - seen += t.playlistKeys() + seen += t.indexKeys() } val i = snaps.indexOfFirst { s -> - s.id == t.id || s.catalogKey() == t.catalogKey() || t.id in s.playlistKeys() + s.indexKeys().intersect(t.indexKeys()).isNotEmpty() } if (i < 0) snaps += t else snaps[i] = t } @@ -90,8 +93,10 @@ class DesktopPlaylistStore(configDir: String) { val next = _playlists.value.map { pl -> var snaps = pl.snapshots for (t in tracks) { - if (pl.trackIds.none { it in t.playlistKeys() }) continue - val i = snaps.indexOfFirst { s -> s.id == t.id || s.catalogKey() == t.catalogKey() } + val hit = pl.trackIds.any { it in t.indexKeys() } || + pl.snapshots.any { s -> s.indexKeys().intersect(t.indexKeys()).isNotEmpty() } + if (!hit) continue + val i = snaps.indexOfFirst { s -> s.indexKeys().intersect(t.indexKeys()).isNotEmpty() } snaps = if (i < 0) snaps + t else snaps.toMutableList().also { it[i] = t } changed = true } @@ -104,11 +109,14 @@ class DesktopPlaylistStore(configDir: String) { } fun playlistsContaining(trackId: String): Set = - _playlists.value.filter { trackId in it.trackIds }.map { it.id }.toSet() + playlistsContaining(Track(id = trackId, uri = "")) fun playlistsContaining(track: Track): Set { - val keys = track.playlistKeys() - return _playlists.value.filter { pl -> pl.trackIds.any { it in keys } }.map { it.id }.toSet() + val keys = track.indexKeys() + return _playlists.value.filter { pl -> + pl.trackIds.any { it in keys } || + pl.snapshots.any { s -> s.indexKeys().any { it in keys } } + }.map { it.id }.toSet() } fun removeTracks(id: String, tracks: List) { @@ -254,28 +262,47 @@ data class DesktopPlaylist( } fun tracks(library: List): List { - val byKey = HashMap() - fun index(t: Track) { - byKey.putIfAbsent(t.id, t) - byKey.putIfAbsent(t.catalogKey(), t) + val index = HashMap() + fun put(t: Track) { + for (k in t.indexKeys()) index.putIfAbsent(k, t) } - for (t in library) index(t) - for (t in snapshots) index(t) + for (t in library) put(t) + for (t in snapshots) put(t) val seen = HashSet() val out = ArrayList() - fun add(t: Track) { - if (t.playlistKeys().any { it in seen }) return - seen += t.playlistKeys() + fun take(t: Track) { + if (t.indexKeys().any { it in seen }) return + seen += t.indexKeys() out += t } + for (snap in snapshots) { + val live = snap.indexKeys().firstNotNullOfOrNull { index[it] } + take(live ?: snap) + } + val missed = ArrayList() for (key in trackIds) { - byKey[key]?.let(::add) + val hit = index[key] ?: matchLoose(key, library) + if (hit != null) take(hit) else missed += key } - for (snap in snapshots) { - add(byKey[snap.id] ?: byKey[snap.catalogKey()] ?: snap) + if (out.size < trackIds.size.coerceAtLeast(snapshots.size) || missed.isNotEmpty()) { + PlaylistLog.resolve(name, trackIds, snapshots, out, missed) } return out } + + private fun matchLoose(key: String, library: List): Track? { + if (!key.startsWith("k:") && !key.startsWith("p:")) return null + val parts = key.drop(2).split('|') + val title = parts.getOrNull(0).orEmpty() + if (title.isEmpty()) return null + val album = if (key.startsWith("k:")) parts.getOrNull(2).orEmpty() else parts.getOrNull(1).orEmpty() + return library.firstOrNull { t -> + val nt = normalizeTitle(t.title ?: t.displayTitle) + val ft = foldSearch(t.title ?: t.displayTitle) + (nt == title || ft == title) && + (album.isEmpty() || foldSearch(t.album ?: "") == album) + } + } } @Serializable diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index b9cad853..342daea9 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -370,26 +370,45 @@ class DesktopSession { private fun mergeTracks(incoming: List) { if (incoming.isEmpty()) return + val before = _tracks.value.size _tracks.value = (_tracks.value + incoming).distinctBy { it.id } + PlaylistLog.index("merge +${incoming.size} $before→${_tracks.value.size}") } fun ensureTracks(incoming: List) { if (incoming.isEmpty()) return + PlaylistLog.index("ensure ${incoming.map { "${it.displayTitle}/${it.id}" }}") mergeTracks(incoming) persistIndex() playlists.remember(incoming) } + private fun playlistRefs(): Set = + playlists.playlists.value.flatMap { pl -> + pl.trackIds + pl.snapshots.flatMap { it.indexKeys() } + }.toHashSet() + private fun replaceSourceTracks(sourceId: String, incoming: List) { - val referenced = playlists.playlists.value.flatMap { it.trackIds }.toHashSet() + val referenced = playlistRefs() val existing = _tracks.value + val incomingIds = incoming.map { it.id }.toHashSet() val orphans = existing.filter { t -> (t.sourceId ?: LOCAL_SCAN_ID) == sourceId && - incoming.none { n -> n.id == t.id } && - (t.id in referenced || t.catalogKey() in referenced) + t.id !in incomingIds && + t.indexKeys().any { it in referenced } } val rest = existing.filterNot { (it.sourceId ?: LOCAL_SCAN_ID) == sourceId } + val dropped = existing.count { + (it.sourceId ?: LOCAL_SCAN_ID) == sourceId && + it.id !in incomingIds && + it.indexKeys().none { k -> k in referenced } + } + PlaylistLog.index( + "replace source=$sourceId incoming=${incoming.size} orphans=${orphans.size} dropped=$dropped refs=${referenced.size}" + ) _tracks.value = (rest + incoming + orphans).distinctBy { it.id } + val snaps = playlists.playlists.value.flatMap { it.snapshots } + if (snaps.isNotEmpty()) mergeTracks(snaps) } private fun persistIndex() { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt new file mode 100644 index 00000000..44d2e189 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt @@ -0,0 +1,31 @@ +package capital.yuri.yuriplayer.desktop + +import capital.yuri.yuriplayer.core.library.Track + +internal object PlaylistLog { + fun add(playlist: String, track: Track) { + log( + "add '$playlist' title='${track.displayTitle}' id=${track.id} " + + "source=${track.sourceId} catalog=${track.catalogKey()} loose=${track.looseKey()}" + ) + } + + fun resolve( + playlist: String, + trackIds: List, + snapshots: List, + out: List, + missed: List + ) { + log( + "resolve '$playlist' ids=${trackIds.size} snaps=${snapshots.size} " + + "out=${out.size} missed=$missed titles=${out.map { it.displayTitle }}" + ) + } + + fun index(msg: String) = log(msg, tag = "YuriPlayer.Index") + + private fun log(msg: String, tag: String = "YuriPlayer.Playlist") { + System.err.println("$tag $msg") + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt index 0f6a1a6f..dd91164d 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt @@ -180,10 +180,9 @@ fun AddToPlaylistDialog( } Button( onClick = { - val toAdd = selected - initiallyIn val toRemove = initiallyIn - selected - if (toAdd.isNotEmpty()) onEnsureTracks(tracks) - toAdd.forEach { store.addTracks(it, tracks) } + onEnsureTracks(tracks) + selected.forEach { store.addTracks(it, tracks) } toRemove.forEach { store.removeTracks(it, tracks) } onDismiss() }, From a982f06be7cb83258e4c5cb08d17d38c44b3a979 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Sat, 22 Aug 2026 02:02:20 +0000 Subject: [PATCH 52/69] Hot/cold queue, add-order playlists, no auto-pin Add to queue now lands in the hot lane (plays next, before the album rest) using the same QueuePanel sections as mobile. VLC actually starts the prepared next track on end. Playlist members render in add order from snapshots so Navidrome rows stay visible. Hearts save to My Stuff without pinning home. --- .../components/player/NowPlayingSidebar.kt | 28 +- .../components/player/QueuePanel.kt | 186 +++++--- .../yuriplayer/core/player/PlayerSession.kt | 414 +++++++++++++----- .../core/player/PlayerSessionTest.kt | 25 +- .../yuriplayer/desktop/DesktopCollection.kt | 25 ++ .../desktop/DesktopPlaylistStore.kt | 13 +- .../desktop/player/VlcjPlaybackEngine.kt | 1 + .../yuriplayer/desktop/ui/PlaylistPage.kt | 2 +- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 83 ++-- 9 files changed, 527 insertions(+), 250 deletions(-) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index e6685853..7f529b33 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -23,13 +23,17 @@ import capital.yuri.yuriplayer.components.model.TrackRowModel @Composable fun NowPlayingSidebar( track: CoverRef?, - queue: List, + hot: List, + cold: List, + coldLabel: String = "Up next", history: List, - onQueueTrack: (TrackRowModel) -> Unit, + onPlayHot: (Int) -> Unit, + onPlayCold: (Int) -> Unit, onHistoryTrack: (TrackRowModel) -> Unit, - onClearQueue: () -> Unit = {}, + onClearHot: () -> Unit = {}, onClearHistory: () -> Unit = {}, - onMoveUpcoming: ((from: Int, to: Int) -> Unit)? = null, + onMoveHot: ((from: Int, to: Int) -> Unit)? = null, + onMoveCold: ((from: Int, to: Int) -> Unit)? = null, likedIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, songMenu: (TrackRowModel) -> List = { emptyList() }, @@ -51,15 +55,17 @@ fun NowPlayingSidebar( Spacer(Modifier.height(8.dp)) QueuePanel( nowPlaying = track, - upcoming = queue.dropWhile { it.id != track?.id }.drop(1), + hot = hot, + cold = cold, + coldLabel = coldLabel, history = history, - onPlay = { row -> - if (history.any { it.id == row.id } && queue.none { it.id == row.id }) onHistoryTrack(row) - else onQueueTrack(row) - }, - onClearQueue = onClearQueue, + onPlayHot = onPlayHot, + onPlayCold = onPlayCold, + onPlayHistory = onHistoryTrack, + onClearHot = onClearHot, onClearHistory = onClearHistory, - onMove = onMoveUpcoming, + onMoveHot = onMoveHot, + onMoveCold = onMoveCold, likedIds = likedIds, onToggleLike = onToggleTrackLike, songMenu = songMenu, diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt index 013a467e..95969cb4 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.CircleShape @@ -34,7 +35,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -56,18 +56,23 @@ import kotlin.math.roundToInt private enum class QueueTab { Queue, History } /** - * Shared queue chrome used by the mobile overlay and the desktop right rail. - * Long-press a row and drag to reorder when [onMove] is set. + * Shared queue chrome (mobile overlay + desktop rail). + * Hot = user-queued (plays next). Cold = album / playlist / radio rest. */ @Composable fun QueuePanel( nowPlaying: CoverRef?, - upcoming: List, + hot: List, + cold: List, + coldLabel: String = "Up next", history: List, - onPlay: (TrackRowModel) -> Unit, - onClearQueue: () -> Unit = {}, + onPlayHot: (Int) -> Unit, + onPlayCold: (Int) -> Unit, + onPlayHistory: (TrackRowModel) -> Unit = {}, + onClearHot: () -> Unit = {}, onClearHistory: () -> Unit = {}, - onMove: ((from: Int, to: Int) -> Unit)? = null, + onMoveHot: ((from: Int, to: Int) -> Unit)? = null, + onMoveCold: ((from: Int, to: Int) -> Unit)? = null, likedIds: Set = emptySet(), onToggleLike: (String) -> Unit = {}, songMenu: (TrackRowModel) -> List = { emptyList() }, @@ -103,8 +108,8 @@ fun QueuePanel( ) } } - if (tab == QueueTab.Queue && upcoming.isNotEmpty()) { - TextButton(onClick = onClearQueue) { Text("Clear") } + if (tab == QueueTab.Queue && hot.isNotEmpty()) { + TextButton(onClick = onClearHot) { Text("Clear") } } if (tab == QueueTab.History && history.isNotEmpty()) { TextButton(onClick = onClearHistory) { Text("Clear") } @@ -134,64 +139,56 @@ fun QueuePanel( onToggleArt = onToggleArt ) } - if (upcoming.isEmpty()) { - Text( - "Nothing up next.", - modifier = Modifier.padding(16.dp), - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) - ) - } else { - val density = LocalDensity.current - val rowPx = with(density) { 64.dp.toPx() } - Box(Modifier.weight(1f)) { - LazyColumn( - modifier = if (onMove == null) Modifier.fillMaxSize() - else Modifier - .fillMaxSize() - .pointerInput(upcoming, onMove) { - var from: Int? = null - var dy = 0f - detectDragGesturesAfterLongPress( - onDragStart = { offset -> - from = (offset.y / rowPx).toInt().coerceIn(0, upcoming.lastIndex) - dy = 0f - }, - onDrag = { change, drag -> - change.consume() - dy += drag.y - }, - onDragEnd = { - val start = from ?: return@detectDragGesturesAfterLongPress - val to = (start + (dy / rowPx).roundToInt()) - .coerceIn(0, upcoming.lastIndex) - if (start != to) onMove(start, to) - from = null - }, - onDragCancel = { from = null } - ) - } - ) { - itemsIndexed(upcoming, key = { _, row -> row.id }) { _, row -> - TrackRow( - track = row, - onClick = { onPlay(row) }, - showCover = true, - showAlbum = false, - liked = row.id in likedIds, - onToggleLike = { onToggleLike(row.id) }, - contextItems = songMenu(row) + Box(Modifier.weight(1f)) { + LazyColumn(Modifier.fillMaxSize()) { + if (hot.isNotEmpty()) { + item(key = "hdr-hot") { + SectionHeader("Queue · ${hot.size}") + } + queueRows( + rows = hot, + prefix = "hot", + onPlay = onPlayHot, + onMove = onMoveHot, + likedIds = likedIds, + onToggleLike = onToggleLike, + songMenu = songMenu + ) + } + item(key = "hdr-cold") { + SectionHeader("$coldLabel · ${cold.size}") + Text( + if (hot.isNotEmpty()) { + "Queued tracks play first. Drag to reorder." + } else { + "Play an album or list to fill what comes next." + }, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f), + modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp) + ) + } + if (cold.isEmpty()) { + item(key = "cold-empty") { + Text( + "Nothing else queued.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f), + modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp) ) } + } else { + queueRows( + rows = cold, + prefix = "cold", + onPlay = onPlayCold, + onMove = onMoveCold, + likedIds = likedIds, + onToggleLike = onToggleLike, + songMenu = songMenu + ) } } - if (onMove != null) { - Text( - "Long-press and drag to reorder", - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f), - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) - ) - } } } QueueTab.History -> { @@ -203,10 +200,10 @@ fun QueuePanel( ) } else { LazyColumn(Modifier.weight(1f)) { - itemsIndexed(history, key = { _, row -> row.id }) { _, row -> + itemsIndexed(history, key = { i, row -> "hist-${row.id}-$i" }) { _, row -> TrackRow( track = row, - onClick = { onPlay(row) }, + onClick = { onPlayHistory(row) }, showCover = true, showAlbum = true, liked = row.id in likedIds, @@ -221,6 +218,63 @@ fun QueuePanel( } } +@Composable +private fun SectionHeader(label: String) { + Text( + label, + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp) + ) +} + +private fun LazyListScope.queueRows( + rows: List, + prefix: String, + onPlay: (Int) -> Unit, + onMove: ((Int, Int) -> Unit)?, + likedIds: Set, + onToggleLike: (String) -> Unit, + songMenu: (TrackRowModel) -> List +) { + itemsIndexed(rows, key = { i, row -> "$prefix-${row.id}-$i" }) { index, row -> + val density = LocalDensity.current + val rowPx = with(density) { 64.dp.toPx() } + TrackRow( + track = row, + onClick = { onPlay(index) }, + showCover = true, + showAlbum = false, + liked = row.id in likedIds, + onToggleLike = { onToggleLike(row.id) }, + contextItems = songMenu(row), + modifier = if (onMove == null) Modifier + else Modifier.pointerInput(rows, onMove) { + var from: Int? = null + var dy = 0f + detectDragGesturesAfterLongPress( + onDragStart = { + from = index + dy = 0f + }, + onDrag = { change, drag -> + change.consume() + dy += drag.y + }, + onDragEnd = { + val start = from ?: return@detectDragGesturesAfterLongPress + val to = (start + (dy / rowPx).roundToInt()) + .coerceIn(0, rows.lastIndex) + if (start != to) onMove(start, to) + from = null + }, + onDragCancel = { from = null } + ) + } + ) + } +} + @Composable private fun NowPlayingQueueCard( track: CoverRef, @@ -291,4 +345,4 @@ private fun ExpandableCover( } } } -} \ No newline at end of file +} diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt index ad28ee67..c817c776 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -5,20 +5,31 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +enum class QueueLane { HOT, COLD } + /** * Host-side queue + now-playing. Engines only produce sound. - * Shuffle / repeat live here so every backend behaves the same. - * Previous within [RESTART_WINDOW_MS] seeks to 0 (Spotify 3s rule). + * + * Hot = user "add to queue" (plays next, before the rest of the album). + * Cold = album / playlist / search context currently playing through. */ class PlayerSession( private val engine: PlaybackEngine ) { + private val _hot = MutableStateFlow>(emptyList()) + val hotQueue: StateFlow> = _hot.asStateFlow() + + private val _cold = MutableStateFlow>(emptyList()) + val coldQueue: StateFlow> = _cold.asStateFlow() + + private var coldOriginal: List = emptyList() + private var lane: QueueLane = QueueLane.COLD + private var indexInLane: Int = -1 + + /** Flattened [current] + remaining hot + remaining cold. Snapshot compatibility. */ private val _queue = MutableStateFlow>(emptyList()) val queue: StateFlow> = _queue.asStateFlow() - /** Unshuffled insertion order. Playing [_queue] is a view of this. */ - private var linear: List = emptyList() - private val _index = MutableStateFlow(0) val index: StateFlow = _index.asStateFlow() @@ -51,10 +62,7 @@ class PlayerSession( } override fun onAutoAdvanced() { - val target = peekNextIndex(fromUser = false) ?: return - recordHistory(_current.value) - applyIndex(target) - warmSuccessor() + advance(fromUser = false, engineAlreadyMoved = true) } } @@ -64,45 +72,58 @@ class PlayerSession( fun play(tracks: List, startIndex: Int = 0) { if (tracks.isEmpty()) return - linear = tracks + coldOriginal = tracks val start = startIndex.coerceIn(tracks.indices) - if (_shuffle.value) { - applyShuffledView(current = tracks[start]) - loadAt(0, 0L, play = true) + val ordered = if (_shuffle.value) { + val tapped = tracks[start] + listOf(tapped) + tracks.filter { it.id != tapped.id }.shuffled() } else { - _queue.value = tracks - loadAt(start, 0L, play = true) + tracks } + _cold.value = ordered + lane = QueueLane.COLD + indexInLane = if (_shuffle.value) 0 else start + publish() + loadCurrent(0L, play = true) } fun togglePlay() { if (_current.value == null) { - val q = _queue.value - if (q.isNotEmpty()) loadAt(_index.value.coerceIn(q.indices), 0L, play = true) + if (activeList().isNotEmpty()) loadCurrent(0L, play = true) return } if (engine.isPlaying.value) engine.pause() else engine.play() } fun pause() = engine.pause() - fun stop() = engine.stop() - fun next() = advance(fromUser = true) fun previous() { - val q = _queue.value - if (q.isEmpty()) return if (engine.getPositionMs() > RESTART_WINDOW_MS) { engine.seekTo(0) return } - val i = _index.value - 1 - when { - i in q.indices -> loadAt(i, 0L, play = true) - _repeat.value == RepeatMode.ALL -> loadAt(q.lastIndex, 0L, play = true) - else -> engine.seekTo(0) + val prev = when { + lane == QueueLane.COLD && indexInLane > 0 -> { + indexInLane-- + lane = QueueLane.COLD + _cold.value.getOrNull(indexInLane) + } + lane == QueueLane.HOT && indexInLane > 0 -> { + indexInLane-- + lane = QueueLane.HOT + _hot.value.getOrNull(indexInLane) + } + _history.value.isNotEmpty() -> _history.value.first() + else -> null } + if (prev == null) { + engine.seekTo(0) + return + } + publish() + loadCurrent(0L, play = true) } fun seekTo(positionMs: Long) = engine.seekTo(positionMs.coerceAtLeast(0)) @@ -110,16 +131,20 @@ class PlayerSession( fun toggleShuffle() { val turningOn = !_shuffle.value _shuffle.value = turningOn - val cur = _current.value + val cur = currentTrack() if (turningOn) { - if (linear.isEmpty()) linear = _queue.value - applyShuffledView(current = cur) + val pool = coldOriginal.ifEmpty { _cold.value } + val rest = pool.filter { it.id != cur?.id }.shuffled() + _cold.value = if (cur != null && lane == QueueLane.COLD) listOf(cur) + rest else rest + if (lane == QueueLane.COLD) indexInLane = 0 } else { - val restored = linear.ifEmpty { _queue.value } - _queue.value = restored - _index.value = cur?.let { c -> restored.indexOfFirst { it.id == c.id } } - ?.takeIf { it >= 0 } ?: 0 + val restored = coldOriginal.ifEmpty { _cold.value } + _cold.value = restored + if (lane == QueueLane.COLD && cur != null) { + indexInLane = restored.indexOfFirst { it.id == cur.id }.takeIf { it >= 0 } ?: 0 + } } + publish() warmSuccessor() } @@ -135,74 +160,111 @@ class PlayerSession( } fun skipTo(index: Int) { - if (index in _queue.value.indices) loadAt(index, 0L, play = true) + val q = _queue.value + if (index !in q.indices) return + playTrack(q[index], q) } fun playTrack(track: Track, context: List = _queue.value.ifEmpty { listOf(track) }) { - val i = context.indexOfFirst { it.id == track.id }.coerceAtLeast(0) - play(if (context.isNotEmpty()) context else listOf(track), i) + val hotIdx = _hot.value.indexOfFirst { it.id == track.id } + if (hotIdx >= 0) { + playHot(hotIdx) + return + } + val coldIdx = _cold.value.indexOfFirst { it.id == track.id } + if (coldIdx >= 0) { + playCold(coldIdx) + return + } + play(if (context.isNotEmpty()) context else listOf(track), context.indexOfFirst { it.id == track.id }.coerceAtLeast(0)) + } + + fun playHot(index: Int) { + if (index !in _hot.value.indices) return + recordHistory(currentTrack()) + val kept = _hot.value.drop(index) + _hot.value = kept + lane = QueueLane.HOT + indexInLane = 0 + publish() + loadCurrent(0L, play = true) + } + + fun playCold(index: Int) { + if (index !in _cold.value.indices) return + recordHistory(currentTrack()) + lane = QueueLane.COLD + indexInLane = index + publish() + loadCurrent(0L, play = true) } + /** Play next: insert at the head of the hot lane. */ fun addNext(track: Track) { - val q = _queue.value.toMutableList() - val i = (_index.value + 1).coerceIn(0, q.size) - q.add(i, track) - _queue.value = q - val lin = linear.toMutableList() - val linAt = _current.value?.let { c -> lin.indexOfFirst { it.id == c.id } } ?: lin.lastIndex - lin.add((linAt + 1).coerceIn(0, lin.size), track) - linear = lin + if (nothingLoaded()) { + play(listOf(track), 0) + return + } + _hot.value = listOf(track) + _hot.value + publish() warmSuccessor() } + /** Append to the hot lane (mobile addToQueue). Never starts playback. */ fun enqueue(track: Track) { - if (_queue.value.isEmpty() && _current.value == null) { + if (nothingLoaded()) { play(listOf(track), 0) return } - _queue.value = _queue.value + track - linear = linear + track + _hot.value = _hot.value + track + publish() warmSuccessor() } fun enqueueAll(tracks: List) { if (tracks.isEmpty()) return - if (_queue.value.isEmpty() && _current.value == null) { + if (nothingLoaded()) { play(tracks, 0) return } - _queue.value = _queue.value + tracks - linear = linear + tracks + _hot.value = _hot.value + tracks + publish() warmSuccessor() } + fun moveHot(from: Int, to: Int) = moveLane(_hot, from, to, QueueLane.HOT) + fun moveCold(from: Int, to: Int) = moveLane(_cold, from, to, QueueLane.COLD) + fun moveQueueItem(from: Int, to: Int) { val q = _queue.value.toMutableList() if (from !in q.indices || to !in q.indices || from == to) return val item = q.removeAt(from) q.add(to, item) - val currentId = _current.value?.id - _queue.value = q - if (!_shuffle.value) linear = q - if (currentId != null) { - _index.value = q.indexOfFirst { it.id == currentId }.coerceAtLeast(0) + // Best-effort: treat as cold-only legacy move. + if (_hot.value.isEmpty()) { + _cold.value = q + coldOriginal = q + val cur = _current.value + indexInLane = cur?.let { c -> q.indexOfFirst { it.id == c.id } }?.takeIf { it >= 0 } ?: 0 + publish() + warmSuccessor() } - warmSuccessor() } - fun clearQueueKeepCurrent() { - val cur = _current.value ?: run { - _queue.value = emptyList() - linear = emptyList() - _index.value = 0 - return + fun clearHot() { + if (lane == QueueLane.HOT) { + val keep = currentTrack() + _hot.value = if (keep != null) listOf(keep) else emptyList() + indexInLane = if (keep != null) 0 else -1 + } else { + _hot.value = emptyList() } - _queue.value = listOf(cur) - linear = listOf(cur) - _index.value = 0 + publish() warmSuccessor() } + fun clearQueueKeepCurrent() = clearHot() + fun clearHistory() { _history.value = emptyList() } @@ -212,31 +274,43 @@ class PlayerSession( fun snapshot(): PlaybackSnapshot = PlaybackSnapshot( queue = _queue.value, - linear = linear, + linear = coldOriginal, index = _index.value, history = _history.value, shuffle = _shuffle.value, repeat = _repeat.value, volume = _volume.value, - positionMs = engine.getPositionMs() + positionMs = engine.getPositionMs(), + hotQueue = _hot.value, + coldQueue = _cold.value, + coldOriginal = coldOriginal, + lane = lane, + indexInLane = indexInLane ) fun restore(snap: PlaybackSnapshot, play: Boolean = false) { - if (snap.queue.isEmpty()) return - linear = snap.linear.ifEmpty { snap.queue } _shuffle.value = snap.shuffle _repeat.value = snap.repeat setVolume(snap.volume) - _queue.value = snap.queue _history.value = snap.history - val i = snap.index.coerceIn(snap.queue.indices) - _index.value = i - _current.value = snap.queue[i] - val nextIdx = peekNextIndex(fromUser = false) - val next = nextIdx?.let { snap.queue.getOrNull(it) }?.takeIf { it.id != snap.queue[i].id } - engine.load(snap.queue[i].toPlaybackMedia(), next?.toPlaybackMedia(), snap.positionMs.coerceAtLeast(0L)) - if (play) engine.play() else engine.pause() - warmSuccessor() + if (snap.coldQueue.isNotEmpty() || snap.hotQueue.isNotEmpty()) { + _hot.value = snap.hotQueue + _cold.value = snap.coldQueue + coldOriginal = snap.coldOriginal.ifEmpty { snap.linear.ifEmpty { snap.coldQueue } } + lane = snap.lane + indexInLane = snap.indexInLane + } else if (snap.queue.isNotEmpty()) { + _hot.value = emptyList() + _cold.value = snap.queue + coldOriginal = snap.linear.ifEmpty { snap.queue } + lane = QueueLane.COLD + indexInLane = snap.index.coerceIn(snap.queue.indices) + } else { + return + } + publish() + loadCurrent(snap.positionMs.coerceAtLeast(0L), play = play) + if (!play) engine.pause() } fun release() { @@ -244,51 +318,127 @@ class PlayerSession( engine.release() } - private fun advance(fromUser: Boolean) { - val target = peekNextIndex(fromUser) ?: return - val q = _queue.value - val expected = q.getOrNull(target) ?: return - val sequential = target == _index.value + 1 - if (sequential && engine.hasPreparedNext() && engine.playPreparedNext()) { - recordHistory(_current.value) - applyIndex(target) - warmSuccessor() - return + private fun nothingLoaded(): Boolean = + currentTrack() == null && _hot.value.isEmpty() && _cold.value.isEmpty() + + private fun currentTrack(): Track? = when (lane) { + QueueLane.HOT -> _hot.value.getOrNull(indexInLane) + QueueLane.COLD -> _cold.value.getOrNull(indexInLane) + } + + private fun activeList(): List = when (lane) { + QueueLane.HOT -> _hot.value + QueueLane.COLD -> _cold.value + } + + private fun upcomingHot(): List { + val hot = _hot.value + return if (lane == QueueLane.HOT) hot.drop(indexInLane + 1) else hot + } + + private fun upcomingCold(): List { + val cold = _cold.value + return if (lane == QueueLane.COLD) cold.drop(indexInLane + 1) else cold + } + + private fun peekNext(): Pair? { + val hotUp = upcomingHot() + if (hotUp.isNotEmpty()) { + val idx = if (lane == QueueLane.HOT) indexInLane + 1 else 0 + return QueueLane.HOT to idx } - loadAt(target, 0L, play = true) + val coldUp = upcomingCold() + if (coldUp.isNotEmpty()) { + val idx = if (lane == QueueLane.COLD) indexInLane + 1 else 0 + return QueueLane.COLD to idx + } + if (_repeat.value == RepeatMode.ALL && coldOriginal.isNotEmpty()) { + return QueueLane.COLD to 0 + } + return null } - private fun peekNextIndex(fromUser: Boolean): Int? { - val q = _queue.value - if (q.isEmpty()) return null - val i = _index.value + 1 - if (i in q.indices) return i - if (_repeat.value == RepeatMode.ALL) return 0 - if (fromUser) return null + private fun peekNextTrack(): Track? { + upcomingHot().firstOrNull()?.let { return it } + upcomingCold().firstOrNull()?.let { return it } + if (_repeat.value == RepeatMode.ALL) return coldOriginal.firstOrNull() return null } - private fun applyShuffledView(current: Track?) { - val pool = linear.ifEmpty { _queue.value } - val rest = pool.filter { it.id != current?.id }.shuffled() - _queue.value = if (current != null) listOf(current) + rest else rest - _index.value = 0 + private fun consumeCurrent() { + when (lane) { + QueueLane.HOT -> { + val list = _hot.value.toMutableList() + if (indexInLane in list.indices) list.removeAt(indexInLane) + _hot.value = list + } + QueueLane.COLD -> { + val list = _cold.value.toMutableList() + if (indexInLane in list.indices) list.removeAt(indexInLane) + _cold.value = list + } + } } - private fun applyIndex(i: Int) { - val track = _queue.value.getOrNull(i) ?: return - _index.value = i - _current.value = track + private fun locate(track: Track): Pair? { + _hot.value.indexOfFirst { it.id == track.id }.takeIf { it >= 0 }?.let { + return QueueLane.HOT to it + } + _cold.value.indexOfFirst { it.id == track.id }.takeIf { it >= 0 }?.let { + return QueueLane.COLD to it + } + return null } - private fun loadAt(i: Int, startMs: Long, play: Boolean) { - val q = _queue.value - val track = q.getOrNull(i) ?: return - recordHistory(_current.value) - _index.value = i + private fun advance(fromUser: Boolean, engineAlreadyMoved: Boolean = false) { + val nextTrack = peekNextTrack() + if (nextTrack == null && fromUser && _repeat.value != RepeatMode.ALL) { + return + } + recordHistory(currentTrack()) + consumeCurrent() + var target = nextTrack?.let { locate(it) } + if (target == null && _repeat.value == RepeatMode.ALL && coldOriginal.isNotEmpty()) { + _cold.value = if (_shuffle.value) coldOriginal.shuffled() else coldOriginal + target = _cold.value.firstOrNull()?.let { QueueLane.COLD to 0 } + } + if (target == null) { + indexInLane = -1 + publish() + if (!fromUser) engine.pause() + return + } + lane = target.first + indexInLane = target.second + publish() + if (engineAlreadyMoved) { + warmSuccessor() + return + } + if (engine.hasPreparedNext() && engine.playPreparedNext()) { + engine.play() + warmSuccessor() + return + } + loadCurrent(0L, play = true) + } + + private fun publish() { + val cur = currentTrack() + _current.value = cur + val flat = buildList { + if (cur != null) add(cur) + addAll(upcomingHot()) + addAll(upcomingCold()) + } + _queue.value = flat + _index.value = 0 + } + + private fun loadCurrent(startMs: Long, play: Boolean) { + val track = currentTrack() ?: return _current.value = track - val nextIdx = peekNextIndex(fromUser = false) - val next = nextIdx?.let { q.getOrNull(it) }?.takeIf { it.id != track.id } + val next = peekNextTrack()?.takeIf { it.id != track.id } engine.load(track.toPlaybackMedia(), next?.toPlaybackMedia(), startMs) if (play) engine.play() else engine.pause() warmSuccessor() @@ -301,12 +451,8 @@ class PlayerSession( } private fun warmSuccessor() { - val nextIdx = peekNextIndex(fromUser = false) ?: run { - engine.setNext(null) - return - } - val next = _queue.value.getOrNull(nextIdx) ?: return - if (next.id == _current.value?.id) { + val next = peekNextTrack() + if (next == null || next.id == currentTrack()?.id) { engine.setNext(null) return } @@ -314,6 +460,31 @@ class PlayerSession( engine.warmupNext() } + private fun moveLane( + flow: MutableStateFlow>, + from: Int, + to: Int, + which: QueueLane + ) { + val list = flow.value.toMutableList() + if (from !in list.indices || to !in list.indices || from == to) return + val item = list.removeAt(from) + list.add(to, item) + flow.value = list + if (lane == which) { + indexInLane = remapIndex(indexInLane, from, to) + } + publish() + warmSuccessor() + } + + private fun remapIndex(current: Int, from: Int, to: Int): Int = when { + current == from -> to + from < current && to >= current -> current - 1 + from > current && to <= current -> current + 1 + else -> current + } + companion object { const val RESTART_WINDOW_MS = 3_000L private const val HISTORY_CAP = 50 @@ -328,5 +499,10 @@ data class PlaybackSnapshot( val shuffle: Boolean = false, val repeat: RepeatMode = RepeatMode.OFF, val volume: Float = 1f, - val positionMs: Long = 0L + val positionMs: Long = 0L, + val hotQueue: List = emptyList(), + val coldQueue: List = emptyList(), + val coldOriginal: List = emptyList(), + val lane: QueueLane = QueueLane.COLD, + val indexInLane: Int = 0 ) diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt index 13facc9c..e07365db 100644 --- a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/player/PlayerSessionTest.kt @@ -58,17 +58,33 @@ class PlayerSessionTest { session.toggleShuffle() assertFalse(session.shuffle.value) assertEquals("4", session.current.value?.id) - assertEquals(list.map { it.id }, session.queue.value.map { it.id }) + assertEquals(list.map { it.id }, session.coldQueue.value.map { it.id }) } @Test - fun enqueueAppendsWithoutChangingCurrent() { + fun enqueueGoesToHotAndPlaysNext() { val engine = FakeEngine() val session = PlayerSession(engine) session.play(tracks(2), 0) session.enqueue(Track(id = "9", uri = "file://9", title = "t9")) assertEquals("1", session.current.value?.id) - assertEquals(listOf("1", "2", "9"), session.queue.value.map { it.id }) + assertEquals(listOf("9"), session.hotQueue.value.map { it.id }) + assertEquals(listOf("1", "9", "2"), session.queue.value.map { it.id }) + session.next() + assertEquals("9", session.current.value?.id) + session.next() + assertEquals("2", session.current.value?.id) + } + + @Test + fun endedAdvancesAndPlays() { + val engine = FakeEngine() + val session = PlayerSession(engine) + session.play(tracks(3), 0) + engine.played = false + engine.emitEnded() + assertEquals("2", session.current.value?.id) + assertTrue(engine.played) } @Test @@ -83,7 +99,8 @@ class PlayerSessionTest { other.restore(snap, play = false) assertEquals("3", other.current.value?.id) assertEquals(12_000L, other.positionMs()) - assertEquals(listOf("1", "2", "3", "4"), other.queue.value.map { it.id }) + assertEquals(listOf("3", "4"), other.queue.value.map { it.id }) + assertEquals(listOf("1", "2", "3", "4"), other.coldQueue.value.map { it.id }) assertEquals(RepeatMode.ALL, other.repeat.value) assertFalse(other.isPlaying.value) other.seekTo(45_000L) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt index 547039ef..7b2ca30a 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopCollection.kt @@ -18,6 +18,9 @@ class DesktopCollection(configDir: String) { private val _pinned = MutableStateFlow>(emptyList()) val pinned: StateFlow> = _pinned.asStateFlow() + private val _saved = MutableStateFlow>(emptyList()) + val saved: StateFlow> = _saved.asStateFlow() + data class Pin(val kind: Kind, val id: String, val title: String, val subtitle: String) enum class Kind { ALBUM, ARTIST, SONG, PLAYLIST } @@ -46,6 +49,17 @@ class DesktopCollection(configDir: String) { fun isPinned(kind: Kind, id: String): Boolean = _pinned.value.any { it.kind == kind && it.id == id } + fun isSaved(kind: Kind, id: String): Boolean = + _saved.value.any { it.kind == kind && it.id == id } + + fun toggleSaved(pin: Pin) { + val cur = _saved.value.toMutableList() + val had = cur.removeAll { it.kind == pin.kind && it.id == pin.id } + if (!had) cur.add(0, pin) + _saved.value = cur + persist() + } + fun togglePin(pin: Pin) { if (isPinned(pin.kind, pin.id)) unpin(pin.kind, pin.id) else pin(pin) } @@ -69,6 +83,13 @@ class DesktopCollection(configDir: String) { Pin(kind, parts[1], parts[2], parts[3]) } _pinned.value = pins + val saved = p.getProperty("saved").orEmpty().split('\n').mapNotNull { line -> + val parts = line.split('\t') + if (parts.size < 4) return@mapNotNull null + val kind = runCatching { Kind.valueOf(parts[0]) }.getOrNull() ?: return@mapNotNull null + Pin(kind, parts[1], parts[2], parts[3]) + } + _saved.value = saved } } @@ -81,6 +102,10 @@ class DesktopCollection(configDir: String) { "pins", _pinned.value.joinToString("\n") { "${it.kind}\t${it.id}\t${it.title}\t${it.subtitle}" } ) + p.setProperty( + "saved", + _saved.value.joinToString("\n") { "${it.kind}\t${it.id}\t${it.title}\t${it.subtitle}" } + ) file.outputStream().use { p.store(it, "Yuri Player collection") } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt index 6ffb65e3..a06bc916 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -266,24 +266,23 @@ data class DesktopPlaylist( fun put(t: Track) { for (k in t.indexKeys()) index.putIfAbsent(k, t) } - for (t in library) put(t) for (t in snapshots) put(t) + for (t in library) put(t) val seen = HashSet() val out = ArrayList() + val missed = ArrayList() fun take(t: Track) { if (t.indexKeys().any { it in seen }) return seen += t.indexKeys() out += t } - for (snap in snapshots) { - val live = snap.indexKeys().firstNotNullOfOrNull { index[it] } - take(live ?: snap) - } - val missed = ArrayList() for (key in trackIds) { - val hit = index[key] ?: matchLoose(key, library) + val hit = index[key] + ?: snapshots.firstOrNull { key in it.indexKeys() } + ?: matchLoose(key, library + snapshots) if (hit != null) take(hit) else missed += key } + for (snap in snapshots) take(snap) if (out.size < trackIds.size.coerceAtLeast(snapshots.size) || missed.isNotEmpty()) { PlaylistLog.resolve(name, trackIds, snapshots, out, missed) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index d8bb1273..40a0ad37 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -194,6 +194,7 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun playPreparedNext(): Boolean { val next = preparedNext.getAndSet(null) ?: return false load(next, null, 0L) + play() return true } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt index 57860e4a..fa7766a8 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -172,7 +172,7 @@ fun PlaylistPage( } Spacer(Modifier.height(16.dp)) LazyColumn(Modifier.weight(1f)) { - itemsIndexed(tracks, key = { _, t -> t.id }) { index, track -> + itemsIndexed(tracks, key = { i, t -> "${t.id}#$i" }) { index, track -> TrackRow( track = track.toRow(highlighted = track.id == currentId), onClick = { onPlay(tracks, index) }, diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 6d708f19..33bbc5eb 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -92,7 +92,6 @@ fun YuriDesktopApp(session: DesktopSession) { val tracks by session.tracks.collectAsState() val current by session.player.current.collectAsState() val playing by session.player.isPlaying.collectAsState() - val queue by session.player.queue.collectAsState() val history by session.player.history.collectAsState() val status by session.scanMessage.collectAsState() val position by session.positionMs.collectAsState() @@ -103,6 +102,9 @@ fun YuriDesktopApp(session: DesktopSession) { val volume by session.player.volume.collectAsState() val liked by session.collection.liked.collectAsState() val pins by session.collection.pinned.collectAsState() + val saved by session.collection.saved.collectAsState() + val hotQueue by session.player.hotQueue.collectAsState() + val coldQueue by session.player.coldQueue.collectAsState() val playlists by session.playlists.playlists.collectAsState() val remotes by session.sources.remotes.collectAsState() val leftFrac by session.layout.leftFraction.collectAsState() @@ -230,7 +232,7 @@ fun YuriDesktopApp(session: DesktopSession) { fun albumRecency(album: AlbumPageModel): Int = album.tracks.minOfOrNull { recency[it.id] ?: Int.MAX_VALUE } ?: Int.MAX_VALUE - val libraryItems = remember(albums, tracks, libraryFilter, pins, history, liked, playlists) { + val libraryItems = remember(albums, tracks, libraryFilter, pins, saved, history, liked, playlists) { val likedAlbums = albums.filter { album -> album.tracks.any { it.id in liked } } val likedArtists = tracks.filter { it.id in liked }.map { it.displayArtist }.distinct() val pinItems = pins.map { pin -> @@ -278,6 +280,7 @@ fun YuriDesktopApp(session: DesktopSession) { LibraryFilter.Artists -> { val names = ( pins.filter { it.kind == DesktopCollection.Kind.ARTIST }.map { it.title } + + saved.filter { it.kind == DesktopCollection.Kind.ARTIST }.map { it.title } + likedArtists ).distinct() names.map { name -> @@ -291,7 +294,10 @@ fun YuriDesktopApp(session: DesktopSession) { } } LibraryFilter.Albums -> { - val pinnedAlbums = pins.filter { it.kind == DesktopCollection.Kind.ALBUM } + val pinnedAlbums = ( + pins.filter { it.kind == DesktopCollection.Kind.ALBUM } + + saved.filter { it.kind == DesktopCollection.Kind.ALBUM } + ) .mapNotNull { pin -> albums.firstOrNull { it.id == pin.id } ?: albums.firstOrNull { it.title.equals(pin.title, true) } @@ -430,17 +436,6 @@ fun YuriDesktopApp(session: DesktopSession) { onToggleLike = { val t = current ?: return@BottomPlayerBar session.collection.toggleLike(t.id) - val album = albums.firstOrNull { a -> a.tracks.any { it.id == t.id } } - if (album != null && session.collection.isLiked(t.id)) { - session.collection.pin( - DesktopCollection.Pin( - DesktopCollection.Kind.ALBUM, - album.id, - album.title, - album.artist - ) - ) - } }, queueVisible = showSidebar, onToggleQueue = { showSidebar = !showSidebar } @@ -448,32 +443,41 @@ fun YuriDesktopApp(session: DesktopSession) { }, sidebar = { PlayerChromeTheme(playerColors, useArtBackground = true) { + val curId = current?.id + val hotIdx = hotQueue.indexOfFirst { it.id == curId } + val coldIdx = coldQueue.indexOfFirst { it.id == curId } + val hotUp = if (hotIdx >= 0) hotQueue.drop(hotIdx + 1) else hotQueue + val coldUp = if (coldIdx >= 0) coldQueue.drop(coldIdx + 1) else coldQueue + val coldLabel = coldUp.firstOrNull()?.displayAlbum?.takeIf { it.isNotBlank() && it != "Unknown Album" } + ?: "Up next" NowPlayingSidebar( track = current?.toCover(), - queue = queue.map { it.toRow(highlighted = it.id == current?.id) }, + hot = hotUp.map { it.toRow() }, + cold = coldUp.map { it.toRow() }, + coldLabel = coldLabel, history = history.map { it.toRow() }, - onQueueTrack = { row -> - session.player.playTrack( - queue.first { it.id == row.id }, - queue - ) - }, + onPlayHot = { i -> hotUp.getOrNull(i)?.let { session.player.playTrack(it) } }, + onPlayCold = { i -> coldUp.getOrNull(i)?.let { session.player.playTrack(it) } }, onHistoryTrack = { row -> val t = history.firstOrNull { it.id == row.id } ?: return@NowPlayingSidebar session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) }, - onClearQueue = session.player::clearQueueKeepCurrent, + onClearHot = session.player::clearHot, onClearHistory = session.player::clearHistory, - onMoveUpcoming = { from, to -> - val cur = current ?: return@NowPlayingSidebar - val base = queue.indexOfFirst { it.id == cur.id } - if (base < 0) return@NowPlayingSidebar - session.player.moveQueueItem(base + 1 + from, base + 1 + to) + onMoveHot = { from, to -> + val skip = if (hotIdx >= 0) hotIdx + 1 else 0 + session.player.moveHot(from + skip, to + skip) + }, + onMoveCold = { from, to -> + val skip = if (coldIdx >= 0) coldIdx + 1 else 0 + session.player.moveCold(from + skip, to + skip) }, likedIds = liked, onToggleTrackLike = session.collection::toggleLike, songMenu = { row -> - tracks.firstOrNull { it.id == row.id }?.let { songMenu(it) }.orEmpty() + (hotQueue + coldQueue + tracks).firstOrNull { it.id == row.id } + ?.let { songMenu(it) } + .orEmpty() } ) } @@ -539,9 +543,9 @@ fun YuriDesktopApp(session: DesktopSession) { albumTracks.getOrNull(i)?.let { editSong = it } }, onArtist = ::openArtist, - liked = session.collection.isPinned(DesktopCollection.Kind.ALBUM, live.id), + liked = session.collection.isSaved(DesktopCollection.Kind.ALBUM, live.id), onToggleLike = { - session.collection.togglePin( + session.collection.toggleSaved( DesktopCollection.Pin( DesktopCollection.Kind.ALBUM, live.id, @@ -615,9 +619,9 @@ fun YuriDesktopApp(session: DesktopSession) { onClearHeader = { profile = session.artists.clearBanner(r.name) }, - liked = session.collection.isPinned(DesktopCollection.Kind.ARTIST, r.name), + liked = session.collection.isSaved(DesktopCollection.Kind.ARTIST, r.name), onToggleLike = { - session.collection.togglePin( + session.collection.toggleSaved( DesktopCollection.Pin( DesktopCollection.Kind.ARTIST, r.name, @@ -665,6 +669,9 @@ fun YuriDesktopApp(session: DesktopSession) { if (pl == null) { Text("Playlist gone.", modifier = Modifier.padding(24.dp)) } else { + LaunchedEffect(pl.id, pl.snapshots.map { it.id }) { + session.ensureTracks(pl.snapshots) + } PlaylistPage( playlist = pl, tracks = pl.tracks(tracks), @@ -676,9 +683,9 @@ fun YuriDesktopApp(session: DesktopSession) { onEditTrack = { editSong = it }, likedIds = liked, onToggleLike = session.collection::toggleLike, - playlistLiked = session.collection.isPinned(DesktopCollection.Kind.PLAYLIST, pl.id), + playlistLiked = session.collection.isSaved(DesktopCollection.Kind.PLAYLIST, pl.id), onTogglePlaylistLike = { - session.collection.togglePin( + session.collection.toggleSaved( DesktopCollection.Pin( DesktopCollection.Kind.PLAYLIST, pl.id, @@ -704,14 +711,6 @@ fun YuriDesktopApp(session: DesktopSession) { onDismiss = { showNewPlaylist = false }, onCreate = { name, description -> val created = session.playlists.create(name, description) - session.collection.pin( - DesktopCollection.Pin( - DesktopCollection.Kind.PLAYLIST, - created.id, - created.name, - "Playlist" - ) - ) showNewPlaylist = false push(Route.Playlist(created.id)) } From 5ee71893fce9916f9e7d30af3285e27a6781658e Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Sat, 22 Aug 2026 02:07:06 +0000 Subject: [PATCH 53/69] Right-click now playing and every queue row Context menus listen on the initial pointer pass so clicks aren't eaten by the player bar or drag-to-reorder. Same song menu as everywhere else, including the bottom-left now playing chip. --- .../yuriplayer/components/list/ContextMenu.kt | 3 +- .../components/player/BottomPlayerBar.kt | 63 ++++++++++--------- .../components/player/NowPlayingSidebar.kt | 2 + .../components/player/QueuePanel.kt | 22 ++++--- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 24 +++++-- 5 files changed, 71 insertions(+), 43 deletions(-) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt index 681ee702..3db735d1 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/list/ContextMenu.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.isSecondaryPressed import androidx.compose.ui.input.pointer.pointerInput @@ -44,7 +45,7 @@ fun ContextMenuAnchor( if (items.isEmpty()) return@pointerInput awaitPointerEventScope { while (true) { - val event = awaitPointerEvent() + val event = awaitPointerEvent(PointerEventPass.Initial) if (event.type != PointerEventType.Press) continue if (!event.buttons.isSecondaryPressed) continue val change = event.changes.firstOrNull() ?: continue diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt index c6cb876b..e003154a 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/BottomPlayerBar.kt @@ -41,7 +41,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.ContextMenuAnchor import capital.yuri.yuriplayer.components.list.formatTime +import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.CoverRef import capital.yuri.yuriplayer.core.player.RepeatMode @@ -66,7 +68,8 @@ fun BottomPlayerBar( liked: Boolean = false, onToggleLike: () -> Unit = {}, queueVisible: Boolean = true, - onToggleQueue: () -> Unit = {} + onToggleQueue: () -> Unit = {}, + songMenu: List = emptyList() ) { val onAccent = MaterialTheme.colorScheme.onPrimary val muted = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) @@ -82,36 +85,38 @@ fun BottomPlayerBar( .padding(horizontal = 12.dp, vertical = 10.dp) .height(84.dp) ) { - Row( - Modifier.align(Alignment.CenterStart).widthIn(max = 360.dp), - verticalAlignment = Alignment.CenterVertically + ContextMenuAnchor( + items = if (track != null) songMenu else emptyList(), + modifier = Modifier.align(Alignment.CenterStart).widthIn(max = 360.dp) ) { - CoverArt(model = track?.artworkUri, size = 56.dp, corner = 6.dp) - Spacer(Modifier.width(12.dp)) - Column(Modifier.weight(1f, fill = false).widthIn(max = 220.dp)) { - Text( - track?.title ?: "Nothing playing", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontWeight = FontWeight.SemiBold, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface - ) - Text( - track?.subtitle ?: " ", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodySmall, - color = muted - ) - } - if (track != null) { - IconButton(onClick = onToggleLike) { - Icon( - if (liked) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, - contentDescription = if (liked) "Unlike" else "Like", - tint = if (liked) accent else muted + Row(verticalAlignment = Alignment.CenterVertically) { + CoverArt(model = track?.artworkUri, size = 56.dp, corner = 6.dp) + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f, fill = false).widthIn(max = 220.dp)) { + Text( + track?.title ?: "Nothing playing", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface ) + Text( + track?.subtitle ?: " ", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = muted + ) + } + if (track != null) { + IconButton(onClick = onToggleLike) { + Icon( + if (liked) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = if (liked) "Unlike" else "Like", + tint = if (liked) accent else muted + ) + } } } } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt index 7f529b33..5a6e570a 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/NowPlayingSidebar.kt @@ -37,6 +37,7 @@ fun NowPlayingSidebar( likedIds: Set = emptySet(), onToggleTrackLike: (String) -> Unit = {}, songMenu: (TrackRowModel) -> List = { emptyList() }, + nowPlayingMenu: List = emptyList(), modifier: Modifier = Modifier ) { var artExpanded by remember { mutableStateOf(false) } @@ -69,6 +70,7 @@ fun NowPlayingSidebar( likedIds = likedIds, onToggleLike = onToggleTrackLike, songMenu = songMenu, + nowPlayingMenu = nowPlayingMenu, artExpanded = artExpanded, onToggleArt = { artExpanded = !artExpanded }, modifier = Modifier.weight(1f) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt index 95969cb4..21800f57 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/QueuePanel.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt +import capital.yuri.yuriplayer.components.list.ContextMenuAnchor import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.CoverRef @@ -76,6 +77,7 @@ fun QueuePanel( likedIds: Set = emptySet(), onToggleLike: (String) -> Unit = {}, songMenu: (TrackRowModel) -> List = { emptyList() }, + nowPlayingMenu: List = emptyList(), artExpanded: Boolean = false, onToggleArt: (() -> Unit)? = null, modifier: Modifier = Modifier @@ -136,7 +138,8 @@ fun QueuePanel( NowPlayingQueueCard( track = np, showCover = !artExpanded, - onToggleArt = onToggleArt + onToggleArt = onToggleArt, + menu = nowPlayingMenu ) } Box(Modifier.weight(1f)) { @@ -279,14 +282,16 @@ private fun LazyListScope.queueRows( private fun NowPlayingQueueCard( track: CoverRef, showCover: Boolean, - onToggleArt: (() -> Unit)? + onToggleArt: (() -> Unit)?, + menu: List = emptyList() ) { - Row( - Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { + ContextMenuAnchor(items = menu) { + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { if (showCover) { ExpandableCover( artworkUri = track.artworkUri, @@ -310,6 +315,7 @@ private fun NowPlayingQueueCard( maxLines = 1 ) } + } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index 33bbc5eb..dc3fd8d8 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -55,6 +55,7 @@ import capital.yuri.yuriplayer.components.menu.ContextMenuScope import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.menu.buildContextMenu import capital.yuri.yuriplayer.components.model.AlbumPageModel +import capital.yuri.yuriplayer.components.model.TrackRowModel import capital.yuri.yuriplayer.components.model.albums import capital.yuri.yuriplayer.components.model.artistPage import capital.yuri.yuriplayer.components.model.toCover @@ -206,6 +207,19 @@ fun YuriDesktopApp(session: DesktopSession) { } } + fun menuForQueueRow(row: TrackRowModel, pool: List): List { + val track = pool.firstOrNull { it.id == row.id } ?: Track( + id = row.id, + uri = "", + title = row.title, + artist = row.artist, + album = row.album, + durationMs = row.durationMs, + artworkUri = row.artworkUri + ) + return songMenu(track) + } + fun pinTracks(item: LibraryRailItem): List = when { item.id == "liked" -> tracks.filter { it.id in liked } item.id.startsWith("playlist:") -> { @@ -438,7 +452,8 @@ fun YuriDesktopApp(session: DesktopSession) { session.collection.toggleLike(t.id) }, queueVisible = showSidebar, - onToggleQueue = { showSidebar = !showSidebar } + onToggleQueue = { showSidebar = !showSidebar }, + songMenu = current?.let { songMenu(it) }.orEmpty() ) }, sidebar = { @@ -475,10 +490,9 @@ fun YuriDesktopApp(session: DesktopSession) { likedIds = liked, onToggleTrackLike = session.collection::toggleLike, songMenu = { row -> - (hotQueue + coldQueue + tracks).firstOrNull { it.id == row.id } - ?.let { songMenu(it) } - .orEmpty() - } + menuForQueueRow(row, hotQueue + coldQueue + history + tracks) + }, + nowPlayingMenu = current?.let { songMenu(it) }.orEmpty() ) } } From 309b5040976f4a4050c70098c8163b30cb7922d9 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Sat, 22 Aug 2026 02:12:00 +0000 Subject: [PATCH 54/69] Keep playlist order with an ordinal on each entry Members are stored as ordered entries (ordinal + snapshot). Adds append at the end, drag-reorder rewrites ordinals, and live library metadata no longer reshuffles the list. --- .../desktop/DesktopPlaylistStore.kt | 182 +++++++++++------- .../yuriplayer/desktop/ui/PlaylistPage.kt | 30 ++- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 4 +- 3 files changed, 145 insertions(+), 71 deletions(-) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt index a06bc916..31013e4c 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -1,8 +1,6 @@ package capital.yuri.yuriplayer.desktop import capital.yuri.yuriplayer.core.library.Track -import capital.yuri.yuriplayer.core.library.foldSearch -import capital.yuri.yuriplayer.core.library.normalizeTitle import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -21,7 +19,7 @@ class DesktopPlaylistStore(configDir: String) { private val root = File(configDir, "playlists") private val jsonFile = File(root, "playlists.json") private val coversDir = File(root, "covers") - private val json = Json { ignoreUnknownKeys = true; prettyPrint = true } + private val json = Json { ignoreUnknownKeys = true; prettyPrint = true; encodeDefaults = true } private val _playlists = MutableStateFlow>(emptyList()) val playlists: StateFlow> = _playlists.asStateFlow() @@ -62,47 +60,58 @@ class DesktopPlaylistStore(configDir: String) { } fun setTracks(id: String, trackIds: List) { - update(id) { it.copy(trackIds = trackIds.distinct()) } + update(id) { pl -> + val byId = pl.orderedEntries().associateBy { it.snapshot.id } + val next = trackIds.mapIndexedNotNull { i, key -> + byId[key]?.copy(ordinal = i) + } + pl.withEntries(next) + } } fun addTracks(id: String, tracks: List) { if (tracks.isEmpty()) return update(id) { pl -> - val existing = pl.trackIds.toMutableList() - val seen = existing.toHashSet() - val snaps = pl.snapshots.toMutableList() + val current = pl.orderedEntries().toMutableList() + var nextOrdinal = (current.maxOfOrNull { it.ordinal } ?: -1) + 1 for (t in tracks) { PlaylistLog.add(pl.name, t) - val already = t.indexKeys().any { it in seen } - if (!already) { - existing += t.id - seen += t.indexKeys() - } - val i = snaps.indexOfFirst { s -> - s.indexKeys().intersect(t.indexKeys()).isNotEmpty() + val i = current.indexOfFirst { e -> e.matches(t) } + if (i >= 0) { + current[i] = current[i].copy( + snapshot = t, + keys = t.indexKeys().toList() + ) + } else { + current += PlaylistEntry( + ordinal = nextOrdinal++, + snapshot = t, + keys = t.indexKeys().toList() + ) } - if (i < 0) snaps += t else snaps[i] = t } - pl.copy(trackIds = existing, snapshots = snaps) + pl.withEntries(current) } } fun remember(tracks: List) { if (tracks.isEmpty()) return - var changed = false + var any = false val next = _playlists.value.map { pl -> - var snaps = pl.snapshots + val current = pl.orderedEntries().toMutableList() + var hit = false for (t in tracks) { - val hit = pl.trackIds.any { it in t.indexKeys() } || - pl.snapshots.any { s -> s.indexKeys().intersect(t.indexKeys()).isNotEmpty() } - if (!hit) continue - val i = snaps.indexOfFirst { s -> s.indexKeys().intersect(t.indexKeys()).isNotEmpty() } - snaps = if (i < 0) snaps + t else snaps.toMutableList().also { it[i] = t } - changed = true + val i = current.indexOfFirst { e -> e.matches(t) } + if (i < 0) continue + current[i] = current[i].copy(snapshot = t, keys = t.indexKeys().toList()) + hit = true + } + if (!hit) pl else { + any = true + pl.withEntries(current) } - if (snaps === pl.snapshots) pl else pl.copy(snapshots = snaps) } - if (changed) { + if (any) { _playlists.value = next persist() } @@ -114,28 +123,26 @@ class DesktopPlaylistStore(configDir: String) { fun playlistsContaining(track: Track): Set { val keys = track.indexKeys() return _playlists.value.filter { pl -> - pl.trackIds.any { it in keys } || - pl.snapshots.any { s -> s.indexKeys().any { it in keys } } + pl.orderedEntries().any { e -> e.allKeys().any { it in keys } } }.map { it.id }.toSet() } fun removeTracks(id: String, tracks: List) { - val drop = tracks.flatMap { it.playlistKeys() }.toHashSet() - update(id) { - it.copy( - trackIds = it.trackIds.filterNot { key -> key in drop }, - snapshots = it.snapshots.filterNot { t -> t.playlistKeys().any { k -> k in drop } } + val drop = tracks.flatMap { it.indexKeys() }.toHashSet() + update(id) { pl -> + pl.withEntries( + pl.orderedEntries().filterNot { e -> e.allKeys().any { it in drop } } ) } } fun moveTrack(id: String, from: Int, to: Int) { - update(id) { - val t = it.trackIds.toMutableList() - if (from !in t.indices || to !in t.indices) return@update it - val item = t.removeAt(from) - t.add(to, item) - it.copy(trackIds = t) + update(id) { pl -> + val list = pl.orderedEntries().toMutableList() + if (from !in list.indices || to !in list.indices || from == to) return@update pl + val item = list.removeAt(from) + list.add(to, item) + pl.withEntries(list) } } @@ -218,7 +225,9 @@ class DesktopPlaylistStore(configDir: String) { if (!jsonFile.exists()) return runCatching { val file = json.decodeFromString(jsonFile.readText()) - _playlists.value = file.playlists + _playlists.value = file.playlists.map { it.withEntries(it.orderedEntries()) } + }.onFailure { + System.err.println("YuriPlayer.Playlist load failed: ${it.message}") } } @@ -226,6 +235,8 @@ class DesktopPlaylistStore(configDir: String) { runCatching { root.mkdirs() jsonFile.writeText(json.encodeToString(PlaylistFile(_playlists.value))) + }.onFailure { + System.err.println("YuriPlayer.Playlist save failed: ${it.message}") } } @@ -253,7 +264,8 @@ data class DesktopPlaylist( val activeCoverId: String? = null, val createdAtMs: Long = 0L, val updatedAtMs: Long = 0L, - val snapshots: List = emptyList() + val snapshots: List = emptyList(), + val entries: List = emptyList() ) { fun artworkUri(library: List): String? { val active = covers.firstOrNull { it.id == activeCoverId } ?: covers.firstOrNull { !it.isSecret } @@ -261,46 +273,78 @@ data class DesktopPlaylist( return tracks(library).firstOrNull()?.artworkUri } + fun orderedEntries(): List { + if (entries.isNotEmpty()) return entries.sortedBy { it.ordinal } + return migrateLegacyEntries() + } + + fun withEntries(list: List): DesktopPlaylist { + val compact = list.sortedBy { it.ordinal }.mapIndexed { i, e -> + e.copy(ordinal = i, keys = e.snapshot.indexKeys().toList().ifEmpty { e.keys }) + } + return copy( + entries = compact, + trackIds = compact.map { it.snapshot.id }, + snapshots = compact.map { it.snapshot } + ) + } + fun tracks(library: List): List { val index = HashMap() - fun put(t: Track) { + for (t in library) { for (k in t.indexKeys()) index.putIfAbsent(k, t) } - for (t in snapshots) put(t) - for (t in library) put(t) + val ordered = orderedEntries() val seen = HashSet() - val out = ArrayList() - val missed = ArrayList() + val out = ArrayList(ordered.size) + for (entry in ordered) { + val live = entry.allKeys().firstNotNullOfOrNull { index[it] } ?: entry.snapshot + if (live.indexKeys().any { it in seen }) continue + seen += live.indexKeys() + out += live + } + return out + } + + private fun migrateLegacyEntries(): List { + val byKey = HashMap() + for (t in snapshots) { + for (k in t.indexKeys()) byKey.putIfAbsent(k, t) + } + val used = HashSet() + val out = ArrayList() fun take(t: Track) { - if (t.indexKeys().any { it in seen }) return - seen += t.indexKeys() - out += t + if (t.indexKeys().any { it in used }) return + used += t.indexKeys() + out += PlaylistEntry( + ordinal = out.size, + snapshot = t, + keys = t.indexKeys().toList() + ) } for (key in trackIds) { - val hit = index[key] - ?: snapshots.firstOrNull { key in it.indexKeys() } - ?: matchLoose(key, library + snapshots) - if (hit != null) take(hit) else missed += key - } - for (snap in snapshots) take(snap) - if (out.size < trackIds.size.coerceAtLeast(snapshots.size) || missed.isNotEmpty()) { - PlaylistLog.resolve(name, trackIds, snapshots, out, missed) + val hit = byKey[key] ?: snapshots.firstOrNull { key in it.indexKeys() } + if (hit != null) take(hit) } + for (s in snapshots) take(s) return out } +} - private fun matchLoose(key: String, library: List): Track? { - if (!key.startsWith("k:") && !key.startsWith("p:")) return null - val parts = key.drop(2).split('|') - val title = parts.getOrNull(0).orEmpty() - if (title.isEmpty()) return null - val album = if (key.startsWith("k:")) parts.getOrNull(2).orEmpty() else parts.getOrNull(1).orEmpty() - return library.firstOrNull { t -> - val nt = normalizeTitle(t.title ?: t.displayTitle) - val ft = foldSearch(t.title ?: t.displayTitle) - (nt == title || ft == title) && - (album.isEmpty() || foldSearch(t.album ?: "") == album) - } +@Serializable +data class PlaylistEntry( + val ordinal: Int, + val snapshot: Track, + val keys: List = emptyList() +) { + fun allKeys(): Set = buildSet { + addAll(keys) + addAll(snapshot.indexKeys()) + } + + fun matches(track: Track): Boolean { + val theirs = track.indexKeys() + return allKeys().any { it in theirs } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt index fa7766a8..00888e79 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.itemsIndexed @@ -40,8 +41,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import kotlin.math.roundToInt import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.dialog.InWindowPanel import capital.yuri.yuriplayer.components.list.LikeHeart @@ -171,6 +175,8 @@ fun PlaylistPage( } } Spacer(Modifier.height(16.dp)) + val density = LocalDensity.current + val rowPx = with(density) { 64.dp.toPx() } LazyColumn(Modifier.weight(1f)) { itemsIndexed(tracks, key = { i, t -> "${t.id}#$i" }) { index, track -> TrackRow( @@ -181,7 +187,29 @@ fun PlaylistPage( showAlbum = true, liked = track.id in likedIds, onToggleLike = { onToggleLike(track.id) }, - contextItems = songMenu(track) + contextItems = songMenu(track), + modifier = Modifier.pointerInput(tracks, playlist.id) { + var from: Int? = null + var dy = 0f + detectDragGesturesAfterLongPress( + onDragStart = { + from = index + dy = 0f + }, + onDrag = { change, drag -> + change.consume() + dy += drag.y + }, + onDragEnd = { + val start = from ?: return@detectDragGesturesAfterLongPress + val to = (start + (dy / rowPx).roundToInt()) + .coerceIn(0, tracks.lastIndex) + if (start != to) store.moveTrack(playlist.id, start, to) + from = null + }, + onDragCancel = { from = null } + ) + } ) } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index dc3fd8d8..e572d51e 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -150,7 +150,9 @@ fun YuriDesktopApp(session: DesktopSession) { fun playlistAddAlternate(songs: List): ContextMenuScope.() -> Unit = { val candidates = playlists - .filter { pl -> songs.any { t -> pl.trackIds.none { it in t.playlistKeys() } } } + .filter { pl -> + songs.any { t -> pl.orderedEntries().none { e -> e.matches(t) } } + } .sortedBy { it.name.lowercase() } if (candidates.isEmpty()) { item("Already in every playlist", enabled = false) {} From 7cd93cb28a80add4d628e06e21a6ace4e9052269 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Sat, 22 Aug 2026 02:15:36 +0000 Subject: [PATCH 55/69] Fix desktop playback and Navidrome resume VLC now starts with media().play instead of prepare-then-pause. Stream URLs are minted at play time so Navidrome/Jellyfin tokens are fresh after restart, and the hot/cold queue is what we persist. --- .../yuriplayer/core/player/PlayerSession.kt | 25 ++++++-- .../yuriplayer/core/source/JellyfinCatalog.kt | 17 +++-- .../yuriplayer/core/source/SubsonicCatalog.kt | 6 ++ .../desktop/DesktopPlaybackStore.kt | 26 ++++++-- .../yuri/yuriplayer/desktop/DesktopSession.kt | 25 ++++++-- .../desktop/player/VlcjPlaybackEngine.kt | 63 ++++++++++++------- 6 files changed, 120 insertions(+), 42 deletions(-) diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt index c817c776..d76bcde0 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -16,6 +16,11 @@ enum class QueueLane { HOT, COLD } class PlayerSession( private val engine: PlaybackEngine ) { + /** + * Refresh network stream URLs (Navidrome/Jellyfin tokens) right before + * the engine opens them. Identity stays on the original [Track]. + */ + var mediaFor: (Track) -> Track = { it } private val _hot = MutableStateFlow>(emptyList()) val hotQueue: StateFlow> = _hot.asStateFlow() @@ -390,7 +395,19 @@ class PlayerSession( return null } + private var advancing = false + private fun advance(fromUser: Boolean, engineAlreadyMoved: Boolean = false) { + if (advancing) return + advancing = true + try { + advanceBody(fromUser, engineAlreadyMoved) + } finally { + advancing = false + } + } + + private fun advanceBody(fromUser: Boolean, engineAlreadyMoved: Boolean) { val nextTrack = peekNextTrack() if (nextTrack == null && fromUser && _repeat.value != RepeatMode.ALL) { return @@ -416,7 +433,6 @@ class PlayerSession( return } if (engine.hasPreparedNext() && engine.playPreparedNext()) { - engine.play() warmSuccessor() return } @@ -438,8 +454,9 @@ class PlayerSession( private fun loadCurrent(startMs: Long, play: Boolean) { val track = currentTrack() ?: return _current.value = track - val next = peekNextTrack()?.takeIf { it.id != track.id } - engine.load(track.toPlaybackMedia(), next?.toPlaybackMedia(), startMs) + val playable = mediaFor(track) + val next = peekNextTrack()?.takeIf { it.id != track.id }?.let { mediaFor(it) } + engine.load(playable.toPlaybackMedia(), next?.toPlaybackMedia(), startMs) if (play) engine.play() else engine.pause() warmSuccessor() } @@ -456,7 +473,7 @@ class PlayerSession( engine.setNext(null) return } - engine.setNext(next.toPlaybackMedia()) + engine.setNext(mediaFor(next).toPlaybackMedia()) engine.warmupNext() } diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt index b51d579c..16bb633c 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/JellyfinCatalog.kt @@ -216,12 +216,7 @@ class JellyfinCatalog( param("api_key", token) } } - val stream = url(account.baseUrl) { - path("Audio", id, "stream") - param("static", true) - param("api_key", token) - item.container?.let { param("Container", it) } - } + val stream = streamUrl(account, id, item.container) return Track( id = "jellyfin:$id", uri = stream, @@ -238,6 +233,16 @@ class JellyfinCatalog( ) } + fun streamUrl(account: RemoteAccount, itemId: String, container: String? = null): String { + val token = account.accessToken.orEmpty() + return url(account.baseUrl) { + path("Audio", itemId, "stream") + param("static", true) + if (token.isNotBlank()) param("api_key", token) + container?.let { param("Container", it) } + } + } + private fun authHeader(token: String?): String { val base = "MediaBrowser Client=\"Yuri Player\", Device=\"Desktop\", DeviceId=\"$deviceId\", Version=\"1.0\"" return if (token.isNullOrBlank()) base else "$base, Token=\"$token\"" diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt index 67245527..b5e95f9a 100644 --- a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/source/SubsonicCatalog.kt @@ -187,6 +187,12 @@ class SubsonicCatalog( } } + fun streamUrl(account: RemoteAccount, songId: String): String = + streamUrl(sessionOf(account), songId) + + fun coverUrl(account: RemoteAccount, coverArtId: String?, size: Int = 600): String? = + coverUrl(sessionOf(account), coverArtId, size) + fun streamUrl(session: Session, id: String): String = restUrl(session, "stream") { param("id", id) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt index 4b3128ff..eb855be4 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt @@ -2,6 +2,7 @@ package capital.yuri.yuriplayer.desktop import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.player.PlaybackSnapshot +import capital.yuri.yuriplayer.core.player.QueueLane import capital.yuri.yuriplayer.core.player.RepeatMode import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString @@ -17,7 +18,7 @@ class DesktopPlaybackStore(configDir: String) { private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true } fun save(snap: PlaybackSnapshot) { - if (snap.queue.isEmpty()) { + if (snap.queue.isEmpty() && snap.coldQueue.isEmpty() && snap.hotQueue.isEmpty()) { clear() return } @@ -32,6 +33,11 @@ class DesktopPlaybackStore(configDir: String) { queue = snap.queue, linear = snap.linear, history = snap.history, + hotQueue = snap.hotQueue, + coldQueue = snap.coldQueue, + coldOriginal = snap.coldOriginal, + lane = snap.lane.name, + indexInLane = snap.indexInLane, savedAt = System.currentTimeMillis() ) val tmp = File(file.parentFile, "playback_state.json.tmp") @@ -47,16 +53,23 @@ class DesktopPlaybackStore(configDir: String) { if (!file.isFile) return null return runCatching { val dto = json.decodeFromString(file.readText()) - if (dto.queue.isEmpty()) return@runCatching null + if (dto.queue.isEmpty() && dto.coldQueue.isEmpty() && dto.hotQueue.isEmpty()) { + return@runCatching null + } PlaybackSnapshot( queue = dto.queue, - linear = dto.linear.ifEmpty { dto.queue }, + linear = dto.linear.ifEmpty { dto.coldOriginal.ifEmpty { dto.queue } }, index = dto.index, history = dto.history, shuffle = dto.shuffle, repeat = runCatching { RepeatMode.valueOf(dto.repeat) }.getOrDefault(RepeatMode.OFF), volume = dto.volume.coerceIn(0f, 1f), - positionMs = dto.positionMs + positionMs = dto.positionMs, + hotQueue = dto.hotQueue, + coldQueue = dto.coldQueue.ifEmpty { dto.linear.ifEmpty { dto.queue } }, + coldOriginal = dto.coldOriginal.ifEmpty { dto.linear.ifEmpty { dto.queue } }, + lane = runCatching { QueueLane.valueOf(dto.lane) }.getOrDefault(QueueLane.COLD), + indexInLane = dto.indexInLane ) }.getOrNull() } @@ -75,6 +88,11 @@ class DesktopPlaybackStore(configDir: String) { val queue: List = emptyList(), val linear: List = emptyList(), val history: List = emptyList(), + val hotQueue: List = emptyList(), + val coldQueue: List = emptyList(), + val coldOriginal: List = emptyList(), + val lane: String = "COLD", + val indexInLane: Int = 0, val savedAt: Long = 0L ) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 342daea9..45af5fff 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -157,6 +157,7 @@ class DesktopSession { scope.launch { sources.remotes.collect { refreshScanSources() } } + player.mediaFor = ::freshMedia hydrateIndex() restorePlayback() startPersist() @@ -197,21 +198,37 @@ class DesktopSession { _durationMs.value = player.durationMs() } + private fun freshMedia(track: Track): Track { + val remote = sources.remotes.value.firstOrNull { it.id == track.sourceId && it.enabled } + ?: return track + val raw = track.rawSourceId() ?: return track + return when (remote.kind) { + SourceKind.SUBSONIC -> track.copy( + uri = subsonic.streamUrl(remote, raw), + artworkUri = track.artworkUri ?: subsonic.coverUrl(remote, raw) + ) + SourceKind.JELLYFIN -> track.copy(uri = jellyfin.streamUrl(remote, raw)) + SourceKind.LOCAL -> track + } + } + private fun resolveSnap(snap: PlaybackSnapshot): PlaybackSnapshot { val lib = _tracks.value if (lib.isEmpty()) return snap val byKey = HashMap(lib.size * 2) for (t in lib) { - byKey.putIfAbsent(t.id, t) - byKey.putIfAbsent(t.catalogKey(), t) + t.indexKeys().forEach { byKey.putIfAbsent(it, t) } } fun resolve(list: List) = list.map { t -> - byKey[t.id] ?: byKey[t.catalogKey()] ?: t + t.indexKeys().firstNotNullOfOrNull { byKey[it] } ?: t } return snap.copy( queue = resolve(snap.queue), linear = resolve(snap.linear), - history = resolve(snap.history) + history = resolve(snap.history), + hotQueue = resolve(snap.hotQueue), + coldQueue = resolve(snap.coldQueue), + coldOriginal = resolve(snap.coldOriginal.ifEmpty { snap.linear }) ) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index 40a0ad37..24b778c3 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -37,7 +37,9 @@ class VlcjPlaybackEngine : PlaybackEngine { private val durationMs = AtomicLong(0) private val volumePercent = AtomicInteger(100) private val pendingStartMs = AtomicLong(-1L) + private val pendingMedia = AtomicReference(null) private val preparedNext = AtomicReference(null) + private val ignoreFinishedUntil = AtomicLong(0) val nativeError: String? override val isPlaying: StateFlow = _isPlaying.asStateFlow() @@ -99,6 +101,7 @@ class VlcjPlaybackEngine : PlaybackEngine { } override fun finished(mediaPlayer: MediaPlayer) { + if (System.currentTimeMillis() < ignoreFinishedUntil.get()) return _isPlaying.value = false listeners.forEach { it.onEnded() } } @@ -115,46 +118,57 @@ class VlcjPlaybackEngine : PlaybackEngine { } override fun load(current: PlaybackMedia, successor: PlaybackMedia?, startPositionMs: Long) { + pendingMedia.set(current) preparedNext.set(successor) pendingStartMs.set(if (startPositionMs > 0) startPositionMs else -1L) positionMs.set(startPositionMs.coerceAtLeast(0L)) + _currentUri.value = current.uri + _isPlaying.value = false + ignoreFinishedUntil.set(System.currentTimeMillis() + 750) + } + + override fun play() { onVlc { val p = player ?: run { listeners.forEach { it.onError(nativeError ?: "No audio engine", false) } return@onVlc } - val mrl = toMrl(current.uri) - System.err.println("Vlcj load $mrl") - val extra = if (startPositionMs > 0) { - arrayOf(":start-time=${startPositionMs / 1000.0}") + val media = pendingMedia.getAndSet(null) + p.audio().setMute(false) + p.audio().setVolume(volumePercent.get()) + ignoreFinishedUntil.set(System.currentTimeMillis() + 750) + if (media != null) { + val mrl = toMrl(media.uri) + System.err.println("Vlcj play $mrl") + val extra = pendingStartMs.get().takeIf { it > 0 }?.let { + arrayOf(":start-time=${it / 1000.0}") + } ?: emptyArray() + val ok = p.media().play(mrl, *(mediaOptions(media) + extra)) + if (!ok) { + listeners.forEach { it.onError("Could not open ${media.title}", recoverable = true) } + return@onVlc + } + _currentUri.value = media.uri } else { - emptyArray() + p.controls().play() } - val ok = p.media().prepare(mrl, *(mediaOptions(current) + extra)) - if (!ok) { - listeners.forEach { it.onError("Could not open ${current.title}", recoverable = true) } - return@onVlc - } - p.audio().setVolume(volumePercent.get()) - p.audio().setMute(false) - if (startPositionMs > 0) p.controls().setTime(startPositionMs) - p.controls().setPause(true) - _currentUri.value = current.uri - _isPlaying.value = false } } - override fun play() { + override fun pause() { onVlc { - player?.audio()?.setMute(false) - player?.controls()?.play() + val media = pendingMedia.get() + val p = player ?: return@onVlc + if (media != null && p.status()?.isPlaying != true) { + val mrl = toMrl(media.uri) + p.media().prepare(mrl, *mediaOptions(media)) + p.controls().setPause(true) + } else { + p.controls().setPause(true) + } } } - override fun pause() { - onVlc { player?.controls()?.setPause(true) } - } - override fun stop() { onVlc { player?.controls()?.stop() } preparedNext.set(null) @@ -193,7 +207,8 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun playPreparedNext(): Boolean { val next = preparedNext.getAndSet(null) ?: return false - load(next, null, 0L) + pendingMedia.set(next) + pendingStartMs.set(-1L) play() return true } From b5a9259ff6e412c4af71daba571bba83f59b4527 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Sat, 22 Aug 2026 02:22:32 +0000 Subject: [PATCH 56/69] Make desktop play actually start, same as Android Restore no longer prepare-pauses the engine (that hung VLC so play/skip never ran). Play and skip both loadCurrent then media().play, reminting stream URLs. Pause only pauses. --- .../yuriplayer/core/player/PlayerSession.kt | 11 +-- .../desktop/player/VlcjPlaybackEngine.kt | 98 ++++++++++--------- 2 files changed, 55 insertions(+), 54 deletions(-) diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt index d76bcde0..ce25ca80 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/PlayerSession.kt @@ -97,7 +97,11 @@ class PlayerSession( if (activeList().isNotEmpty()) loadCurrent(0L, play = true) return } - if (engine.isPlaying.value) engine.pause() else engine.play() + if (engine.isPlaying.value) { + engine.pause() + } else { + loadCurrent(engine.getPositionMs().coerceAtLeast(0L), play = true) + } } fun pause() = engine.pause() @@ -315,7 +319,6 @@ class PlayerSession( } publish() loadCurrent(snap.positionMs.coerceAtLeast(0L), play = play) - if (!play) engine.pause() } fun release() { @@ -432,10 +435,6 @@ class PlayerSession( warmSuccessor() return } - if (engine.hasPreparedNext() && engine.playPreparedNext()) { - warmSuccessor() - return - } loadCurrent(0L, play = true) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index 24b778c3..074b5d2e 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -37,8 +37,9 @@ class VlcjPlaybackEngine : PlaybackEngine { private val durationMs = AtomicLong(0) private val volumePercent = AtomicInteger(100) private val pendingStartMs = AtomicLong(-1L) - private val pendingMedia = AtomicReference(null) + private val loadedMedia = AtomicReference(null) private val preparedNext = AtomicReference(null) + private val openedMrl = AtomicReference(null) private val ignoreFinishedUntil = AtomicLong(0) val nativeError: String? @@ -51,7 +52,7 @@ class VlcjPlaybackEngine : PlaybackEngine { MediaPlayerFactory( "--no-video", "--no-metadata-network-access", - "--network-caching=1500", + "--network-caching=3000", "--file-caching=300", "--aout=any" ) @@ -118,63 +119,37 @@ class VlcjPlaybackEngine : PlaybackEngine { } override fun load(current: PlaybackMedia, successor: PlaybackMedia?, startPositionMs: Long) { - pendingMedia.set(current) + loadedMedia.set(current) preparedNext.set(successor) pendingStartMs.set(if (startPositionMs > 0) startPositionMs else -1L) positionMs.set(startPositionMs.coerceAtLeast(0L)) _currentUri.value = current.uri _isPlaying.value = false - ignoreFinishedUntil.set(System.currentTimeMillis() + 750) } override fun play() { - onVlc { - val p = player ?: run { - listeners.forEach { it.onError(nativeError ?: "No audio engine", false) } - return@onVlc - } - val media = pendingMedia.getAndSet(null) - p.audio().setMute(false) - p.audio().setVolume(volumePercent.get()) - ignoreFinishedUntil.set(System.currentTimeMillis() + 750) - if (media != null) { - val mrl = toMrl(media.uri) - System.err.println("Vlcj play $mrl") - val extra = pendingStartMs.get().takeIf { it > 0 }?.let { - arrayOf(":start-time=${it / 1000.0}") - } ?: emptyArray() - val ok = p.media().play(mrl, *(mediaOptions(media) + extra)) - if (!ok) { - listeners.forEach { it.onError("Could not open ${media.title}", recoverable = true) } - return@onVlc - } - _currentUri.value = media.uri - } else { - p.controls().play() + val media = loadedMedia.get() + if (media == null) { + onVlc { + player?.audio()?.setMute(false) + player?.controls()?.play() } + return } + start(media) } override fun pause() { - onVlc { - val media = pendingMedia.get() - val p = player ?: return@onVlc - if (media != null && p.status()?.isPlaying != true) { - val mrl = toMrl(media.uri) - p.media().prepare(mrl, *mediaOptions(media)) - p.controls().setPause(true) - } else { - p.controls().setPause(true) - } - } + onVlc { player?.controls()?.setPause(true) } } override fun stop() { - onVlc { player?.controls()?.stop() } + onVlc { silentStop() } preparedNext.set(null) _currentUri.value = null _isPlaying.value = false positionMs.set(0) + openedMrl.set(null) } override fun seekTo(positionMs: Long) { @@ -186,7 +161,7 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun getPositionMs(): Long { val pending = pendingStartMs.get() - if (pending > 0L) return pending + if (pending > 0L && !_isPlaying.value) return pending return positionMs.get() } @@ -203,15 +178,9 @@ class VlcjPlaybackEngine : PlaybackEngine { preparedNext.set(item) } - override fun hasPreparedNext(): Boolean = preparedNext.get() != null + override fun hasPreparedNext(): Boolean = false - override fun playPreparedNext(): Boolean { - val next = preparedNext.getAndSet(null) ?: return false - pendingMedia.set(next) - pendingStartMs.set(-1L) - play() - return true - } + override fun playPreparedNext(): Boolean = false override fun release() { onVlc { @@ -230,6 +199,39 @@ class VlcjPlaybackEngine : PlaybackEngine { listeners -= listener } + private fun start(media: PlaybackMedia) { + onVlc { + val p = player ?: run { + listeners.forEach { it.onError(nativeError ?: "No audio engine", false) } + return@onVlc + } + val mrl = toMrl(media.uri) + System.err.println("Vlcj play $mrl") + ignoreFinishedUntil.set(System.currentTimeMillis() + 1_200) + silentStop() + p.audio().setMute(false) + p.audio().setVolume(volumePercent.get()) + val extra = pendingStartMs.get().takeIf { it > 0 }?.let { + arrayOf(":start-time=${it / 1000.0}") + } ?: emptyArray() + val ok = p.media().play(mrl, *(mediaOptions(media) + extra)) + if (!ok) { + listeners.forEach { it.onError("Could not open ${media.title}", recoverable = true) } + return@onVlc + } + openedMrl.set(mrl) + _currentUri.value = media.uri + p.controls().play() + } + } + + private fun silentStop() { + val until = ignoreFinishedUntil.get() + ignoreFinishedUntil.set(System.currentTimeMillis() + 1_200) + runCatching { player?.controls()?.stop() } + ignoreFinishedUntil.set(maxOf(until, System.currentTimeMillis() + 400)) + } + private fun onVlc(block: () -> Unit) { if (vlc.isShutdown) return vlc.execute { From 1d0898780b389e227a2af8d79ba812e4ef19662b Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Sat, 22 Aug 2026 02:27:26 +0000 Subject: [PATCH 57/69] Make Linux MPRIS actually own the session bus Claim org.mpris.MediaPlayer2.yuriplayer on a private connection, return Variants from Properties.Get (what playerctl/GNOME expect), and grab GNOME media keys as a fallback. --- .../desktop/os/linux/MprisMediaControls.kt | 205 ++++++++++++------ 1 file changed, 139 insertions(+), 66 deletions(-) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt index e8ddbd6b..38307ad8 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt @@ -6,35 +6,70 @@ import org.freedesktop.dbus.DBusPath import org.freedesktop.dbus.annotations.DBusInterfaceName import org.freedesktop.dbus.connections.impl.DBusConnection import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder +import org.freedesktop.dbus.exceptions.DBusExecutionException import org.freedesktop.dbus.interfaces.DBusInterface import org.freedesktop.dbus.interfaces.Introspectable import org.freedesktop.dbus.interfaces.Properties +import org.freedesktop.dbus.messages.DBusSignal +import org.freedesktop.dbus.types.UInt32 import org.freedesktop.dbus.types.Variant import java.net.URLEncoder import java.nio.charset.StandardCharsets import java.util.concurrent.atomic.AtomicReference +import kotlin.math.abs /** - * org.mpris.MediaPlayer2 so `playerctl -p yuriplayer` can pause / skip / read - * metadata. Session bus only. + * org.mpris.MediaPlayer2.yuriplayer on the session bus. + * GNOME/KDE media keys and `playerctl -p yuriplayer` talk to this. */ class MprisMediaControls : OsMediaControls { private val callbacks = AtomicReference(null) private val state = AtomicReference(NowPlaying()) private var connection: DBusConnection? = null private var exported: MprisObject? = null + private var gnomeKeys: GnomeMediaKeys? = null override fun attach(callbacks: OsMediaControls.Callbacks) { this.callbacks.set(callbacks) try { - val conn = DBusConnectionBuilder.forSessionBus().build() - conn.requestBusName(BUS_NAME) + val conn = DBusConnectionBuilder.forSessionBus() + .withShared(false) + .build() val obj = MprisObject(callbacks, state) conn.exportObject(OBJECT_PATH, obj) + conn.requestBusName(BUS_NAME) connection = conn exported = obj + System.err.println("MPRIS: claimed $BUS_NAME") + grabGnomeKeys(conn, callbacks) } catch (e: Exception) { - System.err.println("MPRIS unavailable: ${e.message}") + System.err.println("MPRIS unavailable: ${e.javaClass.simpleName}: ${e.message}") + e.printStackTrace() + } + } + + private fun grabGnomeKeys(conn: DBusConnection, cb: OsMediaControls.Callbacks) { + runCatching { + conn.addSigHandler(GnomeMediaKeys.MediaPlayerKeyPressed::class.java) { sig -> + if (sig.application != APP_ID) return@addSigHandler + when (sig.key) { + "Play", "PlayPause" -> cb.onPlayPause() + "Pause" -> cb.onPause() + "Stop" -> cb.onStop() + "Next" -> cb.onNext() + "Previous" -> cb.onPrevious() + } + } + val keys = conn.getRemoteObject( + "org.gnome.SettingsDaemon.MediaKeys", + "/org/gnome/SettingsDaemon/MediaKeys", + GnomeMediaKeys::class.java + ) + keys.GrabMediaPlayerKeys(APP_ID, UInt32(0)) + gnomeKeys = keys + System.err.println("MPRIS: grabbed GNOME media keys") + }.onFailure { + System.err.println("MPRIS: GNOME media keys skipped (${it.message})") } } @@ -42,32 +77,49 @@ class MprisMediaControls : OsMediaControls { val next = NowPlaying(track, playing, positionMs, durationMs, volume.coerceIn(0f, 1f)) val prev = state.getAndSet(next) val conn = connection ?: return - if (prev.playing != next.playing || - prev.track?.id != next.track?.id || - kotlin.math.abs(prev.volume - next.volume) > 0.01f - ) { + val obj = exported ?: return + val changed = LinkedHashMap>() + if (prev.playing != next.playing || (prev.track == null) != (next.track == null)) { + changed["PlaybackStatus"] = Variant(obj.playbackStatus(next)) + } + if (prev.track?.id != next.track?.id) { + changed["Metadata"] = Variant(obj.metadata(next)) + } + if (abs(prev.volume - next.volume) > 0.01f) { + changed["Volume"] = Variant(next.volume.toDouble()) + } + if (changed.isNotEmpty()) { runCatching { conn.sendMessage( Properties.PropertiesChanged( OBJECT_PATH, PLAYER_IFACE, - exported?.properties(next) ?: emptyMap(), + changed, emptyList() ) ) } } + val jump = abs(next.positionMs - prev.positionMs) + if (jump > 2_000 && prev.track?.id == next.track?.id && next.track != null) { + runCatching { + conn.sendMessage(MprisPlayer.Seeked(OBJECT_PATH, next.positionMs * 1000)) + } + } } override fun release() { + runCatching { gnomeKeys?.ReleaseMediaPlayerKeys(APP_ID) } runCatching { connection?.unExportObject(OBJECT_PATH) } runCatching { connection?.releaseBusName(BUS_NAME) } runCatching { connection?.disconnect() } + gnomeKeys = null connection = null exported = null } companion object { + const val APP_ID = "yuriplayer" const val BUS_NAME = "org.mpris.MediaPlayer2.yuriplayer" const val OBJECT_PATH = "/org/mpris/MediaPlayer2" const val ROOT_IFACE = "org.mpris.MediaPlayer2" @@ -97,9 +149,23 @@ interface MprisPlayer : DBusInterface { fun PlayPause() fun Stop() fun Play() - fun Seek(offset: Long) - fun SetPosition(trackId: DBusPath, position: Long) - fun OpenUri(uri: String) + fun Seek(Offset: Long) + fun SetPosition(TrackId: DBusPath, Position: Long) + fun OpenUri(Uri: String) + + class Seeked(path: String, val Position: Long) : DBusSignal(path, Position) +} + +@DBusInterfaceName("org.gnome.SettingsDaemon.MediaKeys") +interface GnomeMediaKeys : DBusInterface { + fun GrabMediaPlayerKeys(application: String, time: UInt32) + fun ReleaseMediaPlayerKeys(application: String) + + class MediaPlayerKeyPressed( + path: String, + val application: String, + val key: String + ) : DBusSignal(path, application, key) } internal class MprisObject( @@ -107,6 +173,7 @@ internal class MprisObject( private val state: AtomicReference ) : MediaPlayer2, MprisPlayer, Properties, Introspectable { + override fun isRemote(): Boolean = false override fun getObjectPath(): String = MprisMediaControls.OBJECT_PATH override fun Raise() = callbacks.onRaise() @@ -117,21 +184,22 @@ internal class MprisObject( override fun PlayPause() = callbacks.onPlayPause() override fun Stop() = callbacks.onStop() override fun Play() = callbacks.onPlay() - override fun Seek(offset: Long) { + override fun Seek(Offset: Long) { val now = state.get() - callbacks.onSeek((now.positionMs + offset / 1000).coerceAtLeast(0)) + callbacks.onSeek((now.positionMs + Offset / 1000).coerceAtLeast(0)) } - override fun SetPosition(trackId: DBusPath, position: Long) { - callbacks.onSeek((position / 1000).coerceAtLeast(0)) + override fun SetPosition(TrackId: DBusPath, Position: Long) { + callbacks.onSeek((Position / 1000).coerceAtLeast(0)) } - override fun OpenUri(uri: String) {} + override fun OpenUri(Uri: String) {} override fun Get(interface_name: String, property_name: String): A { val variant = propertiesFor(interface_name, state.get())[property_name] + ?: throw DBusExecutionException("Unknown property $property_name") @Suppress("UNCHECKED_CAST") - return (variant?.value ?: "") as A + return variant as A } override fun GetAll(interface_name: String): Map> = @@ -149,8 +217,35 @@ internal class MprisObject( override fun Introspect(): String = INTROSPECT_XML - fun properties(now: NowPlaying): Map> = - propertiesFor(MprisMediaControls.PLAYER_IFACE, now) + fun playbackStatus(now: NowPlaying): String = when { + now.playing -> "Playing" + now.track != null -> "Paused" + else -> "Stopped" + } + + fun metadata(now: NowPlaying): Map> { + val track = now.track ?: return mapOf( + "mpris:trackid" to Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack")) + ) + val safe = URLEncoder.encode(track.id.takeLast(48), StandardCharsets.UTF_8) + .replace("%", "_") + .replace("+", "_") + .replace(".", "_") + val id = "/org/mpris/MediaPlayer2/Track/$safe" + val map = linkedMapOf>( + "mpris:trackid" to Variant(DBusPath(id)), + "xesam:title" to Variant(track.displayTitle), + "xesam:artist" to Variant(listOf(track.displayArtist)), + "xesam:album" to Variant(track.displayAlbum) + ) + if (now.durationMs > 0) { + map["mpris:length"] = Variant(now.durationMs * 1000L) + } + track.artworkUri?.takeIf { it.startsWith("file:") || it.startsWith("http") }?.let { + map["mpris:artUrl"] = Variant(it) + } + return map + } private fun propertiesFor(iface: String, now: NowPlaying): Map> = when (iface) { @@ -169,51 +264,23 @@ internal class MprisObject( else -> emptyMap() } - private fun playerProps(now: NowPlaying): Map> { - val status = when { - now.playing -> "Playing" - now.track != null -> "Paused" - else -> "Stopped" - } - return mapOf( - "PlaybackStatus" to Variant(status), - "LoopStatus" to Variant("None"), - "Rate" to Variant(1.0), - "Shuffle" to Variant(false), - "Metadata" to Variant(metadata(now)), - "Volume" to Variant(now.volume.toDouble()), - "Position" to Variant(now.positionMs * 1000), - "MinimumRate" to Variant(1.0), - "MaximumRate" to Variant(1.0), - "CanGoNext" to Variant(true), - "CanGoPrevious" to Variant(true), - "CanPlay" to Variant(true), - "CanPause" to Variant(true), - "CanSeek" to Variant(true), - "CanControl" to Variant(true) - ) - } - - private fun metadata(now: NowPlaying): Map> { - val track = now.track ?: return mapOf( - "mpris:trackid" to Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack")) - ) - val id = "/org/mpris/MediaPlayer2/Track/" + - URLEncoder.encode(track.id.takeLast(48), StandardCharsets.UTF_8) - .replace("%", "_") - .replace("+", "_") - val map = linkedMapOf>( - "mpris:trackid" to Variant(DBusPath(id)), - "xesam:title" to Variant(track.displayTitle), - "xesam:artist" to Variant(listOf(track.displayArtist)), - "xesam:album" to Variant(track.displayAlbum) - ) - if (now.durationMs > 0) { - map["mpris:length"] = Variant(now.durationMs * 1000) - } - track.artworkUri?.let { map["mpris:artUrl"] = Variant(it) } - return map - } + private fun playerProps(now: NowPlaying): Map> = mapOf( + "PlaybackStatus" to Variant(playbackStatus(now)), + "LoopStatus" to Variant("None"), + "Rate" to Variant(1.0), + "Shuffle" to Variant(false), + "Metadata" to Variant(metadata(now)), + "Volume" to Variant(now.volume.toDouble()), + "Position" to Variant(now.positionMs * 1000L), + "MinimumRate" to Variant(1.0), + "MaximumRate" to Variant(1.0), + "CanGoNext" to Variant(true), + "CanGoPrevious" to Variant(true), + "CanPlay" to Variant(true), + "CanPause" to Variant(true), + "CanSeek" to Variant(true), + "CanControl" to Variant(true) + ) companion object { private val INTROSPECT_XML = """ @@ -238,6 +305,11 @@ internal class MprisObject( + + + + + @@ -263,6 +335,7 @@ internal class MprisObject( + From 026209e94944ff6f56a8f74fe1ef6b4b97b448ab Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Sat, 22 Aug 2026 02:31:19 +0000 Subject: [PATCH 58/69] Register Yuri as a Linux session player Installs yuriplayer.desktop + hicolor icons, sets WM_CLASS, and announces MPRIS Identity so GNOME/KDE media indicators can list us. --- desktop/build.gradle.kts | 1 + .../yuri/yuriplayer/desktop/DesktopSession.kt | 7 ++ .../capital/yuri/yuriplayer/desktop/Main.kt | 7 ++ .../os/linux/LinuxDesktopIntegration.kt | 94 +++++++++++++++++++ .../desktop/os/linux/MprisMediaControls.kt | 35 +++++++ gradle/libs.versions.toml | 2 + 6 files changed, 146 insertions(+) create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/LinuxDesktopIntegration.kt diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 0c2613e7..89842dbd 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(libs.vlcj) implementation(libs.dbus.java.core) implementation(libs.dbus.java.transport.native.unix) + implementation(libs.junixsocket.core) implementation(libs.jna) implementation(libs.jna.platform) implementation(libs.coil.compose) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 45af5fff..6f51bdd1 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -160,6 +160,13 @@ class DesktopSession { player.mediaFor = ::freshMedia hydrateIndex() restorePlayback() + media.update( + track = player.current.value, + playing = player.isPlaying.value, + positionMs = player.positionMs(), + durationMs = player.durationMs(), + volume = player.volume.value + ) startPersist() } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt index 34e4ed14..7dc564cc 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt @@ -15,7 +15,10 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import capital.yuri.yuriplayer.components.theme.isDark +import capital.yuri.yuriplayer.core.platform.HostOs import capital.yuri.yuriplayer.core.platform.appDirectories +import capital.yuri.yuriplayer.core.platform.hostOs +import capital.yuri.yuriplayer.desktop.os.linux.LinuxDesktopIntegration import capital.yuri.yuriplayer.desktop.player.LibVlcBootstrap import capital.yuri.yuriplayer.desktop.ui.YuriDesktopApp import coil3.ImageLoader @@ -28,6 +31,7 @@ import okio.Path.Companion.toOkioPath import java.io.File fun main() { + if (hostOs() == HostOs.LINUX) LinuxDesktopIntegration.install() LibVlcBootstrap.install() application { val dirs = appDirectories() @@ -83,6 +87,9 @@ fun main() { } ) { window.background = java.awt.Color(0x12, 0x10, 0x18) + if (hostOs() == HostOs.LINUX) { + window.iconImage = LinuxDesktopIntegration.windowIcon() + } session.onRaise = { window.isMinimized = false window.toFront() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/LinuxDesktopIntegration.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/LinuxDesktopIntegration.kt new file mode 100644 index 00000000..e1e2b78a --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/LinuxDesktopIntegration.kt @@ -0,0 +1,94 @@ +package capital.yuri.yuriplayer.desktop.os.linux + +import java.awt.Color +import java.awt.Font +import java.awt.RenderingHints +import java.awt.image.BufferedImage +import java.io.File +import javax.imageio.ImageIO + +/** + * Registers Yuri as a session application so GNOME/KDE media indicators + * can resolve [DesktopEntry]=yuriplayer to a name + icon. + */ +object LinuxDesktopIntegration { + const val APP_ID = "yuriplayer" + const val DISPLAY_NAME = "Yuri Player" + + fun install() { + System.setProperty("java.awt.wmclass", APP_ID) + runCatching { setX11Class() } + val home = File(System.getProperty("user.home")) + val apps = File(home, ".local/share/applications").also { it.mkdirs() } + val icon256 = File(home, ".local/share/icons/hicolor/256x256/apps").also { it.mkdirs() } + val icon48 = File(home, ".local/share/icons/hicolor/48x48/apps").also { it.mkdirs() } + val png256 = File(icon256, "$APP_ID.png") + val png48 = File(icon48, "$APP_ID.png") + if (!png256.isFile) ImageIO.write(renderIcon(256), "png", png256) + if (!png48.isFile) ImageIO.write(renderIcon(48), "png", png48) + File(apps, "$APP_ID.desktop").writeText(desktopFile()) + runCatching { + ProcessBuilder("update-desktop-database", apps.absolutePath) + .redirectErrorStream(true).start() + } + runCatching { + ProcessBuilder( + "gtk-update-icon-cache", "-f", "-t", + File(home, ".local/share/icons/hicolor").absolutePath + ).redirectErrorStream(true).start() + } + } + + fun windowIcon(): BufferedImage = renderIcon(128) + + private fun desktopFile(): String { + val exec = ProcessHandle.current().info().command().orElse("yuri-player") + return """ + [Desktop Entry] + Type=Application + Version=1.1 + Name=$DISPLAY_NAME + GenericName=Music Player + Comment=Play your library + Exec=$exec + Icon=$APP_ID + Terminal=false + StartupNotify=true + StartupWMClass=$APP_ID + Categories=AudioVideo;Audio;Player;Music; + MimeType=audio/mpeg;audio/flac;audio/ogg;audio/mp4;audio/x-wav;audio/x-vorbis+ogg; + Keywords=music;player;library;jellyfin;navidrome; + DBusActivatable=false + """.trimIndent() + "\n" + } + + private fun setX11Class() { + val toolkit = Class.forName("sun.awt.X11.XToolkit") + val field = toolkit.getDeclaredField("awtAppClassName") + field.isAccessible = true + field.set(null, APP_ID) + } + + private fun renderIcon(size: Int): BufferedImage { + val img = BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB) + val g = img.createGraphics() + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) + g.color = Color(0x1A, 0x12, 0x24) + g.fillRoundRect(0, 0, size, size, size / 4, size / 4) + g.color = Color(0xC4, 0x58, 0xDC) + val pad = size / 6 + g.fillOval(pad, pad, size - pad * 2, size - pad * 2) + g.color = Color(0x1A, 0x12, 0x24) + val inner = size / 3 + g.fillOval((size - inner) / 2, (size - inner) / 2, inner, inner) + g.color = Color.WHITE + g.font = Font(Font.SANS_SERIF, Font.BOLD, (size * 0.42f).toInt().coerceAtLeast(10)) + val y = g.fontMetrics + val text = "Y" + val tx = (size - y.stringWidth(text)) / 2 + val ty = (size - y.height) / 2 + y.ascent + g.drawString(text, tx, ty) + g.dispose() + return img + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt index 38307ad8..de75be35 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt @@ -41,6 +41,7 @@ class MprisMediaControls : OsMediaControls { connection = conn exported = obj System.err.println("MPRIS: claimed $BUS_NAME") + announce(conn, obj) grabGnomeKeys(conn, callbacks) } catch (e: Exception) { System.err.println("MPRIS unavailable: ${e.javaClass.simpleName}: ${e.message}") @@ -48,6 +49,40 @@ class MprisMediaControls : OsMediaControls { } } + private fun announce(conn: DBusConnection, obj: MprisObject) { + val now = state.get() + runCatching { + conn.sendMessage( + Properties.PropertiesChanged( + OBJECT_PATH, + ROOT_IFACE, + mapOf( + "Identity" to Variant("Yuri Player"), + "DesktopEntry" to Variant(APP_ID), + "CanRaise" to Variant(true) + ), + emptyList() + ) + ) + conn.sendMessage( + Properties.PropertiesChanged( + OBJECT_PATH, + PLAYER_IFACE, + mapOf( + "PlaybackStatus" to Variant(obj.playbackStatus(now)), + "Metadata" to Variant(obj.metadata(now)), + "CanControl" to Variant(true), + "CanPlay" to Variant(true), + "CanPause" to Variant(true), + "CanGoNext" to Variant(true), + "CanGoPrevious" to Variant(true) + ), + emptyList() + ) + ) + } + } + private fun grabGnomeKeys(conn: DBusConnection, cb: OsMediaControls.Callbacks) { runCatching { conn.addSigHandler(GnomeMediaKeys.MediaPlayerKeyPressed::class.java) { sig -> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5a3bab61..5aa73681 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,6 +30,7 @@ robolectric = "4.16" composeMultiplatform = "1.8.2" vlcj = "4.10.1" dbusJava = "5.1.1" +junixsocket = "2.10.1" jna = "5.17.0" [libraries] @@ -81,6 +82,7 @@ jaudiotagger = { group = "com.github.goxr3plus", name = "jaudiotagger", version. vlcj = { group = "uk.co.caprica", name = "vlcj", version.ref = "vlcj" } dbus-java-core = { group = "com.github.hypfvieh", name = "dbus-java-core", version.ref = "dbusJava" } dbus-java-transport-native-unix = { group = "com.github.hypfvieh", name = "dbus-java-transport-native-unixsocket", version.ref = "dbusJava" } +junixsocket-core = { group = "com.kohlschutter.junixsocket", name = "junixsocket-core", version.ref = "junixsocket" } jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" } jna-platform = { group = "net.java.dev.jna", name = "jna-platform", version.ref = "jna" } From f994c6c7490700cbe5804a09f23cb73e86a7a31a Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Sat, 22 Aug 2026 02:35:35 +0000 Subject: [PATCH 59/69] Force HTTP/1.1 for Navidrome streams and add KMP logging VLC's HTTP/2 stack was dying with Protocol error and poisoning every later play. Recycle the player on error. yuriLog { } is the common DSL: logcat on Android, slf4j/logback on desktop (Darwin os_log later). Tokens are redacted. --- core/build.gradle.kts | 1 + .../yuriplayer/core/log/YuriLog.android.kt | 16 ++ .../yuri/yuriplayer/core/log/YuriLog.kt | 44 +++++ .../yuri/yuriplayer/core/log/YuriLog.jvm.kt | 16 ++ .../yuri/yuriplayer/core/log/YuriLogTest.kt | 16 ++ desktop/build.gradle.kts | 1 + .../capital/yuri/yuriplayer/desktop/Main.kt | 2 + .../yuri/yuriplayer/desktop/PlaylistLog.kt | 28 +-- .../desktop/player/VlcjPlaybackEngine.kt | 169 +++++++++++------- desktop/src/main/resources/logback.xml | 31 ++++ gradle/libs.versions.toml | 4 + 11 files changed, 253 insertions(+), 75 deletions(-) create mode 100644 core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.android.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.jvm.kt create mode 100644 core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/log/YuriLogTest.kt create mode 100644 desktop/src/main/resources/logback.xml diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 5f537df8..5b6a14c4 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -32,6 +32,7 @@ kotlin { jvmMain.dependencies { implementation(libs.jaudiotagger) implementation(libs.ktor.client.cio) + implementation(libs.slf4j.api) } jvmTest.dependencies { implementation(kotlin("test")) diff --git a/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.android.kt b/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.android.kt new file mode 100644 index 00000000..a452ecad --- /dev/null +++ b/core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.android.kt @@ -0,0 +1,16 @@ +package capital.yuri.yuriplayer.core.log + +import android.util.Log + +actual fun platformLog(level: LogLevel, tag: String, message: String, throwable: Throwable?) { + val name = "YuriPlayer.$tag" + when (level) { + LogLevel.TRACE -> if (throwable != null) Log.v(name, message, throwable) else Log.v(name, message) + LogLevel.DEBUG -> if (throwable != null) Log.d(name, message, throwable) else Log.d(name, message) + LogLevel.INFO -> if (throwable != null) Log.i(name, message, throwable) else Log.i(name, message) + LogLevel.WARN -> if (throwable != null) Log.w(name, message, throwable) else Log.w(name, message) + LogLevel.ERROR -> if (throwable != null) Log.e(name, message, throwable) else Log.e(name, message) + } +} + +actual fun platformLogEnabled(level: LogLevel): Boolean = true diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.kt new file mode 100644 index 00000000..eac9abc9 --- /dev/null +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.kt @@ -0,0 +1,44 @@ +package capital.yuri.yuriplayer.core.log + +/** + * Kotlin DSL over platform logs. + * + * Desktop → SLF4J/Logback, Android → logcat, Darwin (later) → os_log. + * + * ``` + * val log = yuriLog("Vlcj") + * log.i { "play ${redactUrl(mrl)}" } + * log.e(t) { "stream failed" } + * + * yuriLog("Index") { + * d { "merge +$n" } + * } + * ``` + */ +enum class LogLevel { TRACE, DEBUG, INFO, WARN, ERROR } + +class YuriLogger(val tag: String) { + inline fun t(t: Throwable? = null, msg: () -> String) = emit(LogLevel.TRACE, t, msg) + inline fun d(t: Throwable? = null, msg: () -> String) = emit(LogLevel.DEBUG, t, msg) + inline fun i(t: Throwable? = null, msg: () -> String) = emit(LogLevel.INFO, t, msg) + inline fun w(t: Throwable? = null, msg: () -> String) = emit(LogLevel.WARN, t, msg) + inline fun e(t: Throwable? = null, msg: () -> String) = emit(LogLevel.ERROR, t, msg) + + inline fun emit(level: LogLevel, t: Throwable? = null, msg: () -> String) { + if (!platformLogEnabled(level)) return + platformLog(level, tag, redactSecrets(msg()), t) + } +} + +fun yuriLog(tag: String): YuriLogger = YuriLogger(tag) + +inline fun yuriLog(tag: String, block: YuriLogger.() -> Unit) = YuriLogger(tag).block() + +expect fun platformLog(level: LogLevel, tag: String, message: String, throwable: Throwable?) + +expect fun platformLogEnabled(level: LogLevel): Boolean + +fun redactSecrets(text: String): String = + text + .replace(Regex("([?&](?:t|s|api_key|ApiKey|access_token|password|pw|secret)=)[^&\\s]+"), "$1***") + .replace(Regex("(Authorization:\\s*)\\S+", RegexOption.IGNORE_CASE), "$1***") diff --git a/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.jvm.kt b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.jvm.kt new file mode 100644 index 00000000..781d0c1d --- /dev/null +++ b/core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.jvm.kt @@ -0,0 +1,16 @@ +package capital.yuri.yuriplayer.core.log + +import org.slf4j.LoggerFactory + +actual fun platformLog(level: LogLevel, tag: String, message: String, throwable: Throwable?) { + val log = LoggerFactory.getLogger("YuriPlayer.$tag") + when (level) { + LogLevel.TRACE -> if (throwable != null) log.trace(message, throwable) else log.trace(message) + LogLevel.DEBUG -> if (throwable != null) log.debug(message, throwable) else log.debug(message) + LogLevel.INFO -> if (throwable != null) log.info(message, throwable) else log.info(message) + LogLevel.WARN -> if (throwable != null) log.warn(message, throwable) else log.warn(message) + LogLevel.ERROR -> if (throwable != null) log.error(message, throwable) else log.error(message) + } +} + +actual fun platformLogEnabled(level: LogLevel): Boolean = true diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/log/YuriLogTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/log/YuriLogTest.kt new file mode 100644 index 00000000..ac360e29 --- /dev/null +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/log/YuriLogTest.kt @@ -0,0 +1,16 @@ +package capital.yuri.yuriplayer.core.log + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class YuriLogTest { + @Test + fun redactsStreamTokens() { + val raw = "https://navidrome.example/rest/stream.view?u=lizzy&t=deadbeef&s=salt&id=abc" + val out = redactSecrets(raw) + assertFalse("deadbeef" in out) + assertTrue("t=***" in out) + assertTrue("id=abc" in out) + } +} diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 89842dbd..fb87e2d2 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { implementation(libs.junixsocket.core) implementation(libs.jna) implementation(libs.jna.platform) + implementation(libs.logback.classic) implementation(libs.coil.compose) implementation(libs.coil.network.ktor3) implementation(libs.ktor.client.cio) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt index 7dc564cc..84560c9b 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt @@ -32,6 +32,8 @@ import java.io.File fun main() { if (hostOs() == HostOs.LINUX) LinuxDesktopIntegration.install() + val dirs = appDirectories() + System.setProperty("LOG_DIR", dirs.cacheDir) LibVlcBootstrap.install() application { val dirs = appDirectories() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt index 44d2e189..8314f726 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt @@ -1,31 +1,31 @@ package capital.yuri.yuriplayer.desktop import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.log.yuriLog internal object PlaylistLog { - fun add(playlist: String, track: Track) { - log( - "add '$playlist' title='${track.displayTitle}' id=${track.id} " + + private val playlist = yuriLog("Playlist") + private val index = yuriLog("Index") + + fun add(name: String, track: Track) { + playlist.d { + "add '$name' title='${track.displayTitle}' id=${track.id} " + "source=${track.sourceId} catalog=${track.catalogKey()} loose=${track.looseKey()}" - ) + } } fun resolve( - playlist: String, + playlistName: String, trackIds: List, snapshots: List, out: List, missed: List ) { - log( - "resolve '$playlist' ids=${trackIds.size} snaps=${snapshots.size} " + + playlist.w { + "resolve '$playlistName' ids=${trackIds.size} snaps=${snapshots.size} " + "out=${out.size} missed=$missed titles=${out.map { it.displayTitle }}" - ) + } } - fun index(msg: String) = log(msg, tag = "YuriPlayer.Index") - - private fun log(msg: String, tag: String = "YuriPlayer.Playlist") { - System.err.println("$tag $msg") - } -} + fun index(msg: String) = index.d { msg } +} \ No newline at end of file diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index 074b5d2e..fc48ad14 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -1,5 +1,7 @@ package capital.yuri.yuriplayer.desktop.player +import capital.yuri.yuriplayer.core.log.redactSecrets +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.core.player.PlaybackEngine import capital.yuri.yuriplayer.core.player.PlaybackMedia import kotlinx.coroutines.flow.MutableStateFlow @@ -13,23 +15,24 @@ import java.io.File import java.net.URI import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicReference /** * LibVLC via vlcj. Same job as Android's VlcPlaybackEngine: decode + output. - * LWJGL/OpenAL is a mixer, not a codec — we are not going there. * - * All native calls run on one worker so the Compose/AWT thread never blocks - * inside libvlc (start() can deadlock the EDT). + * HTTPS streams (Navidrome/Jellyfin) must use HTTP/1.1 — VLC's h2 stack + * dies with "peer stream 1 error: Protocol error" and poisons later plays. */ class VlcjPlaybackEngine : PlaybackEngine { + private val log = yuriLog("Vlcj") private val vlc = Executors.newSingleThreadExecutor { r -> Thread(r, "yuri-vlc").apply { isDaemon = true } } private val factory: MediaPlayerFactory? - private val player: MediaPlayer? + private var player: MediaPlayer? private val listeners = CopyOnWriteArrayList() private val _isPlaying = MutableStateFlow(false) private val _currentUri = MutableStateFlow(null) @@ -41,6 +44,7 @@ class VlcjPlaybackEngine : PlaybackEngine { private val preparedNext = AtomicReference(null) private val openedMrl = AtomicReference(null) private val ignoreFinishedUntil = AtomicLong(0) + private val recreating = AtomicBoolean(false) val nativeError: String? override val isPlaying: StateFlow = _isPlaying.asStateFlow() @@ -49,13 +53,7 @@ class VlcjPlaybackEngine : PlaybackEngine { init { val discovered = NativeDiscovery().discover() val created = runCatching { - MediaPlayerFactory( - "--no-video", - "--no-metadata-network-access", - "--network-caching=3000", - "--file-caching=300", - "--aout=any" - ) + MediaPlayerFactory(*FACTORY_ARGS) } factory = created.getOrNull() player = factory?.mediaPlayers()?.newMediaPlayer() @@ -68,50 +66,11 @@ class VlcjPlaybackEngine : PlaybackEngine { else -> null } if (nativeError != null) { - System.err.println("VlcjPlaybackEngine: $nativeError") + log.e { nativeError } + } else { + log.i { "libvlc ready http1.1" } } - player?.events()?.addMediaPlayerEventListener(object : MediaPlayerEventAdapter() { - override fun playing(mediaPlayer: MediaPlayer) { - val pending = pendingStartMs.getAndSet(-1L) - if (pending > 0L) { - mediaPlayer.controls().setTime(pending) - positionMs.set(pending) - } else { - positionMs.set(mediaPlayer.status().time().coerceAtLeast(0L)) - } - _isPlaying.value = true - listeners.forEach { it.onIsPlayingChanged(true) } - } - - override fun paused(mediaPlayer: MediaPlayer) { - _isPlaying.value = false - listeners.forEach { it.onIsPlayingChanged(false) } - } - - override fun stopped(mediaPlayer: MediaPlayer) { - _isPlaying.value = false - listeners.forEach { it.onIsPlayingChanged(false) } - } - - override fun timeChanged(mediaPlayer: MediaPlayer, newTime: Long) { - positionMs.set(newTime) - } - - override fun lengthChanged(mediaPlayer: MediaPlayer, newLength: Long) { - durationMs.set(newLength) - } - - override fun finished(mediaPlayer: MediaPlayer) { - if (System.currentTimeMillis() < ignoreFinishedUntil.get()) return - _isPlaying.value = false - listeners.forEach { it.onEnded() } - } - - override fun error(mediaPlayer: MediaPlayer) { - _isPlaying.value = false - listeners.forEach { it.onError("VLC failed to play", recoverable = true) } - } - }) + bindEvents(player) onVlc { player?.audio()?.setVolume(volumePercent.get()) player?.audio()?.setMute(false) @@ -206,15 +165,22 @@ class VlcjPlaybackEngine : PlaybackEngine { return@onVlc } val mrl = toMrl(media.uri) - System.err.println("Vlcj play $mrl") + log.i { "play ${redactSecrets(mrl)}" } ignoreFinishedUntil.set(System.currentTimeMillis() + 1_200) silentStop() p.audio().setMute(false) p.audio().setVolume(volumePercent.get()) - val extra = pendingStartMs.get().takeIf { it > 0 }?.let { - arrayOf(":start-time=${it / 1000.0}") - } ?: emptyArray() - val ok = p.media().play(mrl, *(mediaOptions(media) + extra)) + val extra = buildList { + addAll(mediaOptions(media)) + pendingStartMs.get().takeIf { it > 0 }?.let { + add(":start-time=${it / 1000.0}") + } + if (mrl.startsWith("http")) { + add(":http-reconnect") + add(":http-user-agent=YuriPlayer/1.0") + } + }.toTypedArray() + val ok = p.media().play(mrl, *extra) if (!ok) { listeners.forEach { it.onError("Could not open ${media.title}", recoverable = true) } return@onVlc @@ -238,8 +204,7 @@ class VlcjPlaybackEngine : PlaybackEngine { try { block() } catch (t: Throwable) { - System.err.println("Vlcj: ${t.message}") - t.printStackTrace() + log.e(t) { "native call failed" } listeners.forEach { it.onError(t.message ?: "VLC error", true) } } } @@ -257,8 +222,90 @@ class VlcjPlaybackEngine : PlaybackEngine { } } + private fun bindEvents(target: MediaPlayer?) { + target?.events()?.addMediaPlayerEventListener(object : MediaPlayerEventAdapter() { + override fun playing(mediaPlayer: MediaPlayer) { + val pending = pendingStartMs.getAndSet(-1L) + if (pending > 0L) { + mediaPlayer.controls().setTime(pending) + positionMs.set(pending) + } else { + positionMs.set(mediaPlayer.status().time().coerceAtLeast(0L)) + } + _isPlaying.value = true + log.d { "playing pos=${positionMs.get()} dur=${durationMs.get()}" } + listeners.forEach { it.onIsPlayingChanged(true) } + } + + override fun paused(mediaPlayer: MediaPlayer) { + _isPlaying.value = false + listeners.forEach { it.onIsPlayingChanged(false) } + } + + override fun stopped(mediaPlayer: MediaPlayer) { + _isPlaying.value = false + listeners.forEach { it.onIsPlayingChanged(false) } + } + + override fun timeChanged(mediaPlayer: MediaPlayer, newTime: Long) { + positionMs.set(newTime) + } + + override fun lengthChanged(mediaPlayer: MediaPlayer, newLength: Long) { + durationMs.set(newLength) + } + + override fun finished(mediaPlayer: MediaPlayer) { + if (System.currentTimeMillis() < ignoreFinishedUntil.get()) return + _isPlaying.value = false + log.i { "finished ${redactSecrets(_currentUri.value.orEmpty())}" } + listeners.forEach { it.onEnded() } + } + + override fun error(mediaPlayer: MediaPlayer) { + _isPlaying.value = false + log.e { "error ${redactSecrets(_currentUri.value.orEmpty())}" } + recyclePlayer() + listeners.forEach { it.onError("VLC failed to play", recoverable = true) } + } + }) + } + + private fun recyclePlayer() { + if (!recreating.compareAndSet(false, true)) return + onVlc { + try { + val old = player + runCatching { old?.controls()?.stop() } + runCatching { old?.release() } + val next = factory?.mediaPlayers()?.newMediaPlayer() + player = next + bindEvents(next) + next?.audio()?.setVolume(volumePercent.get()) + next?.audio()?.setMute(false) + openedMrl.set(null) + log.w { "recycled media player after stream error" } + } finally { + recreating.set(false) + } + } + } + private fun mediaOptions(media: PlaybackMedia): Array { if (media.headers.isEmpty()) return emptyArray() return media.headers.map { (k, v) -> ":http-header=$k: $v" }.toTypedArray() } + + companion object { + private val FACTORY_ARGS = arrayOf( + "--no-video", + "--no-metadata-network-access", + "--network-caching=4000", + "--file-caching=300", + "--http-reconnect", + "--no-http-h2", + "--http-user-agent=YuriPlayer/1.0", + "--aout=any" + ) + } } diff --git a/desktop/src/main/resources/logback.xml b/desktop/src/main/resources/logback.xml new file mode 100644 index 00000000..915178f1 --- /dev/null +++ b/desktop/src/main/resources/logback.xml @@ -0,0 +1,31 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{28} — %msg%n + + + + + ${LOG_DIR}/yuriplayer.log + + ${LOG_DIR}/yuriplayer.%d{yyyy-MM-dd}.%i.log + 8MB + 7 + 40MB + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger — %msg%n + + + + + + + + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5aa73681..6d77ce71 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,8 @@ vlcj = "4.10.1" dbusJava = "5.1.1" junixsocket = "2.10.1" jna = "5.17.0" +slf4j = "2.0.17" +logback = "1.5.18" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -85,6 +87,8 @@ dbus-java-transport-native-unix = { group = "com.github.hypfvieh", name = "dbus- junixsocket-core = { group = "com.kohlschutter.junixsocket", name = "junixsocket-core", version.ref = "junixsocket" } jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" } jna-platform = { group = "net.java.dev.jna", name = "jna-platform", version.ref = "jna" } +slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } +logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } From 499c229781c95557ed6339d42041b693dfba45cb Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Sat, 22 Aug 2026 02:50:39 +0000 Subject: [PATCH 60/69] Convert every Log call to yuriLog Replace android.util.Log and leftover printStackTrace with the KMP yuriLog DSL. AlbumLog and PlaylistLog stay as domain helpers on top of the same facade. Tags are short names; platforms prefix YuriPlayer. --- .../capital/yuri/yuriplayer/YuriPlayerApp.kt | 4 +- .../activities/ui/AlbumDetailScreen.kt | 5 +- .../yuri/yuriplayer/data/AlbumArtCache.kt | 26 +++--- .../yuri/yuriplayer/data/AlbumArtResolver.kt | 44 +++++----- .../capital/yuri/yuriplayer/data/AlbumLog.kt | 47 +++++----- .../yuri/yuriplayer/data/AlbumMerge.kt | 7 +- .../yuri/yuriplayer/data/AlbumResolve.kt | 2 - .../yuri/yuriplayer/data/AlbumTrackExpand.kt | 17 ++-- .../yuri/yuriplayer/data/ArtistAlbumsLight.kt | 11 +-- .../yuri/yuriplayer/data/CatalogRepository.kt | 21 +++-- .../yuriplayer/data/ExploreSearchService.kt | 80 ++++++++--------- .../yuri/yuriplayer/data/LibraryCache.kt | 8 +- .../yuri/yuriplayer/data/LibraryIndex.kt | 22 ++--- .../yuriplayer/data/LibraryScanNotifier.kt | 8 +- .../yuriplayer/data/LibraryScanService.kt | 16 ++-- .../yuriplayer/data/LibrarySyncScheduler.kt | 16 ++-- .../yuriplayer/data/MetadataEditService.kt | 35 ++++---- .../data/MetadataEnrichmentService.kt | 25 +++--- .../yuri/yuriplayer/data/MusicRepository.kt | 40 ++++----- .../data/organize/LibraryOrganizeService.kt | 10 +-- .../data/source/AudioDbArtistImageSource.kt | 8 +- .../data/source/BandsintownClient.kt | 20 ++--- .../data/source/DeezerArtistImageSource.kt | 8 +- .../yuriplayer/data/source/DiscogsClient.kt | 12 +-- .../data/source/JellyfinArtistImageSource.kt | 8 +- .../yuriplayer/data/source/JellyfinClient.kt | 42 ++++----- .../data/source/LibraryFaviconStore.kt | 6 +- .../MusicBrainzArtistProfileProvider.kt | 6 +- .../data/source/MusicBrainzClient.kt | 20 ++--- .../data/source/RemotePlaylistService.kt | 8 +- .../data/source/SubsonicArtistImageSource.kt | 6 +- .../yuriplayer/data/source/SubsonicClient.kt | 24 +++--- .../data/source/WikidataArtistImageSource.kt | 10 +-- .../data/source/WikipediaArtistImageSource.kt | 10 +-- .../yuriplayer/data/theme/ThemeService.kt | 6 +- .../yuri/yuriplayer/media/FfmpegService.kt | 14 +-- .../yuri/yuriplayer/network/HttpModule.kt | 3 +- .../yuri/yuriplayer/player/ArtistRadio.kt | 21 ++--- .../yuri/yuriplayer/player/MusicService.kt | 85 ++++++++----------- .../player/MusicServiceEngineHooks.kt | 6 +- .../player/MusicServiceLocalEngine.kt | 15 ++-- .../yuriplayer/player/PlaybackHistoryStore.kt | 12 +-- .../yuriplayer/player/PlaybackStateStore.kt | 8 +- .../yuriplayer/player/PlayerController.kt | 8 +- .../yuriplayer/player/QueueEventBridge.kt | 13 ++- .../yuri/yuriplayer/player/QueueManager.kt | 28 +++--- .../yuri/yuriplayer/player/SourceColdSync.kt | 6 +- .../yuriplayer/player/engine/AudioPipeline.kt | 7 +- .../player/engine/Media3PlaybackEngine.kt | 13 ++- .../player/engine/PlaybackEngineFactory.kt | 11 ++- .../player/engine/StreamPrefetcher.kt | 16 ++-- .../player/engine/VlcPlaybackEngine.kt | 43 +++++----- .../yuriplayer/player/radio/RadioEngine.kt | 52 +++++------- .../player/radio/RadioPlaybackAlgorithm.kt | 8 +- .../player/radio/ReleasePoolAlgorithm.kt | 12 +-- .../yuri/yuriplayer/core/log/YuriLog.kt | 15 +++- .../yuri/yuriplayer/core/log/YuriLogTest.kt | 10 +++ .../desktop/DesktopPlaylistStore.kt | 7 +- .../desktop/os/linux/MprisMediaControls.kt | 12 +-- .../desktop/os/mac/MacNowPlayingControls.kt | 5 +- .../desktop/player/LibVlcBootstrap.kt | 5 +- 61 files changed, 512 insertions(+), 561 deletions(-) diff --git a/app/src/main/java/capital/yuri/yuriplayer/YuriPlayerApp.kt b/app/src/main/java/capital/yuri/yuriplayer/YuriPlayerApp.kt index 13d4f6b7..87163659 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/YuriPlayerApp.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/YuriPlayerApp.kt @@ -1,5 +1,7 @@ package capital.yuri.yuriplayer +import capital.yuri.yuriplayer.core.log.yuriLog + import android.app.Application import android.content.Intent import android.os.Handler @@ -61,7 +63,7 @@ class YuriPlayerApp : Application(), SingletonImageLoader.Factory { ImageLoader.Builder(context) .coroutineContext( SupervisorJob() + Dispatchers.IO + CoroutineExceptionHandler { _, t -> - android.util.Log.w("Coil", "image load cancelled", t) + yuriLog("Coil").w(t) { "image load cancelled" } } ) .components { diff --git a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt index 725026b6..df62dde0 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/activities/ui/AlbumDetailScreen.kt @@ -184,10 +184,7 @@ fun AlbumDetailScreen( mutableIntStateOf(album.trackCount.coerceAtLeast(album.songs.size)) } LaunchedEffect(stableKey) { - AlbumLog.i( - album.name, - "PAGE open stableKey='$stableKey' albumKey='$albumKeyStr' seed=${album.songs.size} artist='${album.artist}'" - ) + AlbumLog.i(album.name, "PAGE open stableKey='$stableKey' albumKey='$albumKeyStr' seed=${album.songs.size} artist='${album.artist}'") var current = album fun publish(next: AlbumItem, stage: String) { val union = dedupeAlbumPageTracks(current.songs + next.songs + album.songs) diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumArtCache.kt b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumArtCache.kt index 36cb2691..9055408c 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumArtCache.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumArtCache.kt @@ -3,7 +3,7 @@ package capital.yuri.yuriplayer.data import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -32,7 +32,7 @@ class AlbumArtCache( private val thumbs = object : LinkedHashMap(MAX_THUMB_MEMORY + 1, 0.75f, true) { override fun removeEldestEntry(eldest: MutableMap.MutableEntry?): Boolean { val drop = size > MAX_THUMB_MEMORY - if (drop) Log.d(TAG, "thumb-evict ${eldest?.key}") + if (drop) log.d { "thumb-evict ${eldest?.key}" } return drop } } @@ -40,7 +40,7 @@ class AlbumArtCache( private val heroes = object : LinkedHashMap(MAX_HERO_MEMORY + 1, 0.75f, true) { override fun removeEldestEntry(eldest: MutableMap.MutableEntry?): Boolean { val drop = size > MAX_HERO_MEMORY - if (drop) Log.d(TAG, "hero-evict ${eldest?.key}") + if (drop) log.d { "hero-evict ${eldest?.key}" } return drop } } @@ -48,7 +48,7 @@ class AlbumArtCache( private val hq = object : LinkedHashMap(MAX_HQ_MEMORY + 1, 0.75f, true) { override fun removeEldestEntry(eldest: MutableMap.MutableEntry?): Boolean { val drop = size > MAX_HQ_MEMORY - if (drop) Log.d(TAG, "hq-evict ${eldest?.key}") + if (drop) log.d { "hq-evict ${eldest?.key}" } return drop } } @@ -151,7 +151,7 @@ class AlbumArtCache( markers.any { m -> k.contains(m, ignoreCase = true) } } keys.forEach { map.remove(it) } - if (keys.isNotEmpty()) Log.d(TAG, "invalidate album=$albumKeyStr keys=$keys") + if (keys.isNotEmpty()) log.d { "invalidate album=$albumKeyStr keys=$keys" } } purge(thumbs) purge(heroes) @@ -163,7 +163,7 @@ class AlbumArtCache( thumbs.clear() heroes.clear() hq.clear() - Log.d(TAG, "mem-clear") + log.d { "mem-clear" } } private fun diskFile(key: String): File { @@ -181,14 +181,14 @@ class AlbumArtCache( val bmp = BitmapFactory.decodeFile(f.absolutePath)?.takeIf { !it.isRecycled } if (bmp == null) return null if (minDim > 0 && maxOf(bmp.width, bmp.height) < minDim) { - Log.d(TAG, "disk-stale $key ${bmp.width}x${bmp.height} < $minDim") + log.d { "disk-stale $key ${bmp.width}x${bmp.height} < $minDim" } f.delete() null } else { bmp } } catch (e: Exception) { - Log.w(TAG, "disk-read failed $key", e) + log.w(e) { "disk-read failed $key" } null } } @@ -208,7 +208,7 @@ class AlbumArtCache( } trimDiskIfNeeded() } catch (e: Exception) { - Log.w(TAG, "disk-write failed $key", e) + log.w(e) { "disk-write failed $key" } } } @@ -242,7 +242,7 @@ class AlbumArtCache( lock.withLock { map[key]?.takeIf { !it.isRecycled }?.let { return it } map[key] = fromDisk - Log.d(TAG, "disk-hit $key mem=${map.size}") + log.d { "disk-hit $key mem=${map.size}" } } return fromDisk } @@ -289,7 +289,7 @@ class AlbumArtCache( lock.withLock { map[key]?.takeIf { !it.isRecycled }?.let { return it } map[key] = bmp - Log.d(TAG, "decode-put $key mem=${map.size}") + log.d { "decode-put $key mem=${map.size}" } } return bmp } @@ -314,7 +314,7 @@ class AlbumArtCache( try { get(context, song, tier) } catch (e: Exception) { - Log.w(TAG, "prefetch failed $key", e) + log.w(e) { "prefetch failed $key" } } } } @@ -337,7 +337,7 @@ class AlbumArtCache( } companion object { - private const val TAG = "YuriPlayer.ArtCache" + private val log = yuriLog("ArtCache") const val THUMB_DECODE_SIZE = 128 const val HERO_DECODE_SIZE = 512 /** Now-playing cover. Stylo 4 NP art is ~680px; 512 looked soft. */ diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumArtResolver.kt b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumArtResolver.kt index 82fafad3..726f78cc 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumArtResolver.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumArtResolver.kt @@ -5,7 +5,7 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.media.MediaMetadataRetriever import android.net.Uri -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import androidx.documentfile.provider.DocumentFile import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex @@ -30,7 +30,7 @@ import java.util.concurrent.ConcurrentHashMap */ object AlbumArtResolver { - private const val TAG = "AlbumArtResolver" + private val log = yuriLog("AlbumArtResolver") private const val MAX_SAF_EXTRACT_BYTES = 80L * 1024L * 1024L /** Master cover stored under filesDir/covers — big enough for now-playing. */ private const val PERSISTENT_COVER_SIZE = 1024 @@ -93,29 +93,29 @@ object AlbumArtResolver { // Stream FLAC picture first (SAF-friendly, no full-file copy) loadStreamEmbedded(context, song, maxSize)?.also { cacheToDisk(context, song, it) - Log.i(TAG, "stream art ok album=$aKey") + log.i { "stream art ok album=$aKey" } return@withContext scaleDown(it, maxSize) } loadEmbeddedMmr(context, song, maxSize)?.also { cacheToDisk(context, song, it) - Log.i(TAG, "MMR art ok album=$aKey") + log.i { "MMR art ok album=$aKey" } return@withContext scaleDown(it, maxSize) } loadEmbeddedJaudio(context, song, maxSize)?.also { cacheToDisk(context, song, it) - Log.i(TAG, "jaudio art ok album=$aKey") + log.i { "jaudio art ok album=$aKey" } return@withContext scaleDown(it, maxSize) } // Last resort: an undersized cached cover beats a blank now-playing card. loadDiskAlbumCache(context, song, maxSize, minDim = 0)?.let { - Log.w(TAG, "undersized cache art album=$aKey ${it.width}x${it.height} want=$maxSize") + log.w { "undersized cache art album=$aKey ${it.width}x${it.height} want=$maxSize" } return@withContext scaleDown(it, maxSize) } - Log.w(TAG, "no art for album=$aKey path=${song.path} uri=${song.contentUri}") + log.w { "no art for album=$aKey path=${song.path} uri=${song.contentUri}" } } null @@ -136,7 +136,7 @@ object AlbumArtResolver { if (!bmp.isRecycled) runCatching { bmp.recycle() } true } catch (e: Exception) { - Log.w(TAG, "downloadToFile failed $url", e) + log.w(e) { "downloadToFile failed $url" } false } } @@ -213,7 +213,7 @@ object AlbumArtResolver { } dest.isFile && dest.length() > 0L } catch (e: Exception) { - Log.w(TAG, "writeBitmapFile failed", e) + log.w(e) { "writeBitmapFile failed" } false } } @@ -312,7 +312,7 @@ object AlbumArtResolver { streamEmbeddedFrom(input, uri.toString(), maxSize) } } catch (e: Exception) { - Log.d(TAG, "stream open failed: ${e.message}") + log.d { "stream open failed: ${e.message}" } null } } @@ -340,7 +340,7 @@ object AlbumArtResolver { else -> null } if (bytes == null) { - Log.d(TAG, "stream no picture for $label flac=$isFlac id3=$isId3") + log.d { "stream no picture for $label flac=$isFlac id3=$isId3" } return null } return decodeBytesSampled(bytes, maxSize) @@ -395,7 +395,7 @@ object AlbumArtResolver { } null } catch (e: Exception) { - Log.d(TAG, "FLAC picture parse failed: ${e.message}") + log.d { "FLAC picture parse failed: ${e.message}" } null } } @@ -451,7 +451,7 @@ object AlbumArtResolver { } null } catch (e: Exception) { - Log.d(TAG, "ID3 APIC parse failed: ${e.message}") + log.d { "ID3 APIC parse failed: ${e.message}" } null } } @@ -491,7 +491,7 @@ object AlbumArtResolver { val bytes = retriever.embeddedPicture ?: return null decodeBytesSampled(bytes, maxSize) } catch (e: Exception) { - Log.d(TAG, "MMR embedded failed: ${e.message}") + log.d { "MMR embedded failed: ${e.message}" } null } finally { try { @@ -566,7 +566,7 @@ object AlbumArtResolver { if (bytes.isEmpty()) return null decodeBytesSampled(bytes, maxSize) } catch (t: Throwable) { - Log.w(TAG, "jaudio art failed ${file.name}: ${t.javaClass.simpleName}: ${t.message}") + log.w { "jaudio art failed ${file.name}: ${t.javaClass.simpleName}: ${t.message}" } null } } @@ -574,7 +574,7 @@ object AlbumArtResolver { private fun extractJaudioFromContentUri(context: Context, uri: Uri, maxSize: Int): Bitmap? { val size = querySize(context, uri) if (size != null && size > MAX_SAF_EXTRACT_BYTES) { - Log.w(TAG, "skip jaudio SAF extract size=$size") + log.w { "skip jaudio SAF extract size=$size" } return null } @@ -587,7 +587,7 @@ object AlbumArtResolver { if (tmp.length() == 0L) return null extractJaudioArtwork(tmp, maxSize) } catch (t: Throwable) { - Log.w(TAG, "jaudio SAF art failed: ${t.javaClass.simpleName}: ${t.message}") + log.w { "jaudio SAF art failed: ${t.javaClass.simpleName}: ${t.message}" } null } finally { runCatching { tmp.delete() } @@ -663,7 +663,7 @@ object AlbumArtResolver { } null } catch (e: Exception) { - Log.d(TAG, "SAF folder cover failed: ${e.message}") + log.d { "SAF folder cover failed: ${e.message}" } null } } @@ -680,7 +680,7 @@ object AlbumArtResolver { private fun loadImageUri(context: Context, uri: Uri?, maxSize: Int): Bitmap? { if (uri == null) return null if (!isLikelyImageUri(context, uri)) { - Log.d(TAG, "skip non-image uri $uri") + log.d { "skip non-image uri $uri" } return null } val scheme = uri.scheme?.lowercase() @@ -699,7 +699,7 @@ object AlbumArtResolver { decodeStreamSampled(input, maxSize) } } catch (e: Exception) { - Log.d(TAG, "loadImageUri failed: ${e.message}") + log.d { "loadImageUri failed: ${e.message}" } null } } @@ -766,13 +766,13 @@ object AlbumArtResolver { } val code = conn.responseCode if (code !in 200..299) { - Log.w(TAG, "HTTP $code for $url") + log.w { "HTTP $code for $url" } conn.disconnect() return null } conn.inputStream } catch (e: Exception) { - Log.w(TAG, "openHttp failed $url", e) + log.w(e) { "openHttp failed $url" } null } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumLog.kt b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumLog.kt index 30fc84e0..4f643692 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumLog.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumLog.kt @@ -1,23 +1,24 @@ package capital.yuri.yuriplayer.data -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.db.CatalogTrackEntity /** * Album expand / merge diagnostics. * - * Tag is always [TAG] = `YuriAlbum`. Every line starts with `[album title]` so you - * can grep Clancy / self-titled without knowing the code path. + * Tag is always [TAG] = `YuriAlbum` → logcat `YuriPlayer.YuriAlbum`. + * Every line starts with `[album title]` so you can grep Clancy / self-titled + * without knowing the code path. * * Logcat (VERBOSE = TRACE): * ``` - * adb shell setprop log.tag.YuriAlbum VERBOSE - * adb logcat -v time YuriAlbum:V *:S - * adb logcat -v time YuriAlbum:V *:S | grep -i clancy - * adb logcat -v time YuriAlbum:V *:S | grep -i 'twenty one pilots' + * adb shell setprop log.tag.YuriPlayer.YuriAlbum VERBOSE + * adb logcat -v time YuriPlayer.YuriAlbum:V *:S + * adb logcat -v time YuriPlayer.YuriAlbum:V *:S | grep -i clancy + * adb logcat -v time YuriPlayer.YuriAlbum:V *:S | grep -i 'twenty one pilots' * ``` * - * Android Studio Logcat: package `capital.yuri.yuriplayer`, tag `YuriAlbum`, + * Android Studio Logcat: package `capital.yuri.yuriplayer`, tag `YuriPlayer.YuriAlbum`, * level **Verbose**. * * Levels: @@ -28,47 +29,47 @@ import capital.yuri.yuriplayer.data.db.CatalogTrackEntity */ internal object AlbumLog { const val TAG = "YuriAlbum" + private val log = yuriLog(TAG) - fun i(album: String?, msg: String) = Log.i(TAG, line(album, msg)) - fun d(album: String?, msg: String) = Log.d(TAG, line(album, msg)) - fun v(album: String?, msg: String) = Log.v(TAG, line(album, msg)) - fun w(album: String?, msg: String) = Log.w(TAG, line(album, msg)) + fun i(album: String?, msg: String) = log.i { line(album, msg) } + fun d(album: String?, msg: String) = log.d { line(album, msg) } + fun v(album: String?, msg: String) = log.t { line(album, msg) } + fun w(album: String?, msg: String) = log.w { line(album, msg) } fun step(album: String?, name: String, count: Int, extra: String = "") { val suffix = if (extra.isNotEmpty()) " $extra" else "" - Log.d(TAG, line(album, "step $name n=$count$suffix")) + log.d { line(album, "step $name n=$count$suffix") } } fun songs(album: String?, label: String, tracks: List) { - Log.d(TAG, line(album, "$label n=${tracks.size}")) + log.d { line(album, "$label n=${tracks.size}") } tracks.forEachIndexed { i, s -> - Log.v(TAG, line(album, " $label[$i] ${songLine(s)}")) + log.t { line(album, " $label[$i] ${songLine(s)}") } } } fun entities(album: String?, label: String, rows: Collection) { - Log.d(TAG, line(album, "$label n=${rows.size}")) + log.d { line(album, "$label n=${rows.size}") } rows.forEachIndexed { i, r -> - Log.v(TAG, line(album, " $label[$i] ${entityLine(r)}")) + log.t { line(album, " $label[$i] ${entityLine(r)}") } } } fun identityGroups(album: String?, groups: Map>) { - Log.d(TAG, line(album, "identity groups=${groups.size}")) + log.d { line(album, "identity groups=${groups.size}") } groups.forEach { (id, members) -> if (members.size > 1) { - Log.w( - TAG, + log.w { line( album, "COLLIDE identity=$id n=${members.size} titles=${members.joinToString(" | ") { it.displayTitle }}" ) - ) + } members.forEachIndexed { i, s -> - Log.v(TAG, line(album, " collide[$i] ${songLine(s)}")) + log.t { line(album, " collide[$i] ${songLine(s)}") } } } else { - Log.v(TAG, line(album, " id=$id → ${members.firstOrNull()?.displayTitle}")) + log.t { line(album, " id=$id → ${members.firstOrNull()?.displayTitle}") } } } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumMerge.kt b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumMerge.kt index dd627793..83a2dee2 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumMerge.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumMerge.kt @@ -10,11 +10,8 @@ fun mergeAlbumSources( fromLocal: AlbumItem? ): AlbumItem { val album = seed.name ?: fromCatalog?.name ?: fromLocal?.name - AlbumLog.i( - album, - "merge seed=${seed.songs.size} catalog=${fromCatalog?.songs?.size ?: 0} local=${fromLocal?.songs?.size ?: 0} " + - "seedName='${seed.name}' catName='${fromCatalog?.name}' localName='${fromLocal?.name}'" - ) + AlbumLog.i(album, "merge seed=${seed.songs.size} catalog=${fromCatalog?.songs?.size ?: 0} local=${fromLocal?.songs?.size ?: 0} " + + "seedName='${seed.name}' catName='${fromCatalog?.name}' localName='${fromLocal?.name}'") AlbumLog.songs(album, "merge.seed", seed.songs) AlbumLog.songs(album, "merge.catalog", fromCatalog?.songs.orEmpty()) AlbumLog.songs(album, "merge.local", fromLocal?.songs.orEmpty()) diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumResolve.kt b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumResolve.kt index fdde8278..09755478 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumResolve.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumResolve.kt @@ -32,10 +32,8 @@ suspend fun resolveAlbumItem( extraSeedSongs = seedSongs ) AlbumLog.songs(albumName, "resolve.expanded", expanded) - val localSongs = library?.let { findLocalAlbum(it, albumName, artistName)?.songs }.orEmpty() AlbumLog.songs(albumName, "resolve.local", localSongs) - val songs = dedupeAlbumPageTracks(expanded + seedSongs + localSongs) AlbumLog.i(albumName, "resolve done n=${songs.size} expanded=${expanded.size} seed=${seedSongs.size} local=${localSongs.size}") if (songs.size < seedSongs.size && seedSongs.size > 1) { diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumTrackExpand.kt b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumTrackExpand.kt index ccacae65..0049798a 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/AlbumTrackExpand.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/AlbumTrackExpand.kt @@ -30,6 +30,7 @@ suspend fun expandAlbumTracks( ?: albumKey.substringBefore('|').takeIf { it.isNotBlank() } AlbumLog.i(albumName, "expand key='$albumKey' artist='$artistName' direct=${direct.size} row=${row != null}") + AlbumLog.entities(albumName, "direct", direct) return expandAlbumTracksByName( @@ -84,6 +85,7 @@ suspend fun fastExpandAlbumTracks( val deduped = dedupeAlbumPageTracks(songs + extraSeedSongs) AlbumLog.i(name, "fast expand n=${deduped.size} raw=${bySongKey.size}") + return deduped.sortedWith(albumTrackOrder()) } @@ -131,11 +133,13 @@ suspend fun expandAlbumTracksByName( keys += albumKey(name, null) } AlbumLog.d(name, "albumKeys=${keys.size} ${keys.joinToString()}") + for (k in keys) { if (k.isBlank()) continue val rows = dao.getTracksForAlbum(k) rows.forEach { put(it) } AlbumLog.v(name, " key '$k' → ${rows.size}") + } AlbumLog.step(name, "after keys", bySongKey.size) @@ -211,11 +215,10 @@ suspend fun expandAlbumTracksByName( val dropped = bySongKey.size - filtered.size if (dropped > 0) { AlbumLog.w(name, "filter dropped $dropped/${bySongKey.size} artistFold='$artistFolded'") + bySongKey.values.filter { !albumOk(it) || !artistOk(it) }.forEach { - AlbumLog.v( - name, - " DROP albumOk=${albumOk(it)} artistOk=${artistOk(it)} ${AlbumLog.entityLine(it)}" - ) + AlbumLog.v(name, " DROP albumOk=${albumOk(it)} artistOk=${artistOk(it)} ${AlbumLog.entityLine(it)}") + } } AlbumLog.entities(name, "filtered", filtered) @@ -229,10 +232,8 @@ suspend fun expandAlbumTracksByName( val deduped = dedupeAlbumPageTracks(combined) - AlbumLog.i( - name, - "expand done artist='$artist' raw=${combined.size} deduped=${deduped.size} keys=${bySongKey.size} filtered=${filtered.size}" - ) + AlbumLog.i(name, "expand done artist='$artist' raw=${combined.size} deduped=${deduped.size} keys=${bySongKey.size} filtered=${filtered.size}") + AlbumLog.songs(name, "deduped", deduped) return deduped.sortedWith(albumTrackOrder()) diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/ArtistAlbumsLight.kt b/app/src/main/java/capital/yuri/yuriplayer/data/ArtistAlbumsLight.kt index cf4d7126..81bd91d8 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/ArtistAlbumsLight.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/ArtistAlbumsLight.kt @@ -30,10 +30,7 @@ suspend fun lightAlbumItemsForArtist( val items = rows.map { row -> row.toLightAlbum(seeds[row.albumKey]?.toLightSong()) } - AlbumLog.i( - displayName, - "light discography from album rows n=${items.size} keys=${keys.size}" - ) + AlbumLog.i(displayName, "light discography from album rows n=${items.size} keys=${keys.size}") return@withContext clusterNearDuplicateAlbums(items).sortedWith(lightAlbumOrder()) } @@ -56,6 +53,7 @@ suspend fun lightAlbumItemsForArtist( ) } AlbumLog.i(displayName, "light discography from tracks n=${fromTracks.size}") + clusterNearDuplicateAlbums(fromTracks).sortedWith(lightAlbumOrder()) } @@ -130,10 +128,7 @@ private fun clusterNearDuplicateAlbums(items: List): List val exact = items.groupBy { albumKey(it.name, it.artist) }.map { (key, group) -> val pick = group.maxByOrNull { it.trackCount } ?: group.first() if (group.size > 1) { - AlbumLog.d( - pick.name, - "light merge cards=${group.size} key='$key' counts=${group.map { it.trackCount }}" - ) + AlbumLog.d(pick.name, "light merge cards=${group.size} key='$key' counts=${group.map { it.trackCount }}") } pick } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/CatalogRepository.kt b/app/src/main/java/capital/yuri/yuriplayer/data/CatalogRepository.kt index 6faa9d35..d200426b 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/CatalogRepository.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/CatalogRepository.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.db.ArtistAliasEntity import capital.yuri.yuriplayer.data.db.CatalogAlbumEntity import capital.yuri.yuriplayer.data.db.CatalogArtistEntity @@ -362,7 +362,7 @@ class CatalogRepository( updatedAtMs = System.currentTimeMillis() ) ) - Log.i(TAG, "patchTrackTags $songKey") + log.i { "patchTrackTags $songKey" } } suspend fun syncLocalLibrary(): List = withContext(Dispatchers.IO) { @@ -376,7 +376,7 @@ class CatalogRepository( rebuildRollupsLocked() val local = dao.getTracksBySource(CatalogSources.LOCAL).map { it.toSong() } - Log.i(TAG, "local sync: ${local.size} local tracks") + log.i { "local sync: ${local.size} local tracks" } local } @@ -495,7 +495,7 @@ class CatalogRepository( if (deleted > 0) { dao.deleteOrphanAlbums() dao.deleteOrphanArtists() - Log.i(TAG, "pruned $deleted missing $sourceType rows (live=${liveIds.size})") + log.i { "pruned $deleted missing $sourceType rows (live=${liveIds.size})" } } deleted } @@ -522,7 +522,7 @@ class CatalogRepository( ) } dao.upsertTracks(entities) - Log.i(TAG, "ensureTracksPresent: upserted ${entities.size}") + log.i { "ensureTracksPresent: upserted ${entities.size}" } } suspend fun rebuildRollups() = withContext(Dispatchers.IO) { @@ -581,7 +581,7 @@ class CatalogRepository( dao.deleteOrphanAlbums() dao.deleteOrphanArtists() - Log.i(TAG, "rollups rebuilt (SQL): albums=${albums.size} artists=${artists.size}") + log.i { "rollups rebuilt (SQL): albums=${albums.size} artists=${artists.size}" } } suspend fun loadAliases() = withContext(Dispatchers.IO) { @@ -680,7 +680,7 @@ class CatalogRepository( if (deleted > 0) { dao.deleteOrphanAlbums() dao.deleteOrphanArtists() - Log.i(TAG, "pruned $deleted stale $sourceType rows (kept ${keepSongKeys.size} My Stuff keys)") + log.i { "pruned $deleted stale $sourceType rows (kept ${keepSongKeys.size} My Stuff keys)" } } } @@ -705,7 +705,7 @@ class CatalogRepository( seenAt = now ) ) - Log.i(TAG, "imported to My Stuff: ${song.songKey} from $sourceType") + log.i { "imported to My Stuff: ${song.songKey} from $sourceType" } } suspend fun applyAlbumYear(albumKey: String, year: Int) = withContext(Dispatchers.IO) { @@ -828,7 +828,7 @@ class CatalogRepository( } if (changed.isEmpty()) return changed.chunked(400).forEach { dao.upsertTracks(it) } - Log.i(TAG, "normalized identity on ${changed.size} tracks") + log.i { "normalized identity on ${changed.size} tracks" } } /** @@ -1015,8 +1015,7 @@ class CatalogRepository( ) companion object { - private const val TAG = "CatalogRepo" - + private val log = yuriLog("CatalogRepo") fun sourceTypeForSong(song: Song): SourceType { val p = song.path.orEmpty() return when { diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/ExploreSearchService.kt b/app/src/main/java/capital/yuri/yuriplayer/data/ExploreSearchService.kt index e15c3c71..9f9c7db7 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/ExploreSearchService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/ExploreSearchService.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.data import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.db.CatalogSources import capital.yuri.yuriplayer.data.source.JellyfinClient import capital.yuri.yuriplayer.data.source.LibrarySourceFactory @@ -102,24 +102,24 @@ class ExploreSearchService( fun requestPauseAll() { pauseAll.set(true) - Log.i(TAG, "pause all requested") + log.i { "pause all requested" } } fun requestStopAll() { stopAll.set(true) pauseAll.set(true) - Log.i(TAG, "stop all requested") + log.i { "stop all requested" } } fun requestPauseSource(sourceInstanceId: Long) { pausedSources.add(sourceInstanceId) - Log.i(TAG, "pause source $sourceInstanceId") + log.i { "pause source $sourceInstanceId" } } fun requestStopSource(sourceInstanceId: Long) { stoppedSources.add(sourceInstanceId) pausedSources.add(sourceInstanceId) - Log.i(TAG, "stop source $sourceInstanceId") + log.i { "stop source $sourceInstanceId" } } fun clearControlFlags() { @@ -145,13 +145,13 @@ class ExploreSearchService( hydrated = true if (count > 0) cacheAtMs = System.currentTimeMillis() refreshCheckpointSnapshot() - Log.i(TAG, "hydrated remote count=$count (no full list loaded)") + log.i { "hydrated remote count=$count (no full list loaded)" } } } fun requestRemoteScan(force: Boolean = false, sourceId: Long? = null) { if (!force && _isScanning.value) { - Log.i(TAG, "requestRemoteScan ignored — already scanning") + log.i { "requestRemoteScan ignored — already scanning" } return } val age = System.currentTimeMillis() - cacheAtMs @@ -160,7 +160,7 @@ class ExploreSearchService( it.status == SourceScanStatus.PAUSED || it.status == SourceScanStatus.STOPPED } if (!hasPaused) { - Log.i(TAG, "requestRemoteScan skipped — cache warm (${_indexedCount.value})") + log.i { "requestRemoteScan skipped — cache warm (${_indexedCount.value})" } return } } @@ -168,13 +168,13 @@ class ExploreSearchService( if (!NetworkPolicy.allowsRemoteSync(context, settings)) { val reason = NetworkPolicy.blockedReason(context, settings) ?: "Remote sync blocked on this network" - Log.i(TAG, "requestRemoteScan blocked: $reason") + log.i { "requestRemoteScan blocked: $reason" } _lastError.value = reason return } if (force) clearControlFlags() - Log.i(TAG, "requestRemoteScan force=$force sourceId=$sourceId indexed=${_indexedCount.value}") + log.i { "requestRemoteScan force=$force sourceId=$sourceId indexed=${_indexedCount.value}" } LibraryScanService.startRemote(context.applicationContext, force, sourceId) } @@ -291,7 +291,7 @@ class ExploreSearchService( ?: "Remote sync blocked on this network" _lastError.value = reason progress(reason) - Log.i(TAG, "sync paused: $reason") + log.i { "sync paused: $reason" } return false } @@ -308,15 +308,15 @@ class ExploreSearchService( progress("Building album / artist index…") runCatching { catalog.rebuildRollups() - Log.i(TAG, "rollups rebuilt ($reason)") + log.i { "rollups rebuilt ($reason)" } }.onFailure { - Log.w(TAG, "rollups failed ($reason): ${it.message}") + log.w { "rollups failed ($reason): ${it.message}" } } } private suspend fun runRemoteScan(force: Boolean, onlySourceId: Long? = null) { if (!mutex.tryLock()) { - Log.i(TAG, "scan already running — skip") + log.i { "scan already running — skip" } return } try { @@ -330,7 +330,7 @@ class ExploreSearchService( _lastError.value = null pageCounter.set(0) notifier.update("Syncing libraries", "Connecting…") - Log.i(TAG, "remote scan start force=$force sourceId=$onlySourceId") + log.i { "remote scan start force=$force sourceId=$onlySourceId" } val rows = instances.getAll().filter { it.enabled } .filter { onlySourceId == null || it.id == onlySourceId } @@ -499,13 +499,13 @@ class ExploreSearchService( } } - Log.i(TAG, "remote index ready: count=$finalCount") + log.i { "remote index ready: count=$finalCount" } } catch (e: CancellationException) { - Log.i(TAG, "remote scan cancelled") + log.i { "remote scan cancelled" } throw e } catch (e: Exception) { _lastError.value = e.message ?: "Scan failed" - Log.e(TAG, "remote index failed", e) + log.e(e) { "remote index failed" } } finally { _isScanning.value = false _scanProgress.value = null @@ -532,7 +532,7 @@ class ExploreSearchService( totalHint: Int? ): RemoteScanResult { val remoteTotal = jellyfinClient.audioItemCount(session).getOrElse { - Log.w(TAG, "delta count failed $sourceName: ${it.message}") + log.w { "delta count failed $sourceName: ${it.message}" } _lastError.value = "$sourceName: ${it.message}" return RemoteScanResult() } @@ -541,13 +541,13 @@ class ExploreSearchService( checkpoints.markDone(rowId, sourceName, known, remoteTotal) refreshCheckpointSnapshot() progress("$sourceName: up to date ($remoteTotal)") - Log.i(TAG, "jellyfin delta skip $sourceName known=$known remote=$remoteTotal") + log.i { "jellyfin delta skip $sourceName known=$known remote=$remoteTotal" } return RemoteScanResult(delivered = 0) } val grow = remoteTotal - known val cap = (grow + 64).coerceIn(1, 2_000) progress("$sourceName: $grow new tracks") - Log.i(TAG, "jellyfin delta $sourceName +$grow known=$known remote=$remoteTotal cap=$cap") + log.i { "jellyfin delta $sourceName +$grow known=$known remote=$remoteTotal cap=$cap" } var inserted = 0 var updated = 0 @@ -575,13 +575,13 @@ class ExploreSearchService( val n = total ?: remoteTotal progress("$sourceName: +$inserted new · $n on server") }.onFailure { - Log.w(TAG, "jellyfin delta fetch failed $sourceName: ${it.message}") + log.w { "jellyfin delta fetch failed $sourceName: ${it.message}" } _lastError.value = "$sourceName: ${it.message}" } checkpoints.markDone(rowId, sourceName, knownCount + delivered, remoteTotal) refreshCheckpointSnapshot() - Log.i(TAG, "jellyfin delta done $sourceName +$inserted ~$updated of $grow") + log.i { "jellyfin delta done $sourceName +$inserted ~$updated of $grow" } return RemoteScanResult( delivered = delivered, inserted = inserted, @@ -609,7 +609,7 @@ class ExploreSearchService( val session = jellyfinSessions[rowId] ?: jellyfinClient.authenticate(url, user, pass) .getOrElse { if (it is CancellationException) throw it - Log.w(TAG, "jellyfin auth failed: ${it.message}") + log.w { "jellyfin auth failed: ${it.message}" } _lastError.value = "$sourceName: ${it.message}" return RemoteScanResult() }.also { jellyfinSessions[rowId] = it } @@ -701,16 +701,16 @@ class ExploreSearchService( when { it is CancellationException && paused -> { checkpoints.markPaused(rowId, sourceName, cursor, delivered, totalHint) - Log.i(TAG, "jellyfin paused $sourceName at cursor=$cursor delivered=$delivered") + log.i { "jellyfin paused $sourceName at cursor=$cursor delivered=$delivered" } } it is CancellationException && aborted -> { checkpoints.markStopped(rowId, sourceName, cursor, delivered, totalHint) - Log.i(TAG, "jellyfin stopped $sourceName at cursor=$cursor") + log.i { "jellyfin stopped $sourceName at cursor=$cursor" } } it is CancellationException -> throw it else -> { jellyfinSessions.remove(rowId) - Log.w(TAG, "jellyfin scan failed: ${it.message}") + log.w { "jellyfin scan failed: ${it.message}" } _lastError.value = "$sourceName: ${it.message}" } } @@ -732,10 +732,7 @@ class ExploreSearchService( } checkpoints.markDone(rowId, sourceName, delivered, totalHint) refreshCheckpointSnapshot() - Log.i( - TAG, - "jellyfin $sourceName done delivered=$delivered +$inserted ~$updated -$removed incremental=$incremental" - ) + log.i { "jellyfin $sourceName done delivered=$delivered +$inserted ~$updated -$removed incremental=$incremental" } return RemoteScanResult( delivered = delivered, inserted = inserted, @@ -775,7 +772,7 @@ class ExploreSearchService( attempted++ budget.yieldBetweenArt() } - Log.i(TAG, "passive art pass: attempted=$attempted cached=${cachedAlbumArtKeys.size}") + log.i { "passive art pass: attempted=$attempted cached=${cachedAlbumArtKeys.size}" } } private suspend fun scanSubsonicNewest( @@ -792,7 +789,7 @@ class ExploreSearchService( pageSize = 20, type = "newest" ).getOrElse { - Log.w(TAG, "subsonic newest failed $sourceName: ${it.message}") + log.w { "subsonic newest failed $sourceName: ${it.message}" } return RemoteScanResult() } var inserted = 0 @@ -813,7 +810,7 @@ class ExploreSearchService( } checkpoints.markDone(instanceId, sourceName, delivered, null) refreshCheckpointSnapshot() - Log.i(TAG, "subsonic newest $sourceName albums=${page.albums.size} +$inserted ~$updated") + log.i { "subsonic newest $sourceName albums=${page.albums.size} +$inserted ~$updated" } progress("$sourceName: +$inserted new") return RemoteScanResult(delivered = delivered, inserted = inserted, updated = updated) } @@ -848,7 +845,7 @@ class ExploreSearchService( val session = subsonicClient.ping(baseSession).getOrElse { if (it is CancellationException) throw it - Log.w(TAG, "subsonic ping failed: ${it.message}") + log.w { "subsonic ping failed: ${it.message}" } _lastError.value = "$sourceName: ${it.message}" return RemoteScanResult() } @@ -931,15 +928,15 @@ class ExploreSearchService( when { it is CancellationException && paused -> { checkpoints.markPaused(instanceId, sourceName, albumOffset, delivered, null) - Log.i(TAG, "subsonic paused $sourceName at albumOffset=$albumOffset delivered=$delivered") + log.i { "subsonic paused $sourceName at albumOffset=$albumOffset delivered=$delivered" } } it is CancellationException && aborted -> { checkpoints.markStopped(instanceId, sourceName, albumOffset, delivered, null) - Log.i(TAG, "subsonic stopped $sourceName at albumOffset=$albumOffset") + log.i { "subsonic stopped $sourceName at albumOffset=$albumOffset" } } it is CancellationException -> throw it else -> { - Log.w(TAG, "subsonic scan failed: ${it.message}") + log.w { "subsonic scan failed: ${it.message}" } _lastError.value = "$sourceName: ${it.message}" } } @@ -961,10 +958,7 @@ class ExploreSearchService( } checkpoints.markDone(instanceId, sourceName, delivered, delivered) refreshCheckpointSnapshot() - Log.i( - TAG, - "subsonic $sourceName done delivered=$delivered +$inserted ~$updated -$removed" - ) + log.i { "subsonic $sourceName done delivered=$delivered +$inserted ~$updated -$removed" } return RemoteScanResult( delivered = delivered, inserted = inserted, @@ -991,7 +985,7 @@ class ExploreSearchService( } companion object { - private const val TAG = "ExploreSearch" + private val log = yuriLog("ExploreSearch") private const val SCOPE_TRACK = "track" private const val CACHE_TTL_MS = 15L * 60 * 1000 private const val WARM_SOURCE_MIN_TRACKS = 50 diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryCache.kt b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryCache.kt index 1209f985..ea7b89a5 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryCache.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryCache.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.data import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.json.AppJson import kotlinx.serialization.Serializable import java.io.File @@ -20,7 +20,7 @@ class LibraryCache(context: Context) { val songs = dto.songs.map { it.cleaned() } CachedLibrary(songs, dto.scannedAt) } catch (e: Exception) { - Log.w(TAG, "Failed to load library cache", e) + log.w(e) { "Failed to load library cache" } null } } @@ -39,7 +39,7 @@ class LibraryCache(context: Context) { tmpFile.delete() } } catch (e: Exception) { - Log.e(TAG, "Failed to save library cache", e) + log.e(e) { "Failed to save library cache" } try { tmpFile.delete() } catch (_: Exception) {} } } @@ -54,7 +54,7 @@ class LibraryCache(context: Context) { fun cacheFilePath(): String = file.absolutePath companion object { - private const val TAG = "LibraryCache" + private val log = yuriLog("LibraryCache") private const val FILE_NAME = "library_index.json" private const val CACHE_VERSION = 3 } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryIndex.kt b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryIndex.kt index 352eab3a..c91ed86f 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryIndex.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryIndex.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.data import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -65,7 +65,7 @@ class LibraryIndex( if (hadCache) { _songs.value = cached!!.songs _lastScannedAt.value = cached.scannedAt - Log.i(TAG, "bootstrap from cache: ${cached.songs.size} tracks") + log.i { "bootstrap from cache: ${cached.songs.size} tracks" } } // 2) Room only when cache missed — avoid loading large local tables @@ -75,7 +75,7 @@ class LibraryIndex( if (fromDb.isNotEmpty()) { _songs.value = fromDb _lastScannedAt.value = System.currentTimeMillis() - Log.i(TAG, "bootstrap from Room local: ${fromDb.size} tracks") + log.i { "bootstrap from Room local: ${fromDb.size} tracks" } } } @@ -85,7 +85,7 @@ class LibraryIndex( _songs.value.isEmpty() -> { delay(COLD_EMPTY_RESCAN_DELAY_MS) if (_songs.value.isEmpty() && !_isLoading.value) { - Log.i(TAG, "bootstrap: empty after delay → local rescan") + log.i { "bootstrap: empty after delay → local rescan" } refresh() } } @@ -98,7 +98,7 @@ class LibraryIndex( if (stillStale && !_isLoading.value) { delay(STALE_RESCAN_DELAY_MS) if (!_isLoading.value) { - Log.i(TAG, "bootstrap: stale after reconcile → deferred local rescan") + log.i { "bootstrap: stale after reconcile → deferred local rescan" } refresh() } } @@ -108,11 +108,11 @@ class LibraryIndex( age > staleAfterMs -> { delay(STALE_RESCAN_DELAY_MS) if (!_isLoading.value) { - Log.i(TAG, "bootstrap: stale (${age}ms) → deferred local rescan") + log.i { "bootstrap: stale (${age}ms) → deferred local rescan" } refresh() } } - else -> Log.i(TAG, "bootstrap: warm local index, skip auto-rescan") + else -> log.i { "bootstrap: warm local index, skip auto-rescan" } } } } @@ -122,7 +122,7 @@ class LibraryIndex( val local = withContext(Dispatchers.IO) { catalog.getLocalSongs() } _songs.value = local _lastScannedAt.value = System.currentTimeMillis() - Log.i(TAG, "reloadFromCatalog (local): ${local.size} tracks") + log.i { "reloadFromCatalog (local): ${local.size} tracks" } } fun refresh() { @@ -150,7 +150,7 @@ class LibraryIndex( notifier.finish("Library scan", "Storage permission required") } catch (e: Exception) { _error.value = e.message ?: "Scan failed" - Log.e(TAG, "Refresh failed", e) + log.e(e) { "Refresh failed" } _events.tryEmit(LibraryEvent.ScanFailed(e.message ?: "Scan failed")) notifier.finish("Library scan", e.message ?: "Scan failed") } finally { @@ -174,7 +174,7 @@ class LibraryIndex( } if (changed) { _songs.value = next - Log.i(TAG, "applied year $year to albumKey=$albumKey") + log.i { "applied year $year to albumKey=$albumKey" } } } @@ -304,7 +304,7 @@ class LibraryIndex( fun untaggedCount(): Int = _songs.value.count { !it.isTagged } companion object { - private const val TAG = "LibraryIndex" + private val log = yuriLog("LibraryIndex") const val DEFAULT_STALE_MS = 12L * 60 * 60 * 1000 /** Wait so MusicService can restore + user can hit Play first. */ private const val COLD_EMPTY_RESCAN_DELAY_MS = 5_000L diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanNotifier.kt b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanNotifier.kt index 2b503e62..ff5854bf 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanNotifier.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanNotifier.kt @@ -1,5 +1,7 @@ package capital.yuri.yuriplayer.data +import capital.yuri.yuriplayer.core.log.yuriLog + import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager @@ -95,7 +97,7 @@ class LibraryScanNotifier( nm.notify(NOTIFICATION_ID, build(title, text, progress, max)) } catch (e: SecurityException) { // POST_NOTIFICATIONS denied — FGS may still hold the service notification - android.util.Log.w(TAG, "notify blocked: ${e.message}") + log.w { "notify blocked: ${e.message}" } } } @@ -121,7 +123,7 @@ class LibraryScanNotifier( .build() nm.notify(NOTIFICATION_ID, done) } catch (e: SecurityException) { - android.util.Log.w(TAG, "finish notify blocked: ${e.message}") + log.w { "finish notify blocked: ${e.message}" } } } @@ -130,7 +132,7 @@ class LibraryScanNotifier( } companion object { - private const val TAG = "LibraryScanNotifier" + private val log = yuriLog("LibraryScanNotifier") const val CHANNEL_ID = "yuri_library_scan" const val NOTIFICATION_ID = 4201 } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanService.kt b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanService.kt index 7a83b311..18004261 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/LibraryScanService.kt @@ -7,7 +7,7 @@ import android.content.pm.ServiceInfo import android.os.Build import android.os.IBinder import android.os.Process -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import androidx.core.app.ServiceCompat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -45,14 +45,14 @@ class LibraryScanService : Service() { when (action) { ACTION_STOP -> { - Log.i(TAG, "stop requested sourceId=$sourceId") + log.i { "stop requested sourceId=$sourceId" } if (sourceId != null) explore.requestStopSource(sourceId) else explore.requestStopAll() // Let the worker finish cleanly via cooperative cancel flags return START_NOT_STICKY } ACTION_PAUSE -> { - Log.i(TAG, "pause requested sourceId=$sourceId") + log.i { "pause requested sourceId=$sourceId" } if (sourceId != null) explore.requestPauseSource(sourceId) else explore.requestPauseAll() return START_NOT_STICKY @@ -65,12 +65,12 @@ class LibraryScanService : Service() { if (sourceId != null) { queuedForce = true queuedSourceId = sourceId - Log.i(TAG, "queue initial index sourceId=$sourceId") + log.i { "queue initial index sourceId=$sourceId" } startAsForeground("Syncing libraries", explore.scanProgress.value ?: "Working…") return START_NOT_STICKY } if (!force) { - Log.i(TAG, "remote scan already active — ignore duplicate start") + log.i { "remote scan already active — ignore duplicate start" } startAsForeground("Syncing libraries", explore.scanProgress.value ?: "Working…") return START_NOT_STICKY } @@ -105,7 +105,7 @@ class LibraryScanService : Service() { } } } catch (e: Exception) { - Log.e(TAG, "scan service failed", e) + log.e(e) { "scan service failed" } notifier.finish("Scan failed", e.message ?: "Unknown error") } finally { val nextId = queuedSourceId @@ -113,7 +113,7 @@ class LibraryScanService : Service() { queuedSourceId = null queuedForce = false if (nextId != null) { - Log.i(TAG, "start queued index sourceId=$nextId") + log.i { "start queued index sourceId=$nextId" } startRemote(applicationContext, nextForce, nextId) } else { stopForegroundCompat() @@ -155,7 +155,7 @@ class LibraryScanService : Service() { } companion object { - private const val TAG = "LibraryScanService" + private val log = yuriLog("LibraryScanService") const val ACTION_REMOTE = "capital.yuri.yuriplayer.action.SCAN_REMOTE" const val ACTION_LOCAL = "capital.yuri.yuriplayer.action.SCAN_LOCAL" const val ACTION_STOP = "capital.yuri.yuriplayer.action.SCAN_STOP" diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySyncScheduler.kt b/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySyncScheduler.kt index 33628ef3..7bc26d62 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySyncScheduler.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/LibrarySyncScheduler.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.data import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.source.RemotePlaylistService import capital.yuri.yuriplayer.data.source.SourceInstanceRepository import capital.yuri.yuriplayer.data.source.SourceType @@ -33,11 +33,11 @@ class LibrarySyncScheduler( delay(BOOT_DELAY_MS) while (isActive) { runCatching { tick() } - .onFailure { Log.w(TAG, "tick failed: ${it.message}") } + .onFailure { log.w { "tick failed: ${it.message}" } } delay(POLL_MS) } } - Log.i(TAG, "started") + log.i { "started" } } private suspend fun tick() = withContext(Dispatchers.IO) { @@ -55,7 +55,7 @@ class LibrarySyncScheduler( if (last > 0L && now - last < interval.millis) return val n = playlists.syncOwnedToMyStuff() settings.markProfileSynced(now) - Log.i(TAG, "profile sync imported=$n interval=${interval.id}") + log.i { "profile sync imported=$n interval=${interval.id}" } } private suspend fun maybePartial(now: Long) { @@ -72,7 +72,7 @@ class LibrarySyncScheduler( if (last > 0L && now - last < interval.millis) continue val cp = checkpoints.get(row.id) if (cp?.status == SourceScanStatus.RUNNING) { - Log.i(TAG, "skip partial ${row.name} — scan already running") + log.i { "skip partial ${row.name} — scan already running" } continue } val type = when (SourceType.from(row.type)) { @@ -83,10 +83,10 @@ class LibrarySyncScheduler( val indexed = catalog.countTracksForSource(type, row.id) val scanned = checkpoints.get(row.id)?.status == SourceScanStatus.DONE if (indexed <= 0 && !scanned) { - Log.i(TAG, "skip partial ${row.name} — no index yet") + log.i { "skip partial ${row.name} — no index yet" } continue } - Log.i(TAG, "partial sync '${row.name}' interval=${interval.id} indexed=$indexed") + log.i { "partial sync '${row.name}' interval=${interval.id} indexed=$indexed" } LibraryScanService.startRemote( context.applicationContext, force = false, @@ -98,7 +98,7 @@ class LibrarySyncScheduler( } companion object { - private const val TAG = "LibrarySync" + private val log = yuriLog("LibrarySync") private const val BOOT_DELAY_MS = 20_000L private const val POLL_MS = 60_000L } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/MetadataEditService.kt b/app/src/main/java/capital/yuri/yuriplayer/data/MetadataEditService.kt index 6ec0de2b..97ec477b 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/MetadataEditService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/MetadataEditService.kt @@ -7,7 +7,7 @@ import android.net.Uri import android.os.Build import android.os.ParcelFileDescriptor import android.provider.MediaStore -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.source.SourceType import capital.yuri.yuriplayer.player.engine.isNetworkUri import capital.yuri.yuriplayer.player.engine.isVirtualLibraryPath @@ -47,7 +47,7 @@ class MetadataEditService( runCatching { TagOptionSingleton.getInstance().isAndroid = true }.onFailure { - Log.w(TAG, "Could not set TagOptionSingleton.isAndroid", it) + log.w(it) { "Could not set TagOptionSingleton.isAndroid" } } } @@ -123,7 +123,7 @@ class MetadataEditService( libraryIndex.reloadFromCatalog() Result(1, 0, "Saved") } catch (e: Exception) { - Log.e(TAG, "saveSong failed key=${song.songKey}", e) + log.e(e) { "saveSong failed key=${song.songKey}" } Result(0, 1, e.message ?: "Write failed — need storage permission?") } } @@ -160,7 +160,7 @@ class MetadataEditService( mbid = null ) } - Log.i(TAG, "app cover cached $appCoverPath") + log.i { "app cover cached $appCoverPath" } } val coverFile = edit.coverBytes?.let { bytes -> @@ -175,7 +175,7 @@ class MetadataEditService( scanned += out.absolutePath out } catch (e: Exception) { - Log.d(TAG, "folder cover.jpg write skipped: ${e.message}") + log.d { "folder cover.jpg write skipped: ${e.message}" } null } } else null @@ -208,7 +208,7 @@ class MetadataEditService( ) ok++ } catch (e: Exception) { - Log.e(TAG, "saveAlbum track failed key=${song.songKey}", e) + log.e(e) { "saveAlbum track failed key=${song.songKey}" } failed++ } } @@ -247,7 +247,7 @@ class MetadataEditService( } if (dest.isFile && dest.length() > 0L) dest.absolutePath else null } catch (e: Exception) { - Log.w(TAG, "persistAppCover failed", e) + log.w(e) { "persistAppCover failed" } null } } @@ -313,7 +313,7 @@ class MetadataEditService( c.getString(idx)?.takeIf { it.isNotBlank() && File(it).isFile } } } catch (e: Exception) { - Log.d(TAG, "DATA query failed for $uri: ${e.message}") + log.d { "DATA query failed for $uri: ${e.message}" } null } @@ -358,7 +358,7 @@ class MetadataEditService( return true } } catch (e: Exception) { - Log.d(TAG, "openOutputStream wt failed for $uri: ${e.message}") + log.d { "openOutputStream wt failed for $uri: ${e.message}" } } try { context.contentResolver.openFileDescriptor(uri, "rwt")?.use { pfd -> @@ -368,7 +368,7 @@ class MetadataEditService( return true } } catch (e: Exception) { - Log.d(TAG, "openFileDescriptor rwt failed for $uri: ${e.message}") + log.d { "openFileDescriptor rwt failed for $uri: ${e.message}" } } try { context.contentResolver.openFileDescriptor(uri, "rw")?.use { pfd -> @@ -378,7 +378,7 @@ class MetadataEditService( return true } } catch (e: Exception) { - Log.d(TAG, "openFileDescriptor rw failed for $uri: ${e.message}") + log.d { "openFileDescriptor rw failed for $uri: ${e.message}" } } return false } @@ -433,10 +433,7 @@ class MetadataEditService( tag.setField(art) } catch (t: Throwable) { // FLAC embed often fails on Android; app cover cache still holds the image. - Log.d( - TAG, - "embedded art skipped for ${file.name}: ${t.javaClass.simpleName}: ${t.message}" - ) + log.d { "embedded art skipped for ${file.name}: ${t.javaClass.simpleName}: ${t.message}" } } } audio.commit() @@ -495,7 +492,7 @@ class MetadataEditService( runCatching { context.contentResolver.update(song.contentUri, values, null, null) }.onFailure { - Log.d(TAG, "MediaStore song update skipped: ${it.message}") + log.d { "MediaStore song update skipped: ${it.message}" } } } @@ -512,7 +509,7 @@ class MetadataEditService( runCatching { context.contentResolver.update(song.contentUri, values, null, null) }.onFailure { - Log.d(TAG, "MediaStore album update skipped: ${it.message}") + log.d { "MediaStore album update skipped: ${it.message}" } } } @@ -533,13 +530,13 @@ class MetadataEditService( ?: return@withContext null bytes to mime } catch (e: Exception) { - Log.e(TAG, "readImageBytes failed", e) + log.e(e) { "readImageBytes failed" } null } } companion object { - private const val TAG = "MetadataEdit" + private val log = yuriLog("MetadataEdit") private const val FRONT_COVER_PICTURE_TYPE = 3 } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/MetadataEnrichmentService.kt b/app/src/main/java/capital/yuri/yuriplayer/data/MetadataEnrichmentService.kt index 19705613..403f885d 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/MetadataEnrichmentService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/MetadataEnrichmentService.kt @@ -2,7 +2,7 @@ package capital.yuri.yuriplayer.data import android.content.Context import android.media.MediaMetadataRetriever -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.db.AlbumMetadataDao import capital.yuri.yuriplayer.data.db.AlbumMetadataEntity import capital.yuri.yuriplayer.data.source.MusicBrainzClient @@ -59,7 +59,7 @@ class MetadataEnrichmentService( fun enrichLibraryAsync(maxAlbums: Int = 80) { if (!settings.isAutomaticMetadataEnabled()) { - Log.i(TAG, "skip auto enrich — automatic metadata disabled") + log.i { "skip auto enrich — automatic metadata disabled" } return } scope.launch { @@ -71,11 +71,11 @@ class MetadataEnrichmentService( val albums = library.albums(taggedOnly = false) .filter { needsWorkCheap(it) } .take(maxAlbums) - Log.i(TAG, "auto enrich queue size=${albums.size}") + log.i { "auto enrich queue size=${albums.size}" } var done = 0 for (album in albums) { if (!settings.isAutomaticMetadataEnabled()) { - Log.i(TAG, "auto enrich cancelled mid-run") + log.i { "auto enrich cancelled mid-run" } break } enrichAlbum(album, force = false, probeEmbeddedArt = false) @@ -86,7 +86,7 @@ class MetadataEnrichmentService( } _statusMessage.value = if (done > 0) "Updated $done albums" else null } catch (e: Exception) { - Log.e(TAG, "enrichLibrary failed", e) + log.e(e) { "enrichLibrary failed" } _statusMessage.value = "Couldn't look up artwork" } finally { _busy.value = false @@ -103,7 +103,7 @@ class MetadataEnrichmentService( enrichAlbum(album, force = force, probeEmbeddedArt = true) _statusMessage.value = "Updated ${album.displayName}" } catch (e: Exception) { - Log.w(TAG, "enrichAlbum failed ${album.displayName}", e) + log.w(e) { "enrichAlbum failed ${album.displayName}" } _statusMessage.value = "Couldn't update ${album.displayName}" } finally { _busy.value = false @@ -123,7 +123,7 @@ class MetadataEnrichmentService( } _statusMessage.value = "Updated ${albums.size} albums" } catch (e: Exception) { - Log.e(TAG, "enrichAlbums failed", e) + log.e(e) { "enrichAlbums failed" } _statusMessage.value = "Couldn't look up artwork" } finally { _busy.value = false @@ -176,7 +176,7 @@ class MetadataEnrichmentService( return } - Log.i(TAG, "lookup \"${album.displayName}\" / \"${album.displayArtist}\" force=$force") + log.i { "lookup \"${album.displayName}\" / \"${album.displayArtist}\" force=$force" } var hit = client.searchRelease( artist = album.artist, album = album.name, @@ -204,7 +204,7 @@ class MetadataEnrichmentService( updatedAtMs = System.currentTimeMillis() ) ) - Log.w(TAG, "no MB hit for $key") + log.w { "no MB hit for $key" } return } @@ -229,9 +229,9 @@ class MetadataEnrichmentService( if (client.downloadFrontCover(hit.mbid, dest)) { coverPath = dest.absolutePath coverChanged = true - Log.i(TAG, "cover saved $coverPath for ${album.displayName}") + log.i { "cover saved $coverPath for ${album.displayName}" } } else { - Log.w(TAG, "cover download failed mbid=${hit.mbid}") + log.w { "cover download failed mbid=${hit.mbid}" } } } @@ -291,8 +291,7 @@ class MetadataEnrichmentService( } companion object { - private const val TAG = "MetaEnrich" - + private val log = yuriLog("MetaEnrich") fun sanitizeFileName(key: String): String = key.lowercase() .replace(Regex("[^a-z0-9._-]+"), "_") diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/MusicRepository.kt b/app/src/main/java/capital/yuri/yuriplayer/data/MusicRepository.kt index d412eebd..6dac8a1e 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/MusicRepository.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/MusicRepository.kt @@ -6,7 +6,7 @@ import android.media.MediaMetadataRetriever import android.media.MediaScannerConnection import android.net.Uri import android.provider.MediaStore -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import android.webkit.MimeTypeMap import androidx.documentfile.provider.DocumentFile import kotlinx.coroutines.Dispatchers @@ -25,7 +25,7 @@ class MusicRepository( ) { companion object { - private const val TAG = "YuriPlayer.Library" + private val log = yuriLog("Library") private val AUDIO_EXTENSIONS = setOf( "flac", "mp3", "ogg", "opus", "m4a", "mp4", "aac", "wav", "aiff", "aif", "wma", "alac" @@ -64,7 +64,7 @@ class MusicRepository( if (!byKey.containsKey(key)) byKey[key] = song } val withYear = byKey.values.count { it.year != null } - Log.i(TAG, "mediastore scan: ${byKey.size} tracks ($withYear with year)") + log.i { "mediastore scan: ${byKey.size} tracks ($withYear with year)" } return byKey.values.toList() } @@ -75,7 +75,7 @@ class MusicRepository( private fun scanManualTrees(): List { val trees = settings.getManualTreeUris() if (trees.isEmpty()) { - Log.w(TAG, "manual scan: no SAF trees configured") + log.w { "manual scan: no SAF trees configured" } return emptyList() } val byKey = LinkedHashMap() @@ -83,10 +83,10 @@ class MusicRepository( val treeUri = runCatching { Uri.parse(uriString) }.getOrNull() ?: continue val root = DocumentFile.fromTreeUri(context, treeUri) if (root == null || !root.isDirectory) { - Log.w(TAG, "manual scan: invalid tree $uriString") + log.w { "manual scan: invalid tree $uriString" } continue } - Log.i(TAG, "manual scan tree $uriString") + log.i { "manual scan tree $uriString" } walkDocumentTree(root, depth = 0, maxDepth = 12) { doc, parent -> val name = doc.name ?: return@walkDocumentTree if (!isAudioFileName(name)) return@walkDocumentTree @@ -113,7 +113,7 @@ class MusicRepository( ) } } - Log.i(TAG, "manual scan complete: ${byKey.size} tracks") + log.i { "manual scan complete: ${byKey.size} tracks" } return byKey.values.toList() } @@ -142,7 +142,7 @@ class MusicRepository( val children = try { dir.listFiles() } catch (e: Exception) { - Log.w(TAG, "listFiles failed for ${dir.uri}: ${e.message}") + log.w { "listFiles failed for ${dir.uri}: ${e.message}" } return } for (child in children) { @@ -170,13 +170,13 @@ class MusicRepository( .filter { it.isFile && isAudioPath(it.absolutePath) } .forEach { paths += it.absolutePath } } catch (e: Exception) { - Log.w(TAG, "walk failed ${root.absolutePath}", e) + log.w(e) { "walk failed ${root.absolutePath}" } } } if (paths.isEmpty()) return val unique = paths.distinct() - Log.i(TAG, "media-scan ${unique.size} paths") + log.i { "media-scan ${unique.size} paths" } suspendCancellableCoroutine { cont -> val left = AtomicInteger(unique.size) fun done() { @@ -188,11 +188,11 @@ class MusicRepository( unique.toTypedArray(), null ) { path, uri -> - Log.d(TAG, "scanned $path → $uri") + log.d { "scanned $path → $uri" } done() } } catch (e: Exception) { - Log.w(TAG, "media-scan failed", e) + log.w(e) { "media-scan failed" } if (cont.isActive) cont.resume(Unit) } } @@ -340,13 +340,13 @@ class MusicRepository( val seen = HashSet() roots.forEach { root -> if (!root.exists()) { - Log.w(TAG, "scan root missing: ${root.absolutePath}") + log.w { "scan root missing: ${root.absolutePath}" } return@forEach } - Log.i(TAG, "fs-scan ${root.absolutePath}") + log.i { "fs-scan ${root.absolutePath}" } try { root.walkTopDown().maxDepth(8).onFail { f, e -> - Log.w(TAG, "walk fail $f: ${e.message}") + log.w { "walk fail $f: ${e.message}" } } .filter { it.isFile && isAudioPath(it.absolutePath) } .forEach { file -> @@ -371,9 +371,9 @@ class MusicRepository( ) } } catch (e: SecurityException) { - Log.e(TAG, "fs-scan permission denied for ${root.absolutePath}", e) + log.e(e) { "fs-scan permission denied for ${root.absolutePath}" } } catch (e: Exception) { - Log.e(TAG, "fs-scan failed ${root.absolutePath}", e) + log.e(e) { "fs-scan failed ${root.absolutePath}" } } } return songs @@ -549,7 +549,7 @@ class MusicRepository( pathHint = path ) } catch (e: Exception) { - Log.d(TAG, "jaudio tags failed for $path: ${e.message}") + log.d { "jaudio tags failed for $path: ${e.message}" } FileTags() } } @@ -573,9 +573,9 @@ class MusicRepository( if (disc != null) tag.setField(FieldKey.DISC_NO, disc.toString()) if (!title.isNullOrBlank()) tag.setField(FieldKey.TITLE, title) audio.commit() - Log.i(TAG, "wrote inferred tags track=$track disc=$disc title='$title' for ${file.name}") + log.i { "wrote inferred tags track=$track disc=$disc title='$title' for ${file.name}" } } catch (e: Exception) { - Log.d(TAG, "inferred tag write skipped for $path: ${e.message}") + log.d { "inferred tag write skipped for $path: ${e.message}" } } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/organize/LibraryOrganizeService.kt b/app/src/main/java/capital/yuri/yuriplayer/data/organize/LibraryOrganizeService.kt index 0e3af526..4aabde4f 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/organize/LibraryOrganizeService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/organize/LibraryOrganizeService.kt @@ -2,7 +2,7 @@ package capital.yuri.yuriplayer.data.organize import android.content.Context import android.net.Uri -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import androidx.documentfile.provider.DocumentFile import capital.yuri.yuriplayer.data.LibraryIndex import capital.yuri.yuriplayer.data.Song @@ -122,7 +122,7 @@ class LibraryOrganizeService( _status.value = msg ApplyResult(moved, skipped, failed, msg) } catch (e: Exception) { - Log.e(TAG, "apply failed", e) + log.e(e) { "apply failed" } _status.value = e.message ApplyResult(0, 0, 1, e.message ?: "Organize failed") } finally { @@ -177,7 +177,7 @@ class LibraryOrganizeService( dir = when { existing != null && existing.isDirectory -> existing existing != null && existing.isFile -> { - Log.w(TAG, "path collision with file $seg") + log.w { "path collision with file $seg" } return false } else -> dir.createDirectory(seg) ?: return false @@ -220,7 +220,7 @@ class LibraryOrganizeService( src.delete() true } catch (e: Exception) { - Log.w(TAG, "move failed ${song.songKey}: ${e.message}") + log.w { "move failed ${song.songKey}: ${e.message}" } false } } @@ -238,6 +238,6 @@ class LibraryOrganizeService( } companion object { - private const val TAG = "LibraryOrganize" + private val log = yuriLog("LibraryOrganize") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/AudioDbArtistImageSource.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/AudioDbArtistImageSource.kt index 947be0de..8848d836 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/AudioDbArtistImageSource.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/AudioDbArtistImageSource.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.artistKey import capital.yuri.yuriplayer.http.url import io.ktor.client.HttpClient @@ -95,7 +95,7 @@ class AudioDbArtistImageSource( return try { JSONObject(body).optJSONArray("artists")?.optJSONObject(0) } catch (e: Exception) { - Log.w(TAG, "parse failed", e) + log.w(e) { "parse failed" } null } } @@ -105,13 +105,13 @@ class AudioDbArtistImageSource( val response = http.get(url) if (!response.status.isSuccess()) null else response.bodyAsText() } catch (e: Exception) { - Log.w(TAG, "GET $url", e) + log.w(e) { "GET $url" } null } } companion object { - private const val TAG = "AudioDbArtist" + private val log = yuriLog("AudioDbArtist") private const val API_KEY = "2" } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/BandsintownClient.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/BandsintownClient.kt index 0d8dec81..b14649ea 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/BandsintownClient.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/BandsintownClient.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.http.url import io.ktor.client.HttpClient import io.ktor.client.request.get @@ -30,7 +30,7 @@ class BandsintownClient( if (name.isEmpty()) return@withContext emptyList() val id = appId?.trim().orEmpty() if (id.isEmpty()) { - Log.i(TAG, "skip events for $name — no partner app_id configured") + log.i { "skip events for $name — no partner app_id configured" } return@withContext emptyList() } val requestUrl = url("https://rest.bandsintown.com") { @@ -41,20 +41,17 @@ class BandsintownClient( val body = try { val response = http.get(requestUrl) if (response.status.value == 403) { - Log.w( - TAG, - "403 for $name — app_id is not partner-approved. " + - "Request access at API@bandsintown.com or use a Bandsintown-for-Artists key." - ) + log.w { "403 for $name — app_id is not partner-approved. " + + "Request access at API@bandsintown.com or use a Bandsintown-for-Artists key." } return@withContext emptyList() } if (!response.status.isSuccess()) { - Log.w(TAG, "events ${response.status} for $name") + log.w { "events ${response.status} for $name" } return@withContext emptyList() } response.bodyAsText() } catch (e: Exception) { - Log.w(TAG, "events failed for $name", e) + log.w(e) { "events failed for $name" } return@withContext emptyList() } // API returns [] or error object @@ -83,14 +80,13 @@ class BandsintownClient( } } } catch (e: Exception) { - Log.w(TAG, "parse events failed", e) + log.w(e) { "parse events failed" } emptyList() } } companion object { - private const val TAG = "Bandsintown" - + private val log = yuriLog("Bandsintown") /** Deep-link fallback when API access is unavailable. */ fun publicArtistUrl(artistName: String): String { return url("https://www.bandsintown.com") { diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/DeezerArtistImageSource.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/DeezerArtistImageSource.kt index 45f7152b..2eb37424 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/DeezerArtistImageSource.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/DeezerArtistImageSource.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.http.url import io.ktor.client.HttpClient import io.ktor.client.request.get @@ -95,7 +95,7 @@ class DeezerArtistImageSource( ) } } catch (e: Exception) { - Log.w(TAG, "parse failed", e) + log.w(e) { "parse failed" } emptyList() } } @@ -105,12 +105,12 @@ class DeezerArtistImageSource( val response = http.get(url) if (!response.status.isSuccess()) null else response.bodyAsText() } catch (e: Exception) { - Log.w(TAG, "GET $url", e) + log.w(e) { "GET $url" } null } } companion object { - private const val TAG = "DeezerArtistImg" + private val log = yuriLog("DeezerArtistImg") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/DiscogsClient.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/DiscogsClient.kt index 78214e9b..bb4db6fc 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/DiscogsClient.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/DiscogsClient.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.http.UrlScope import capital.yuri.yuriplayer.http.url import io.ktor.client.HttpClient @@ -64,7 +64,7 @@ class DiscogsClient( } hits.sortedByDescending { ArtistNameMatch.score(wanted, it.title) } } catch (e: Exception) { - Log.w(TAG, "search parse failed", e) + log.w(e) { "search parse failed" } emptyList() } } @@ -78,7 +78,7 @@ class DiscogsClient( return try { JSONObject(body) } catch (e: Exception) { - Log.w(TAG, "json parse failed ${path.joinToString("/")}", e) + log.w(e) { "json parse failed ${path.joinToString("/")}" } null } } @@ -107,18 +107,18 @@ class DiscogsClient( } } if (!response.status.isSuccess()) { - Log.w(TAG, "GET $requestUrl → ${response.status}") + log.w { "GET $requestUrl → ${response.status}" } return@withLock null } response.bodyAsText() } catch (e: Exception) { - Log.w(TAG, "GET failed $requestUrl", e) + log.w(e) { "GET failed $requestUrl" } null } } companion object { - private const val TAG = "Discogs" + private val log = yuriLog("Discogs") private const val API = "https://api.discogs.com" private const val MIN_INTERVAL_MS = 1_100L } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/JellyfinArtistImageSource.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/JellyfinArtistImageSource.kt index 8e5209bd..42e1ac9c 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/JellyfinArtistImageSource.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/JellyfinArtistImageSource.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.db.SourceInstanceEntity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -70,7 +70,7 @@ class JellyfinArtistImageSource( username = user, password = secret ).getOrElse { - Log.w(TAG, "auth failed for ${server.name}: ${it.message}") + log.w { "auth failed for ${server.name}: ${it.message}" } null } } @@ -116,12 +116,12 @@ class JellyfinArtistImageSource( ) } } catch (e: Exception) { - Log.w(TAG, "searchArtists failed: ${e.message}") + log.w { "searchArtists failed: ${e.message}" } emptyList() } } companion object { - private const val TAG = "JellyfinArtistImg" + private val log = yuriLog("JellyfinArtistImg") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/JellyfinClient.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/JellyfinClient.kt index caf53563..ffac5b73 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/JellyfinClient.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/JellyfinClient.kt @@ -2,7 +2,7 @@ package capital.yuri.yuriplayer.data.source import android.content.Context import android.net.Uri -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.http.url import org.jellyfin.sdk.Jellyfin @@ -44,7 +44,7 @@ class JellyfinClient( val user = username.trim() require(user.isNotEmpty()) { "Username is empty" } - Log.i(TAG, "authenticate → $root user=$user (official SDK)") + log.i { "authenticate → $root user=$user (official SDK)" } val api = jellyfin.createApi(baseUrl = root) try { @@ -59,10 +59,7 @@ class JellyfinClient( api.update(accessToken = token) - Log.i( - TAG, - "auth ok server=${authenticationResult.serverId} userId=${userId.take(8)}…" - ) + log.i { "auth ok server=${authenticationResult.serverId} userId=${userId.take(8)}…" } Session( baseUrl = root, userId = userId, @@ -78,7 +75,7 @@ class JellyfinClient( } catch (e: ApiClientException) { error(e.message ?: "Jellyfin API error") } - }.onFailure { Log.w(TAG, "authenticate failed: ${it.message}") } + }.onFailure { log.w { "authenticate failed: ${it.message}" } } suspend fun listAudioItems(session: Session, limit: Int = 50_000): Result> = runCatching { @@ -87,7 +84,7 @@ class JellyfinClient( out += page }.getOrThrow() out - }.onFailure { Log.w(TAG, "listAudioItems failed: ${it.message}") } + }.onFailure { log.w { "listAudioItems failed: ${it.message}" } } enum class ListingMode { FULL, LIGHT } @@ -105,7 +102,7 @@ class JellyfinClient( startIndex = 0 ) result.totalRecordCount ?: 0 - }.onFailure { Log.w(TAG, "audioItemCount failed: ${it.message}") } + }.onFailure { log.w { "audioItemCount failed: ${it.message}" } } suspend fun listAudioItemsPaged( session: Session, @@ -127,7 +124,7 @@ class JellyfinClient( val wantImages = true if (start > 0) { - Log.i(TAG, "resume paging from startIndex=$start mode=$mode") + log.i { "resume paging from startIndex=$start mode=$mode" } } while (delivered < maxItems) { @@ -149,24 +146,21 @@ class JellyfinClient( totalHint = result.totalRecordCount ?: totalHint val raw = result.items.orEmpty() if (raw.isEmpty()) { - Log.i(TAG, "empty page start=$start totalHint=$totalHint page=$pageNum") + log.i { "empty page start=$start totalHint=$totalHint page=$pageNum" } break } val page = raw.mapNotNull { item -> runCatching { item.toSong(session) } - .onFailure { e -> Log.w(TAG, "toSong failed for '${item.name}': ${e.message}") } + .onFailure { e -> log.w { "toSong failed for '${item.name}': ${e.message}" } } .getOrNull() } val liveIds = raw.mapNotNull { item -> item.id?.toString()?.let { "jellyfin:$it" } } pageNum++ - Log.i( - TAG, - "page#$pageNum start=$start raw=${raw.size} mapped=${page.size} " + - "delivered=$delivered totalHint=$totalHint mode=$mode sort=$sortBy" - ) + log.i { "page#$pageNum start=$start raw=${raw.size} mapped=${page.size} " + + "delivered=$delivered totalHint=$totalHint mode=$mode sort=$sortBy" } onPage(page, start, totalHint, liveIds) delivered += raw.size @@ -175,9 +169,9 @@ class JellyfinClient( if (totalHint != null && start >= totalHint) break } - Log.i(TAG, "listAudioItemsPaged done delivered=$delivered totalHint=$totalHint pages=$pageNum") + log.i { "listAudioItemsPaged done delivered=$delivered totalHint=$totalHint pages=$pageNum" } delivered - }.onFailure { Log.w(TAG, "listAudioItemsPaged failed: ${it.message}") } + }.onFailure { log.w { "listAudioItemsPaged failed: ${it.message}" } } /** * Jellyfin Instant Mix — similar tracks from a seed item (song / album / artist id). @@ -202,7 +196,7 @@ class JellyfinClient( enableImageTypes = listOf(ImageType.PRIMARY) ) result.items.orEmpty().mapNotNull { it.toSong(session) } - }.onFailure { Log.w(TAG, "instantMixFromItem failed: ${it.message}") } + }.onFailure { log.w { "instantMixFromItem failed: ${it.message}" } } data class PlaylistRef( val id: String, @@ -237,7 +231,7 @@ class JellyfinClient( owned = item.canDelete == true ) } - }.onFailure { Log.w(TAG, "listPlaylists failed: ${it.message}") } + }.onFailure { log.w { "listPlaylists failed: ${it.message}" } } suspend fun playlistSongs(session: Session, playlistId: String): Result> = runCatching { @@ -257,7 +251,7 @@ class JellyfinClient( limit = 2_000 ) result.items.orEmpty().mapNotNull { it.toSong(session) } - }.onFailure { Log.w(TAG, "playlistSongs failed: ${it.message}") } + }.onFailure { log.w { "playlistSongs failed: ${it.message}" } } suspend fun searchAudio( session: Session, @@ -327,7 +321,7 @@ class JellyfinClient( limit = limit.coerceIn(1, 100) ) result.items.orEmpty().mapNotNull(map) - }.onFailure { Log.w(TAG, "search $kind failed: ${it.message}") } + }.onFailure { log.w { "search $kind failed: ${it.message}" } } fun streamUrl(session: Session, itemId: String, container: String? = null): String = url(session.baseUrl) { @@ -421,7 +415,7 @@ class JellyfinClient( } companion object { - private const val TAG = "JellyfinClient" + private val log = yuriLog("JellyfinClient") private const val PREFS = "jellyfin_client" private const val KEY_DEVICE_ID = "device_id" diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/LibraryFaviconStore.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/LibraryFaviconStore.kt index 4402175b..bdc28086 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/LibraryFaviconStore.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/LibraryFaviconStore.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.data.source import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.db.SourceInstanceEntity import capital.yuri.yuriplayer.http.url import io.ktor.client.HttpClient @@ -41,7 +41,7 @@ class LibraryFaviconStore( }.getOrNull() ?: continue val dest = File(dir, "${instance.id}.bin") dest.writeBytes(bytes) - Log.i(TAG, "favicon ${instance.name} ← $url ${bytes.size}B") + log.i { "favicon ${instance.name} ← $url ${bytes.size}B" } return@withContext dest } null @@ -53,6 +53,6 @@ class LibraryFaviconStore( } companion object { - private const val TAG = "LibraryFavicon" + private val log = yuriLog("LibraryFavicon") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/MusicBrainzArtistProfileProvider.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/MusicBrainzArtistProfileProvider.kt index ddf2bf7d..6eb247d4 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/MusicBrainzArtistProfileProvider.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/MusicBrainzArtistProfileProvider.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.data.source import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.artistKey import capital.yuri.yuriplayer.data.db.CatalogDao import kotlinx.coroutines.Dispatchers @@ -42,7 +42,7 @@ class MusicBrainzArtistProfileProvider( if (ok && dest.isFile && dest.length() > 0L) { imagePath = dest.absolutePath } else { - Log.w(TAG, "Failed to download artist image for $artistName from $imageUrl") + log.w { "Failed to download artist image for $artistName from $imageUrl" } } } @@ -74,6 +74,6 @@ class MusicBrainzArtistProfileProvider( } companion object { - private const val TAG = "MBArtist" + private val log = yuriLog("MBArtist") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/MusicBrainzClient.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/MusicBrainzClient.kt index 2df745bb..f809bc26 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/MusicBrainzClient.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/MusicBrainzClient.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.http.url import io.ktor.client.HttpClient import io.ktor.client.request.get @@ -168,7 +168,7 @@ class MusicBrainzClient( } } } catch (e: Exception) { - Log.w(TAG, "allWikidataP18 $qid", e) + log.w(e) { "allWikidataP18 $qid" } emptyList() } } @@ -229,7 +229,7 @@ class MusicBrainzClient( else -> exactId ?: bestId?.takeIf { bestScore >= 50 } } } catch (e: Exception) { - Log.w(TAG, "pickBestArtistMbid failed", e) + log.w(e) { "pickBestArtistMbid failed" } null } } @@ -258,7 +258,7 @@ class MusicBrainzClient( root.optJSONObject("originalimage")?.optString("source")?.takeIf { it.isNotBlank() } ?: root.optJSONObject("thumbnail")?.optString("source")?.takeIf { it.isNotBlank() } } catch (e: Exception) { - Log.w(TAG, "Wikipedia image failed", e) + log.w(e) { "Wikipedia image failed" } null } } @@ -379,7 +379,7 @@ class MusicBrainzClient( genres = parseTags(json) ) } catch (e: Exception) { - Log.w(TAG, "parse artist detail failed", e) + log.w(e) { "parse artist detail failed" } ArtistHit(mbid, "") } } @@ -405,7 +405,7 @@ class MusicBrainzClient( } best } catch (e: Exception) { - Log.w(TAG, "parse release search failed", e) + log.w(e) { "parse release search failed" } null } } @@ -415,12 +415,12 @@ class MusicBrainzClient( try { val response = http.get(url) if (!response.status.isSuccess()) { - Log.w(TAG, "GET $url → ${response.status}") + log.w { "GET $url → ${response.status}" } return@withLock null } response.bodyAsText() } catch (e: Exception) { - Log.w(TAG, "GET failed $url", e) + log.w(e) { "GET failed $url" } null } } @@ -442,7 +442,7 @@ class MusicBrainzClient( dest.isFile && dest.length() > 0L } } catch (e: Exception) { - Log.w(TAG, "download failed $url", e) + log.w(e) { "download failed $url" } false } } @@ -461,7 +461,7 @@ class MusicBrainzClient( .replace(")", "\\)") companion object { - private const val TAG = "MusicBrainz" + private val log = yuriLog("MusicBrainz") /** Anonymous MB guideline ≈ 1 req/s. */ private const val MIN_INTERVAL_MS = 1_100L } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/RemotePlaylistService.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/RemotePlaylistService.kt index 6d4e5b29..51f3c8ac 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/RemotePlaylistService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/RemotePlaylistService.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.Playlist import capital.yuri.yuriplayer.data.PlaylistRepository import capital.yuri.yuriplayer.data.Song @@ -65,7 +65,7 @@ class RemotePlaylistService( if (playlists.get(remote.stableId) != null) continue if (importToLocal(remote) != null) imported++ } - if (imported > 0) Log.i(TAG, "imported $imported owned remote playlists") + if (imported > 0) log.i { "imported $imported owned remote playlists" } return imported } @@ -125,7 +125,7 @@ class RemotePlaylistService( val user = row.username ?: return null val secret = row.secret ?: return null return jellyfin.authenticate(url, user, secret).getOrElse { - Log.w(TAG, "jellyfin auth for playlists failed: ${it.message}") + log.w { "jellyfin auth for playlists failed: ${it.message}" } null } } @@ -142,6 +142,6 @@ class RemotePlaylistService( } companion object { - private const val TAG = "RemotePlaylists" + private val log = yuriLog("RemotePlaylists") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/SubsonicArtistImageSource.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/SubsonicArtistImageSource.kt index 59b98b11..85291302 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/SubsonicArtistImageSource.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/SubsonicArtistImageSource.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.db.SourceInstanceEntity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -67,12 +67,12 @@ class SubsonicArtistImageSource( password = secret ) return client.ping(base).getOrElse { - Log.w(TAG, "ping failed for ${server.name}: ${it.message}") + log.w { "ping failed for ${server.name}: ${it.message}" } null } } companion object { - private const val TAG = "SubsonicArtistImg" + private val log = yuriLog("SubsonicArtistImg") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/SubsonicClient.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/SubsonicClient.kt index a52b5c3d..0004406d 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/SubsonicClient.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/SubsonicClient.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.data.source import android.net.Uri -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.http.UrlScope import capital.yuri.yuriplayer.http.url @@ -65,7 +65,7 @@ class SubsonicClient( openSubsonic = open, serverVersion = resp.version ?: resp.serverVersion ) - }.onFailure { Log.w(TAG, "ping failed: ${it.message}") } + }.onFailure { log.w { "ping failed: ${it.message}" } } /** * Paged album index via getAlbumList2 (alphabeticalByName). @@ -100,7 +100,7 @@ class SubsonicClient( offset = offset, exhausted = refs.size < pageSize ) - }.onFailure { Log.w(TAG, "listAlbumsPage failed: ${it.message}") } + }.onFailure { log.w { "listAlbumsPage failed: ${it.message}" } } suspend fun listSongsForAlbum(session: Session, albumId: String): Result> = runCatching { @@ -117,7 +117,7 @@ class SubsonicClient( albumArtistFallback = album.resolvedAlbumArtist() ) } - }.onFailure { Log.w(TAG, "listSongsForAlbum($albumId) failed: ${it.message}") } + }.onFailure { log.w { "listSongsForAlbum($albumId) failed: ${it.message}" } } /** * Streams all songs via album list paging. Prefer this for large Navidrome libraries. @@ -148,7 +148,7 @@ class SubsonicClient( var albumFetchFailed = false for (album in page.albums) { val songs = listSongsForAlbum(session, album.id).getOrElse { - Log.w(TAG, "getAlbum failed ${album.id}: ${it.message}") + log.w { "getAlbum failed ${album.id}: ${it.message}" } albumFetchFailed = true emptyList() } @@ -164,7 +164,7 @@ class SubsonicClient( if (page.exhausted) break } delivered - }.onFailure { Log.w(TAG, "listSongsPaged failed: ${it.message}") } + }.onFailure { log.w { "listSongsPaged failed: ${it.message}" } } /** * Legacy full walk via indexes (small libraries / servers without albumList2). @@ -208,7 +208,7 @@ class SubsonicClient( ?: resp.similarSongs?.song ?: emptyList() children.mapNotNull { it.toSong(session) } - }.onFailure { Log.w(TAG, "similarSongs failed: ${it.message}") } + }.onFailure { log.w { "similarSongs failed: ${it.message}" } } data class LibraryHits( val songs: List, @@ -250,7 +250,7 @@ class SubsonicClient( ArtistHit(id = id, name = name, coverArt = a.coverArt) } LibraryHits(songs = songs, albums = albums, artists = artists) - }.onFailure { Log.w(TAG, "searchLibrary failed: ${it.message}") } + }.onFailure { log.w { "searchLibrary failed: ${it.message}" } } suspend fun searchArtists( session: Session, @@ -260,7 +260,7 @@ class SubsonicClient( searchLibrary(session, query, songCount = 0, albumCount = 0, artistCount = count) .getOrThrow() .artists - }.onFailure { Log.w(TAG, "searchArtists failed: ${it.message}") } + }.onFailure { log.w { "searchArtists failed: ${it.message}" } } data class ArtistHit( val id: String, @@ -291,7 +291,7 @@ class SubsonicClient( owner = p.owner ) } - }.onFailure { Log.w(TAG, "listPlaylists failed: ${it.message}") } + }.onFailure { log.w { "listPlaylists failed: ${it.message}" } } suspend fun playlistSongs(session: Session, playlistId: String): Result> = runCatching { @@ -300,7 +300,7 @@ class SubsonicClient( } val detail = json.decodeFromString(body).subsonicResponse.playlist detail?.entry.orEmpty().mapNotNull { it.toSong(session, albumCoverArt = detail?.coverArt) } - }.onFailure { Log.w(TAG, "playlistSongs failed: ${it.message}") } + }.onFailure { log.w { "playlistSongs failed: ${it.message}" } } fun streamUrl(session: Session, id: String): String = restUrl(session, "stream") { @@ -570,7 +570,7 @@ class SubsonicClient( ) companion object { - private const val TAG = "SubsonicClient" + private val log = yuriLog("SubsonicClient") /** 1.16.1 is widely implemented; OpenSubsonic servers accept it. */ private const val API_VERSION = "1.16.1" } diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/WikidataArtistImageSource.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/WikidataArtistImageSource.kt index 0428f108..678e2e9f 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/WikidataArtistImageSource.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/WikidataArtistImageSource.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.artistKey import capital.yuri.yuriplayer.http.url import io.ktor.client.HttpClient @@ -100,7 +100,7 @@ class WikidataArtistImageSource( hits } } catch (e: Exception) { - Log.w(TAG, "search failed", e) + log.w(e) { "search failed" } emptyList() } @@ -291,7 +291,7 @@ class WikidataArtistImageSource( ?.takeIf { it.isNotBlank() } } } catch (e: Exception) { - Log.w(TAG, "labels failed", e) + log.w(e) { "labels failed" } emptyList() } } @@ -301,13 +301,13 @@ class WikidataArtistImageSource( val response = http.get(url) if (!response.status.isSuccess()) null else response.bodyAsText() } catch (e: Exception) { - Log.w(TAG, "GET $url", e) + log.w(e) { "GET $url" } null } } companion object { - private const val TAG = "WikidataArtistImg" + private val log = yuriLog("WikidataArtistImg") private val MUSIC_INSTANCE_QIDS = setOf( "Q215380", "Q5741069", "Q1058743", "Q2088357", "Q588750", diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/source/WikipediaArtistImageSource.kt b/app/src/main/java/capital/yuri/yuriplayer/data/source/WikipediaArtistImageSource.kt index 6118865b..d0ebef9c 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/source/WikipediaArtistImageSource.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/source/WikipediaArtistImageSource.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.data.source -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.artistKey import capital.yuri.yuriplayer.http.url import io.ktor.client.HttpClient @@ -172,7 +172,7 @@ class WikipediaArtistImageSource( } } } catch (e: Exception) { - Log.w(TAG, "search failed for $qRaw", e) + log.w(e) { "search failed for $qRaw" } } if (seen.size >= 6) break } @@ -235,7 +235,7 @@ class WikipediaArtistImageSource( } out.distinct() } catch (e: Exception) { - Log.w(TAG, "pageImages failed", e) + log.w(e) { "pageImages failed" } emptyList() } } @@ -245,13 +245,13 @@ class WikipediaArtistImageSource( val response = http.get(url) if (!response.status.isSuccess()) null else response.bodyAsText() } catch (e: Exception) { - Log.w(TAG, "GET $url", e) + log.w(e) { "GET $url" } null } } companion object { - private const val TAG = "WikiArtistImg" + private val log = yuriLog("WikiArtistImg") /** Person / group signals. */ private val ARTIST_HINTS = listOf( diff --git a/app/src/main/java/capital/yuri/yuriplayer/data/theme/ThemeService.kt b/app/src/main/java/capital/yuri/yuriplayer/data/theme/ThemeService.kt index eb33f7c9..aa0c00b7 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/data/theme/ThemeService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/data/theme/ThemeService.kt @@ -4,7 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import androidx.compose.material3.ColorScheme import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance @@ -191,7 +191,7 @@ class ThemeService( } ?: return null scaleTo(decoded, maxSize) } catch (e: Exception) { - Log.w(TAG, "uri decode failed $uri", e) + log.w(e) { "uri decode failed $uri" } null } } @@ -218,7 +218,7 @@ class ThemeService( } companion object { - private const val TAG = "YuriPlayer.Theme" + private val log = yuriLog("Theme") private const val DISK_PREFS = "theme_color_cache" private const val DISK_ORDER = "__order" private const val MAX_DISK = 256 diff --git a/app/src/main/java/capital/yuri/yuriplayer/media/FfmpegService.kt b/app/src/main/java/capital/yuri/yuriplayer/media/FfmpegService.kt index ecfbf368..ee4d44f3 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/media/FfmpegService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/media/FfmpegService.kt @@ -2,7 +2,7 @@ package capital.yuri.yuriplayer.media import android.content.Context import android.os.Build -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -73,7 +73,7 @@ class FfmpegService(private val context: Context) { true }.getOrDefault(false) if (!copied) { - Log.w(TAG, "No FFmpeg asset for abi=$abi ($assetPath). Build native/ffmpeg first.") + log.w { "No FFmpeg asset for abi=$abi ($assetPath). Build native/ffmpeg first." } return null } } @@ -81,7 +81,7 @@ class FfmpegService(private val context: Context) { dest.setReadable(true, true) dest.setExecutable(true, true) if (!dest.canExecute()) { - Log.w(TAG, "FFmpeg binary not executable: $dest") + log.w { "FFmpeg binary not executable: $dest" } return null } binary = dest @@ -103,19 +103,19 @@ class FfmpegService(private val context: Context) { .redirectErrorStream(true) .directory(context.cacheDir) val proc = pb.start() - val log = proc.inputStream.bufferedReader().use { it.readText() } + val output = proc.inputStream.bufferedReader().use { it.readText() } val code = proc.waitFor() if (code != 0) { - Log.w(TAG, "ffmpeg exit=$code\n$log") + log.w { "ffmpeg exit=$code\n$output" } } code == 0 } catch (e: Exception) { - Log.w(TAG, "ffmpeg exec failed", e) + log.w(e) { "ffmpeg exec failed" } false } } companion object { - private const val TAG = "FfmpegService" + private val log = yuriLog("FfmpegService") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/network/HttpModule.kt b/app/src/main/java/capital/yuri/yuriplayer/network/HttpModule.kt index 021b6bc1..043674d4 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/network/HttpModule.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/network/HttpModule.kt @@ -1,5 +1,6 @@ package capital.yuri.yuriplayer.network +import capital.yuri.yuriplayer.core.log.yuriLog import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.HttpTimeout @@ -48,7 +49,7 @@ val httpModule = module { install(Logging) { logger = object : Logger { override fun log(message: String) { - android.util.Log.d("Ktor", redactSecrets(omitBinaryDump(message))) + yuriLog("Ktor").d { redactSecrets(omitBinaryDump(message)) } } } // BODY so auth failures are diagnosable; secrets stripped in [redactSecrets]. diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/ArtistRadio.kt b/app/src/main/java/capital/yuri/yuriplayer/player/ArtistRadio.kt index 52afdd57..36bf5ef9 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/ArtistRadio.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/ArtistRadio.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.player -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.AlbumItem import capital.yuri.yuriplayer.data.LibraryIndex import capital.yuri.yuriplayer.data.Song @@ -14,8 +14,7 @@ import kotlin.random.Random */ object ArtistRadio { - private const val TAG = "YuriPlayer.Radio" - + private val log = yuriLog("Radio") data class Pick( val album: AlbumItem, val source: ColdSource @@ -29,7 +28,7 @@ object ArtistRadio { ): Pick? { val artistName = resolveArtistName(seedSong, finishedSource) if (artistName == null) { - Log.i(TAG, "no artist from seed=${seedSong?.displayTitle} source=$finishedSource") + log.i { "no artist from seed=${seedSong?.displayTitle} source=$finishedSource" } return null } val artistNorm = artistKey(artistName) ?: return null @@ -42,21 +41,15 @@ object ArtistRadio { } if (candidates.isEmpty()) { - Log.i( - TAG, - "no candidates for artist='$artistName' (norm=$artistNorm) " + - "exclude=$excludeAlbumKeys libraryAlbums=${library.albums(taggedOnly = true).size}" - ) + log.i { "no candidates for artist='$artistName' (norm=$artistNorm) " + + "exclude=$excludeAlbumKeys libraryAlbums=${library.albums(taggedOnly = true).size}" } return null } val pick = candidates[Random.nextInt(candidates.size)] val key = albumKey(pick.name, pick.artist) - Log.i( - TAG, - "picked '${pick.displayName}' by ${pick.displayArtist} " + - "(${pick.trackCount} tracks) from ${candidates.size} candidates" - ) + log.i { "picked '${pick.displayName}' by ${pick.displayArtist} " + + "(${pick.trackCount} tracks) from ${candidates.size} candidates" } return Pick( album = pick, source = ColdSource( diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/MusicService.kt b/app/src/main/java/capital/yuri/yuriplayer/player/MusicService.kt index 53940069..1be30863 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/MusicService.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/MusicService.kt @@ -14,7 +14,7 @@ import android.os.Build import android.os.IBinder import android.os.PowerManager import android.os.SystemClock -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import androidx.core.app.NotificationCompat import capital.yuri.yuriplayer.activities.MainActivity import capital.yuri.yuriplayer.data.LibrarySettings @@ -123,7 +123,7 @@ class MusicService : Service() { sessionActivity = openPlayerPendingIntent(), onPlay = { if (SystemClock.elapsedRealtime() < ignoreSessionPlayUntilElapsed) { - Log.i(TAG, "session onPlay ignored — startup") + log.i { "session onPlay ignored — startup" } return@MusicServiceEngineHooks } play() @@ -141,20 +141,20 @@ class MusicService : Service() { else if (!userWantsPlay) releaseSleepLocks() }, onError = { message, recoverable -> - Log.e(TAG, "engine error: $message recoverable=$recoverable") + log.e { "engine error: $message recoverable=$recoverable" } if (recoverable && userWantsPlay && !recoveringAudio && !advancing) { recoverFromAudioGlitch() } } ) - Log.i(TAG, "engine=${engineHooks?.engineId}") + log.i { "engine=${engineHooks?.engineId}" } restorePlaybackState() startPeriodicPersist() startStallWatchdog() serviceScope.launch { librarySettings.streamQuality.drop(1).collect { q -> - Log.i(TAG, "stream quality → ${q.id} — rebuffer next") + log.i { "stream quality → ${q.id} — rebuffer next" } syncPreparedNext() } } @@ -197,7 +197,7 @@ class MusicService : Service() { private fun flushPendingRemoteRestore(autoPlay: Boolean): Boolean { val pending = pendingRemoteRestore ?: return false pendingRemoteRestore = null - Log.i(TAG, "flushPendingRemoteRestore pos=${pending.positionMs} autoPlay=$autoPlay") + log.i { "flushPendingRemoteRestore pos=${pending.positionMs} autoPlay=$autoPlay" } rebufferWindow(pending.positionMs, autoPlay = autoPlay, forceReload = true) return true } @@ -206,7 +206,7 @@ class MusicService : Service() { val current = queueManager.currentSong() ?: return clearStickySeek() pendingRemoteRestore = null - Log.i(TAG, "hardRestartCurrent '${current.displayTitle}' pos=$startPositionMs") + log.i { "hardRestartCurrent '${current.displayTitle}' pos=$startPositionMs" } rebufferWindow(startPositionMs.coerceAtLeast(0L), autoPlay = autoPlay, forceReload = true) } @@ -247,13 +247,10 @@ class MusicService : Service() { return } - Log.i( - TAG, - "rebufferWindow engine=${hooks.engineId} current='${current.displayTitle}' " + + log.i { "rebufferWindow engine=${hooks.engineId} current='${current.displayTitle}' " + "peekNext='${if (repeatOne) "(repeat-one)" else nextSong?.displayTitle}' " + "startMs=$startPositionMs autoPlay=$autoPlay force=$forceReload " + - "remote=${isRemoteSong(current)}" - ) + "remote=${isRemoteSong(current)}" } try { windowGeneration += 1 @@ -274,7 +271,7 @@ class MusicService : Service() { autoPlay = autoPlay ) } catch (e: Exception) { - Log.e(TAG, "rebufferWindow failed", e) + log.e(e) { "rebufferWindow failed" } } _nowPlaying.value = current @@ -361,7 +358,7 @@ class MusicService : Service() { } val upcoming = queueManager.upcoming(WARM_AHEAD) val next = upcoming.firstOrNull() - Log.i(TAG, "syncPreparedNext '${next?.displayTitle}' warm=${upcoming.size}") + log.i { "syncPreparedNext '${next?.displayTitle}' warm=${upcoming.size}" } hooks.setNext(next) warmUpcoming(upcoming) } @@ -406,7 +403,7 @@ class MusicService : Service() { val now = SystemClock.elapsedRealtime() if (now < ignoreAutoAdvancedUntilElapsed) { ignoreAutoAdvancedUntilElapsed = 0L - Log.i(TAG, "autoAdvanced ignored — queue already moved") + log.i { "autoAdvanced ignored — queue already moved" } return } if (advancing) return @@ -414,7 +411,7 @@ class MusicService : Service() { advancing = true try { val result = queueManager.advance(userInitiated = false) - Log.i(TAG, "autoAdvanced → '${result.song?.displayTitle}'") + log.i { "autoAdvanced → '${result.song?.displayTitle}'" } applyAdvanceKeepEngine(result) } finally { advancing = false @@ -432,10 +429,7 @@ class MusicService : Service() { val preparedId = hooks.preparedNextId() val expectedId = expected.toPlaybackMedia(quality = librarySettings.getStreamQuality()).mediaId if (preparedId == null || preparedId != expectedId) { - Log.i( - TAG, - "skip prepared mismatch want='${expected.displayTitle}' have=$preparedId" - ) + log.i { "skip prepared mismatch want='${expected.displayTitle}' have=$preparedId" } syncPreparedNext() return false } @@ -460,7 +454,7 @@ class MusicService : Service() { if (inUserSeekGuard()) { val target = stickySeekTargetMs.takeIf { it >= 0L } ?: engineHooks?.getPositionMs() ?: 0L - Log.i(TAG, "ENDED suppressed after user seek → reseek $target") + log.i { "ENDED suppressed after user seek → reseek $target" } rebufferWindow(target, autoPlay = true, forceReload = true) return } @@ -474,7 +468,7 @@ class MusicService : Service() { advancing = true try { if (advanceUsingPreparedNext(userInitiated = false)) { - Log.i(TAG, "onEnded → playPreparedNext") + log.i { "onEnded → playPreparedNext" } return } if (!claimEndOfWindow(fromUser = false)) return @@ -688,9 +682,9 @@ class MusicService : Service() { try { hooks.seekTo(target) if (hooks.getPlayWhenReady() && !hooks.isPlaying()) hooks.play() - Log.i(TAG, "seekTo target=$target (raw=$positionMs) duration=$duration") + log.i { "seekTo target=$target (raw=$positionMs) duration=$duration" } } catch (e: Exception) { - Log.w(TAG, "seekTo failed", e) + log.w(e) { "seekTo failed" } clearStickySeek() } persistState() @@ -735,10 +729,7 @@ class MusicService : Service() { positionMs = saved.positionMs, wasPlayWhenReady = false ) - Log.i( - TAG, - "restore deferred remote '${current?.displayTitle}' pos=${saved.positionMs}" - ) + log.i { "restore deferred remote '${current?.displayTitle}' pos=${saved.positionMs}" } } else { delay(RESTORE_PREPARE_DELAY_MS) rebufferWindow( @@ -756,7 +747,7 @@ class MusicService : Service() { ignoreSessionPlayUntilElapsed = SystemClock.elapsedRealtime() + STARTUP_IGNORE_SESSION_PLAY_MS engineHooks?.setSessionActive(true) - Log.i(TAG, "restore complete — paused until user plays") + log.i { "restore complete — paused until user plays" } } } } @@ -830,11 +821,8 @@ class MusicService : Service() { if (nearEnd && userWantsPlay && frozenFor >= NEAR_END_ADVANCE_MS && (wantPlay || buffering) ) { - Log.i( - TAG, - "near-end freeze ${frozenFor}ms pos=$pos/$duration " + - "playing=$playing buffering=$buffering wantPlay=$wantPlay → onEnded" - ) + log.i { "near-end freeze ${frozenFor}ms pos=$pos/$duration " + + "playing=$playing buffering=$buffering wantPlay=$wantPlay → onEnded" } onEngineEnded() stallSamplePos = -1L continue @@ -845,7 +833,7 @@ class MusicService : Service() { if (wantPlay && !playing && !buffering && pos > 1_000L && frozenFor >= ENDED_SILENCE_MS && remaining <= 5_000L ) { - Log.i(TAG, "silent near end ${frozenFor}ms pos=$pos/$duration → onEnded") + log.i { "silent near end ${frozenFor}ms pos=$pos/$duration → onEnded" } onEngineEnded() stallSamplePos = -1L continue @@ -858,7 +846,7 @@ class MusicService : Service() { if (userWantsPlay && !playing && !buffering && !inCall && frozenFor >= UNEXPECTED_PAUSE_MS && !nearEnd ) { - Log.i(TAG, "unexpected pause ${frozenFor}ms pos=$pos — resume") + log.i { "unexpected pause ${frozenFor}ms pos=$pos — resume" } acquireSleepLocks() hooks.play() } @@ -870,17 +858,14 @@ class MusicService : Service() { val threshold = if (remote) NETWORK_STALL_MS else STALL_MS if (userWantsPlay && !playing && !isInCall() && frozenFor >= UNEXPECTED_PAUSE_MS) { - Log.i(TAG, "unexpected pause ${frozenFor}ms pos=$pos — resume") + log.i { "unexpected pause ${frozenFor}ms pos=$pos — resume" } acquireSleepLocks() hooks.play() continue } if (frozenFor >= threshold) { - Log.w( - TAG, - "stall watchdog: pos frozen at $pos for ${frozenFor}ms remote=$remote — recovering" - ) + log.w { "stall watchdog: pos frozen at $pos for ${frozenFor}ms remote=$remote — recovering" } recoverFromAudioGlitch(atPositionMs = pos) } } @@ -925,7 +910,7 @@ class MusicService : Service() { } if (!lock.isHeld) lock.acquire() } catch (e: Exception) { - Log.w(TAG, "wakeLock", e) + log.w(e) { "wakeLock" } } if (!isRemoteSong(_nowPlaying.value)) return try { @@ -938,7 +923,7 @@ class MusicService : Service() { } if (!lock.isHeld) lock.acquire() } catch (e: Exception) { - Log.w(TAG, "wifiLock", e) + log.w(e) { "wifiLock" } } } @@ -946,12 +931,12 @@ class MusicService : Service() { try { if (wakeLock?.isHeld == true) wakeLock?.release() } catch (e: Exception) { - Log.w(TAG, "wakeLock release", e) + log.w(e) { "wakeLock release" } } try { if (wifiLock?.isHeld == true) wifiLock?.release() } catch (e: Exception) { - Log.w(TAG, "wifiLock release", e) + log.w(e) { "wifiLock release" } } } @@ -965,7 +950,7 @@ class MusicService : Service() { val wasPlaying = userWantsPlay || hooks?.getPlayWhenReady() == true || hooks?.isPlaying() == true - Log.w(TAG, "audio glitch → rebuffer at $pos autoPlay=$wasPlaying") + log.w { "audio glitch → rebuffer at $pos autoPlay=$wasPlaying" } rebufferWindow(pos, autoPlay = wasPlaying, forceReload = true) } finally { delay(600) @@ -1071,7 +1056,7 @@ class MusicService : Service() { .setShowActionsInCompactView(0, 1, 2) ) } catch (e: Throwable) { - Log.d(TAG, "MediaStyle skipped: ${e.message}") + log.d { "MediaStyle skipped: ${e.message}" } } } return builder.build() @@ -1118,7 +1103,7 @@ class MusicService : Service() { } override fun onTaskRemoved(rootIntent: Intent?) { - Log.i(TAG, "onTaskRemoved — stopping playback") + log.i { "onTaskRemoved — stopping playback" } stopForAppClosed() super.onTaskRemoved(rootIntent) } @@ -1134,7 +1119,7 @@ class MusicService : Service() { try { stateStore.save(snap, pos, playWhenReady = false) } catch (e: Exception) { - Log.w(TAG, "persist on close", e) + log.w(e) { "persist on close" } } if (historyStore.clearOnClose) { historyStore.clear() @@ -1156,7 +1141,7 @@ class MusicService : Service() { } companion object { - private const val TAG = "YuriPlayer" + private val log = yuriLog("MusicService") const val CHANNEL_ID = "yuri_playback" const val NOTIFICATION_ID = 42 const val ACTION_PLAY = "capital.yuri.yuriplayer.action.PLAY" diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/MusicServiceEngineHooks.kt b/app/src/main/java/capital/yuri/yuriplayer/player/MusicServiceEngineHooks.kt index 308a993c..d88a7d78 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/MusicServiceEngineHooks.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/MusicServiceEngineHooks.kt @@ -2,7 +2,7 @@ package capital.yuri.yuriplayer.player import android.app.PendingIntent import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.LibrarySettings import capital.yuri.yuriplayer.data.Song @@ -51,7 +51,7 @@ internal class MusicServiceEngineHooks( fun playWindow(song: Song, next: Song?, startPositionMs: Long, autoPlay: Boolean) { active = true host.playWindow(song, next, startPositionMs, autoPlay) - Log.i(TAG, "window via ${host.engineId} '${song.displayTitle}' next=${next?.displayTitle}") + log.i { "window via ${host.engineId} '${song.displayTitle}' next=${next?.displayTitle}" } } fun setNext(song: Song?) { @@ -115,6 +115,6 @@ internal class MusicServiceEngineHooks( } companion object { - private const val TAG = "EngineHooks" + private val log = yuriLog("EngineHooks") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/MusicServiceLocalEngine.kt b/app/src/main/java/capital/yuri/yuriplayer/player/MusicServiceLocalEngine.kt index c47c4c3c..be087cdb 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/MusicServiceLocalEngine.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/MusicServiceLocalEngine.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.player import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.LibrarySettings import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.player.engine.AudioPipeline @@ -54,14 +54,14 @@ class MusicServiceLocalEngine( override fun onIsPlayingChanged(playing: Boolean) = playingChanged(playing) override fun onError(message: String, recoverable: Boolean) { - Log.e(TAG, "engine($engineId) error: $message recoverable=$recoverable") + log.e { "engine($engineId) error: $message recoverable=$recoverable" } onEngineError(message, recoverable) } } init { engine.addListener(listener) - Log.i(TAG, "active engine=$engineId") + log.i { "active engine=$engineId" } } /** @@ -78,13 +78,10 @@ class MusicServiceLocalEngine( quality = quality.id, uri = current.uri.toString() ) - Log.i( - TAG, - "load engine=$engineId '${song.displayTitle}' " + + log.i { "load engine=$engineId '${song.displayTitle}' " + "uri=${current.uri} scheme=${current.uri.scheme} quality=${quality.id} " + "mime=${song.mimeType} live=${current.live} autoPlay=$autoPlay pos=$startPositionMs " + - "successor=${successor?.title}" - ) + "successor=${successor?.title}" } engine.setPlayWhenReady(autoPlay) engine.load(current, successor, startPositionMs) if (autoPlay) engine.play() @@ -125,6 +122,6 @@ class MusicServiceLocalEngine( } companion object { - private const val TAG = "SelectedEngine" + private val log = yuriLog("SelectedEngine") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/PlaybackHistoryStore.kt b/app/src/main/java/capital/yuri/yuriplayer/player/PlaybackHistoryStore.kt index 86da6d42..4f2f7a2e 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/PlaybackHistoryStore.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/PlaybackHistoryStore.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.player import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.data.json.AppJson import kotlinx.coroutines.flow.MutableStateFlow @@ -63,7 +63,7 @@ class PlaybackHistoryStore(context: Context) { while (current.size > maxEntries) current.removeAt(current.lastIndex) _entries.value = current persist() - Log.d(TAG, "record '${song.displayTitle}' size=${current.size}") + log.d { "record '${song.displayTitle}' size=${current.size}" } } fun clear() { @@ -72,7 +72,7 @@ class PlaybackHistoryStore(context: Context) { if (file.exists()) file.delete() } catch (_: Exception) { } - Log.i(TAG, "history cleared") + log.i { "history cleared" } } private fun trimToMax() { @@ -96,7 +96,7 @@ class PlaybackHistoryStore(context: Context) { tmp.delete() } } catch (e: Exception) { - Log.w(TAG, "persist failed", e) + log.w(e) { "persist failed" } } } @@ -106,7 +106,7 @@ class PlaybackHistoryStore(context: Context) { val dto = json.decodeFromString(HistoryFileDto.serializer(), file.readText()) dto.entries.take(maxEntries) } catch (e: Exception) { - Log.w(TAG, "load failed", e) + log.w(e) { "load failed" } emptyList() } } @@ -117,7 +117,7 @@ class PlaybackHistoryStore(context: Context) { } companion object { - private const val TAG = "YuriPlayer.History" + private val log = yuriLog("History") private const val FILE_NAME = "playback_history.json" private const val PREFS = "yuri_history_prefs" private const val KEY_MAX = "max_entries" diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/PlaybackStateStore.kt b/app/src/main/java/capital/yuri/yuriplayer/player/PlaybackStateStore.kt index 94743f55..431ab46d 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/PlaybackStateStore.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/PlaybackStateStore.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.player import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.data.json.AppJson import kotlinx.serialization.Serializable @@ -44,7 +44,7 @@ class PlaybackStateStore(context: Context) { tmp.delete() } } catch (e: Exception) { - Log.w(TAG, "Failed to save playback state", e) + log.w(e) { "Failed to save playback state" } } } @@ -81,7 +81,7 @@ class PlaybackStateStore(context: Context) { playWhenReady = dto.playWhenReady ) } catch (e: Exception) { - Log.w(TAG, "Failed to load playback state", e) + log.w(e) { "Failed to load playback state" } null } } @@ -121,7 +121,7 @@ class PlaybackStateStore(context: Context) { ) companion object { - private const val TAG = "PlaybackStateStore" + private val log = yuriLog("PlaybackStateStore") private const val FILE_NAME = "playback_state.json" /** v4: playedStack for Previous across restarts. */ private const val VERSION = 4 diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/PlayerController.kt b/app/src/main/java/capital/yuri/yuriplayer/player/PlayerController.kt index 38ba410d..19ef10b3 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/PlayerController.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/PlayerController.kt @@ -5,7 +5,7 @@ import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.IBinder -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import androidx.core.content.ContextCompat import capital.yuri.yuriplayer.data.AlbumItem import capital.yuri.yuriplayer.data.Song @@ -79,7 +79,7 @@ class PlayerController( try { pending?.invoke() } catch (e: Exception) { - Log.e(TAG, "pending action failed", e) + log.e(e) { "pending action failed" } } } @@ -139,7 +139,7 @@ class PlayerController( if (s != null) { action(s) } else { - Log.i(TAG, "service not bound yet — queueing action") + log.i { "service not bound yet — queueing action" } pendingAction = { service?.let(action) } } } @@ -327,6 +327,6 @@ class PlayerController( } companion object { - private const val TAG = "YuriPlayer.Ctrl" + private val log = yuriLog("Ctrl") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/QueueEventBridge.kt b/app/src/main/java/capital/yuri/yuriplayer/player/QueueEventBridge.kt index 98acf0bd..e4c6d513 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/QueueEventBridge.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/QueueEventBridge.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.player -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -23,15 +23,12 @@ class QueueEventBridge( when (event) { is QueueEvent.SourceChanged -> { autoPlay.noteSource(event.source) - Log.d(TAG, "SourceChanged ${event.source}") + log.d { "SourceChanged ${event.source}" } } is QueueEvent.Exhausted -> { - Log.i( - TAG, - "Exhausted seed=${event.seed?.displayTitle} " + + log.i { "Exhausted seed=${event.seed?.displayTitle} " + "source=${event.source} repeat=${event.repeatMode} " + - "(MusicService starts auto-play if enabled)" - ) + "(MusicService starts auto-play if enabled)" } } is QueueEvent.RepeatModeChanged, is QueueEvent.ShuffleChanged -> Unit @@ -41,6 +38,6 @@ class QueueEventBridge( } companion object { - private const val TAG = "YuriPlayer.QueueEvents" + private val log = yuriLog("QueueEvents") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/QueueManager.kt b/app/src/main/java/capital/yuri/yuriplayer/player/QueueManager.kt index 48183b4d..15ea237f 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/QueueManager.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/QueueManager.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.player -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.player.radio.RadioSession import capital.yuri.yuriplayer.player.radio.RadioSourcePrefs @@ -245,7 +245,7 @@ class QueueManager { val pick = helper.maybePick(seed, source, repeatMode) ?: return null val songs = pick.songs if (songs.isEmpty()) return null - Log.i(TAG, "radio/auto-play rescue → ${songs.size} tracks (${pick.source.title})") + log.i { "radio/auto-play rescue → ${songs.size} tracks (${pick.source.title})" } if (pick.session != null) { radioSession = pick.session shuffleEnabled = pick.session.prefs.shuffle @@ -280,11 +280,8 @@ class QueueManager { if (add.isEmpty()) return coldQueue.addAll(add) coldOriginal = coldOriginal + add - Log.i( - TAG, - "radio stock +${add.size} cold=${coldQueue.size} " + - "shuffle=${sess.prefs.shuffle} unit=${sess.prefs.shuffleUnit}" - ) + log.i { "radio stock +${add.size} cold=${coldQueue.size} " + + "shuffle=${sess.prefs.shuffle} unit=${sess.prefs.shuffleUnit}" } publish() } @@ -325,7 +322,7 @@ class QueueManager { keepRadio: Boolean = false ) { if (songs.isEmpty()) return - Log.i(TAG, "playSource size=${songs.size} start=$startIndex source=$source keepRadio=$keepRadio") + log.i { "playSource size=${songs.size} start=$startIndex source=$source keepRadio=$keepRadio" } floatingCurrent = null playedStack.clear() coldSource = source @@ -417,11 +414,8 @@ class QueueManager { } } - Log.i( - TAG, - "updateColdFromSource id=$sourceId size=${songs.size} " + - "cold=${coldQueue.size} shuffle=$shuffleEnabled (overrides reset)" - ) + log.i { "updateColdFromSource id=$sourceId size=${songs.size} " + + "cold=${coldQueue.size} shuffle=$shuffleEnabled (overrides reset)" } publish() return true } @@ -466,7 +460,7 @@ class QueueManager { } } radioUpcoming = emptyList() - Log.i(TAG, "replaceColdKeepingCurrent cold=${coldQueue.size}") + log.i { "replaceColdKeepingCurrent cold=${coldQueue.size}" } publish() if (radioSession?.active == true) ensureRadioStock() } @@ -856,7 +850,7 @@ class QueueManager { linearPrevious()?.let { prev -> indexInLane -= 1 publish() - Log.i(TAG, "skipPrevious linear → '${prev.displayTitle}' idx=$indexInLane") + log.i { "skipPrevious linear → '${prev.displayTitle}' idx=$indexInLane" } return AdvanceResult(song = prev) } @@ -894,7 +888,7 @@ class QueueManager { floatingCurrent = null publish() - Log.i(TAG, "skipPrevious history → '${prev.displayTitle}'") + log.i { "skipPrevious history → '${prev.displayTitle}'" } return AdvanceResult(song = prev) } @@ -931,7 +925,7 @@ class QueueManager { ) companion object { - private const val TAG = "YuriPlayer.Queue" + private val log = yuriLog("Queue") const val PREV_RESTART_MS = 3_000L private const val MAX_PLAYED_STACK = 200 } diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/SourceColdSync.kt b/app/src/main/java/capital/yuri/yuriplayer/player/SourceColdSync.kt index 2263ec11..d540094c 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/SourceColdSync.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/SourceColdSync.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.player -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.PlaylistRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -37,7 +37,7 @@ class SourceColdSync( .distinctUntilChanged() .collectLatest { playlistId -> if (playlistId == null) return@collectLatest - Log.i(TAG, "watching playlist $playlistId for cold sync") + log.i { "watching playlist $playlistId for cold sync" } playlists.observePlaylist(playlistId).collect { pl -> val songs = pl?.songs.orEmpty() if (songs.isEmpty()) return@collect @@ -55,6 +55,6 @@ class SourceColdSync( } companion object { - private const val TAG = "YuriPlayer.ColdSync" + private val log = yuriLog("ColdSync") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/engine/AudioPipeline.kt b/app/src/main/java/capital/yuri/yuriplayer/player/engine/AudioPipeline.kt index 3f122ae2..6c4910d4 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/engine/AudioPipeline.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/engine/AudioPipeline.kt @@ -1,16 +1,17 @@ package capital.yuri.yuriplayer.player.engine -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** * What we asked the server for vs what actually arrived. - * Filter logcat: `adb logcat YuriAudio:I *:S` + * Filter logcat: `adb logcat YuriPlayer.YuriAudio:I *:S` */ object AudioPipeline { const val TAG = "YuriAudio" + private val log = yuriLog(TAG) data class Snapshot( val title: String, @@ -42,7 +43,7 @@ object AudioPipeline { private val _last = MutableStateFlow(null) val last: StateFlow = _last.asStateFlow() - fun i(msg: String) = Log.i(TAG, msg) + fun i(msg: String) = log.i { msg } fun notePlay( title: String, diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/engine/Media3PlaybackEngine.kt b/app/src/main/java/capital/yuri/yuriplayer/player/engine/Media3PlaybackEngine.kt index 47cd190e..7d8a2072 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/engine/Media3PlaybackEngine.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/engine/Media3PlaybackEngine.kt @@ -4,7 +4,7 @@ import android.content.Context import android.net.Uri import android.os.Handler import android.os.Looper -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import androidx.annotation.OptIn import androidx.media3.common.AudioAttributes import androidx.media3.common.C @@ -133,7 +133,7 @@ class Media3PlaybackEngine( } override fun onPlayerError(error: PlaybackException) { - Log.e(TAG, "player error code=${error.errorCode} ${error.message}", error) + log.e(error) { "player error code=${error.errorCode} ${error.message}" } val recoverable = error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED || error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT || @@ -190,10 +190,7 @@ class Media3PlaybackEngine( player.setMediaItems(mediaItems, idx, startPositionMs.coerceAtLeast(0L)) player.prepare() _currentUri.value = playable.getOrNull(idx)?.uri - Log.i( - TAG, - "setWindow size=${playable.size} start=$idx network=${playable[idx].isNetwork} live=${playable[idx].live}" - ) + log.i { "setWindow size=${playable.size} start=$idx network=${playable[idx].isNetwork} live=${playable[idx].live}" } } private fun applyHeaders(items: List) { @@ -299,7 +296,7 @@ class Media3PlaybackEngine( applyHeaders(listOf(playable)) player.addMediaItem(playable.toMediaItem()) if (player.playbackState == Player.STATE_IDLE) player.prepare() - Log.i(TAG, "setNext '${playable.title}' network=${playable.isNetwork}") + log.i { "setNext '${playable.title}' network=${playable.isNetwork}" } } override fun hasPreparedNext(): Boolean = player.hasNextMediaItem() @@ -361,7 +358,7 @@ class Media3PlaybackEngine( } companion object { - private const val TAG = "Media3Engine" + private val log = yuriLog("Media3Engine") private const val NEXT_PRELOAD_US = 30_000_000L val DESCRIPTOR = PlaybackEngineDescriptor( diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/engine/PlaybackEngineFactory.kt b/app/src/main/java/capital/yuri/yuriplayer/player/engine/PlaybackEngineFactory.kt index 460cd5c6..52cc7fd6 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/engine/PlaybackEngineFactory.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/engine/PlaybackEngineFactory.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.player.engine import android.content.Context -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog /** * Which [PlaybackEngine] implementation plays **all** audio (local + remote). @@ -43,22 +43,21 @@ object PlaybackEngineCatalog { * One selection plays everything — no per-URI hybrid routing. */ object PlaybackEngineFactory { - private const val TAG = "EngineFactory" - + private val log = yuriLog("EngineFactory") fun create(context: Context, id: PlaybackEngineId): PlaybackEngine { val app = context.applicationContext return when (id) { PlaybackEngineId.MEDIA3 -> { - Log.i(TAG, "create Media3PlaybackEngine") + log.i { "create Media3PlaybackEngine" } Media3PlaybackEngine(app) } PlaybackEngineId.VLC -> { - Log.i(TAG, "create VlcPlaybackEngine") + log.i { "create VlcPlaybackEngine" } VlcPlaybackEngine(app) } PlaybackEngineId.FFMPEG -> { // Not implemented — fall back so playback still works. - Log.w(TAG, "FFmpeg engine not ready; falling back to VLC") + log.w { "FFmpeg engine not ready; falling back to VLC" } VlcPlaybackEngine(app) } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/engine/StreamPrefetcher.kt b/app/src/main/java/capital/yuri/yuriplayer/player/engine/StreamPrefetcher.kt index 6fda36c9..5a8b25f5 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/engine/StreamPrefetcher.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/engine/StreamPrefetcher.kt @@ -4,7 +4,7 @@ import android.content.Context import android.net.Uri import android.os.Handler import android.os.HandlerThread -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import java.io.File import java.io.FileOutputStream import java.net.HttpURLConnection @@ -91,7 +91,7 @@ class StreamPrefetcher private constructor(cacheDir: File) { return } tmp.delete() - Log.i(TAG, "prefetch start '${item.title}'") + log.i { "prefetch start '${item.title}'" } conn = (URL(item.uri.toString()).openConnection() as HttpURLConnection).apply { instanceFollowRedirects = true connectTimeout = 15_000 @@ -101,13 +101,13 @@ class StreamPrefetcher private constructor(cacheDir: File) { } val code = conn.responseCode if (code !in 200..299) { - Log.w(TAG, "prefetch HTTP $code '${item.title}'") + log.w { "prefetch HTTP $code '${item.title}'" } return } val total = conn.contentLengthLong.takeIf { it > 0 } ?: -1L AudioPipeline.noteHttp(conn.contentType, total.takeIf { it > 0 }, item.title) if (total > MAX_BYTES) { - Log.w(TAG, "prefetch skip '${item.title}' too large $total") + log.w { "prefetch skip '${item.title}' too large $total" } return } FileOutputStream(tmp).use { out -> @@ -120,7 +120,7 @@ class StreamPrefetcher private constructor(cacheDir: File) { out.write(buf, 0, n) written += n if (written > MAX_BYTES) { - Log.w(TAG, "prefetch abort '${item.title}' over cap") + log.w { "prefetch abort '${item.title}' over cap" } tmp.delete() return } @@ -141,9 +141,9 @@ class StreamPrefetcher private constructor(cacheDir: File) { tmp.delete() } synchronized(ready) { ready[item.mediaId] = dest } - Log.i(TAG, "prefetch ready '${item.title}' ${dest.length()}B of $total") + log.i { "prefetch ready '${item.title}' ${dest.length()}B of $total" } } catch (e: Exception) { - Log.w(TAG, "prefetch failed '${item.title}': ${e.message}") + log.w { "prefetch failed '${item.title}': ${e.message}" } tmp.delete() } finally { runCatching { conn?.disconnect() } @@ -162,7 +162,7 @@ class StreamPrefetcher private constructor(cacheDir: File) { } companion object { - private const val TAG = "StreamPrefetch" + private val log = yuriLog("StreamPrefetch") private const val MIN_BYTES = 256 * 1024L private const val MAX_BYTES = 256L * 1024L * 1024L private const val MAX_FILES = 8 diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/engine/VlcPlaybackEngine.kt b/app/src/main/java/capital/yuri/yuriplayer/player/engine/VlcPlaybackEngine.kt index ad0b34d5..45b931b0 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/engine/VlcPlaybackEngine.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/engine/VlcPlaybackEngine.kt @@ -7,7 +7,7 @@ import android.os.Handler import android.os.HandlerThread import android.os.Looper import android.os.ParcelFileDescriptor -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -114,7 +114,7 @@ class VlcPlaybackEngine( if (loadGeneration != eventGeneration) return@post if (mp !== active()) return@post if (!mp.isPlaying) { - Log.i(TAG, "unexpected pause → resume") + log.i { "unexpected pause → resume" } runCatching { mp.play() } } } @@ -127,11 +127,11 @@ class VlcPlaybackEngine( dispatch { onPlaybackStateChanged(PlaybackEngine.PlaybackState.IDLE) } if (!playWhenReady) return if (!windowHasPlayed) { - Log.i(TAG, "stopped before play — load, not EOF") + log.i { "stopped before play — load, not EOF" } return } if (currentIsLive()) { - Log.i(TAG, "live stopped → reconnect") + log.i { "live stopped → reconnect" } runCatching { mp.play() } return } @@ -148,7 +148,7 @@ class VlcPlaybackEngine( if (loadGeneration != eventGeneration) return if (currentIsLive()) { if (playWhenReady) { - Log.i(TAG, "live EOF → reconnect") + log.i { "live EOF → reconnect" } runCatching { mp.play() } } return @@ -165,7 +165,7 @@ class VlcPlaybackEngine( dispatch { onEnded() } } MediaPlayer.Event.EncounteredError -> { - Log.e(TAG, "VLC EncounteredError uri=${_currentUri.value}") + log.e { "VLC EncounteredError uri=${_currentUri.value}" } _isPlaying.value = false dispatch { onIsPlayingChanged(false) } if (playWhenReady && swapToPreparedNext()) { @@ -195,12 +195,12 @@ class VlcPlaybackEngine( event.buffering >= 100f if (!buffered) return nextReady = true - Log.i(TAG, "standby ready '${nextItem?.title}' warming=$nextWarming") + log.i { "standby ready '${nextItem?.title}' warming=$nextWarming" } // Stay playing at volume 0. Pause kills HTTP (`reading while // paused`) and the swapped stream then dies at EOF. } MediaPlayer.Event.EncounteredError -> { - Log.w(TAG, "standby prepare failed '${nextItem?.title}'") + log.w { "standby prepare failed '${nextItem?.title}'" } nextReady = false nextPreparing = false nextWarming = false @@ -400,7 +400,7 @@ class VlcPlaybackEngine( opened.fold( onSuccess = { input -> attachAndMaybePlay(playable, input, gen) }, onFailure = { e -> - Log.e(TAG, "open failed ${playable.uri}", e) + log.e(e) { "open failed ${playable.uri}" } dispatch { onError(e.message ?: "VLC open failed", recoverable = true) } } ) @@ -423,7 +423,7 @@ class VlcPlaybackEngine( mediaFromOpened(item, input) } catch (e: Exception) { input.close() - Log.e(TAG, "mediaFromOpened ${item.uri}", e) + log.e(e) { "mediaFromOpened ${item.uri}" } dispatch { onError(e.message ?: "VLC media failed", recoverable = true) } return } @@ -435,10 +435,7 @@ class VlcPlaybackEngine( } bindMedia(active(), media, isNext = false) dispatch { onMediaTransition(PlaybackEngine.TransitionReason.PLAYLIST) } - Log.i( - TAG, - "attached uri=${item.uri} network=${item.isNetwork} autoPlay=$playWhenReady" - ) + log.i { "attached uri=${item.uri} network=${item.isNetwork} autoPlay=$playWhenReady" } if (playWhenReady) active().play() } @@ -450,7 +447,7 @@ class VlcPlaybackEngine( pendingSeekMs = -1L val max = (len - 1_000L).coerceAtLeast(0L) val target = seek.coerceIn(0L, max) - Log.i(TAG, "seek ${seek}ms → ${target}ms of ${len}ms") + log.i { "seek ${seek}ms → ${target}ms of ${len}ms" } runCatching { mp.time = target } } @@ -471,7 +468,7 @@ class VlcPlaybackEngine( opened.fold( onSuccess = { input -> attachStandby(item, input, gen) }, onFailure = { e -> - Log.w(TAG, "standby open failed ${item.uri}: ${e.message}") + log.w { "standby open failed ${item.uri}: ${e.message}" } if (gen == nextGeneration) { nextReady = false nextPreparing = false @@ -497,7 +494,7 @@ class VlcPlaybackEngine( mediaFromOpened(item, input) } catch (e: Exception) { input.close() - Log.w(TAG, "standby media failed ${item.uri}", e) + log.w(e) { "standby media failed ${item.uri}" } nextReady = false nextPreparing = false return @@ -511,7 +508,7 @@ class VlcPlaybackEngine( val sp = standby() runCatching { sp.stop() } bindMedia(sp, media, isNext = true) - Log.i(TAG, "standby attached '${item.title}' network=${item.isNetwork}") + log.i { "standby attached '${item.title}' network=${item.isNetwork}" } nextPreparing = false nextReady = true } @@ -548,7 +545,7 @@ class VlcPlaybackEngine( np.play() _isPlaying.value = true buffering = false - Log.i(TAG, "swap → '${nxt.title}'") + log.i { "swap → '${nxt.title}'" } runCatching { old.stop() } runCatching { old.media = null } @@ -573,7 +570,7 @@ class VlcPlaybackEngine( private fun openInput(item: PlaybackMedia): OpenedInput { prefetcher.fileIfReady(item.mediaId)?.let { f -> - Log.i(TAG, "open disk '${item.title}' ${f.length()}B") + log.i { "open disk '${item.title}' ${f.length()}B" } return OpenedInput.Path(f.absolutePath) } val uri = item.uri @@ -598,12 +595,12 @@ class VlcPlaybackEngine( val resolver = appContext.contentResolver val afd = runCatching { resolver.openAssetFileDescriptor(uri, "r") }.getOrNull() if (afd != null) { - Log.i(TAG, "opened AFD uri=$uri offset=${afd.startOffset} len=${afd.length}") + log.i { "opened AFD uri=$uri offset=${afd.startOffset} len=${afd.length}" } return OpenedInput.ContentAfd(afd) } val pfd = resolver.openFileDescriptor(uri, "r") ?: throw IllegalStateException("Cannot open content uri: $uri") - Log.i(TAG, "opened PFD uri=$uri") + log.i { "opened PFD uri=$uri" } return OpenedInput.ContentPfd(pfd) } @@ -690,7 +687,7 @@ class VlcPlaybackEngine( } companion object { - private const val TAG = "VlcEngine" + private val log = yuriLog("VlcEngine") private const val FILE_CACHE_MS = 800 private const val NETWORK_CACHE_MS = 6_000 private const val PREFETCH_BYTES = 16 * 1024 * 1024 diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/radio/RadioEngine.kt b/app/src/main/java/capital/yuri/yuriplayer/player/radio/RadioEngine.kt index fa37f367..a3e89d23 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/radio/RadioEngine.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/radio/RadioEngine.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.player.radio -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.AlbumItem import capital.yuri.yuriplayer.data.LibraryIndex import capital.yuri.yuriplayer.data.LibrarySettings @@ -57,7 +57,7 @@ class RadioEngine( activeAlgorithm = RadioAlgorithmId.RELEASE_POOL session = withPrefs memory.note(ReleaseClassifier.releaseKey(album), ReleaseClassifier.kindOf(album)) - Log.i(TAG, "album radio '${withPrefs.displayName}' artists=${cfg.artistKeys.size}") + log.i { "album radio '${withPrefs.displayName}' artists=${cfg.artistKeys.size}" } return withPrefs } @@ -66,7 +66,7 @@ class RadioEngine( activeAlgorithm = RadioAlgorithmId.PLAYBACK session = sess poolReleases = releasesForArtist(artistName) - Log.i(TAG, "artist radio '${sess.displayName}' releases=${poolReleases.size}") + log.i { "artist radio '${sess.displayName}' releases=${poolReleases.size}" } return sess } @@ -80,7 +80,7 @@ class RadioEngine( configurePool(cfg) activeAlgorithm = RadioAlgorithmId.RELEASE_POOL session = withPrefs - Log.i(TAG, "playlist radio '${withPrefs.displayName}'") + log.i { "playlist radio '${withPrefs.displayName}'" } return withPrefs } @@ -107,11 +107,8 @@ class RadioEngine( } } } - Log.i( - TAG, - "adoptSession '${sess.displayName}' kind=${sess.kind} " + - "pool=${poolReleases.size} shuffle=${sess.prefs.shuffle}" - ) + log.i { "adoptSession '${sess.displayName}' kind=${sess.kind} " + + "pool=${poolReleases.size} shuffle=${sess.prefs.shuffle}" } } fun ensureAutoPlaySession(seedSong: Song?, finishedSource: ColdSource?): RadioSession { @@ -127,12 +124,9 @@ class RadioEngine( fun updatePrefs(prefs: RadioSourcePrefs) { val s = session ?: return session = s.copy(prefs = prefs) - Log.i( - TAG, - "prefs shuffle=${prefs.shuffle} unit=${prefs.shuffleUnit} max=${prefs.maxRadioQueue} " + + log.i { "prefs shuffle=${prefs.shuffle} unit=${prefs.shuffleUnit} max=${prefs.maxRadioQueue} " + "lib=${prefs.useLibraryDiscovery} jf=${prefs.useJellyfinInstantMix} " + - "sub=${prefs.useSubsonicSimilar} mono=${prefs.useMonochromeDiscovery}" - ) + "sub=${prefs.useSubsonicSimilar} mono=${prefs.useMonochromeDiscovery}" } } fun setShufflePrefs(enabled: Boolean): RadioSourcePrefs? { @@ -140,7 +134,7 @@ class RadioEngine( if (s.prefs.shuffle == enabled) return null val next = s.prefs.copy(shuffle = enabled) session = s.copy(prefs = next) - Log.i(TAG, "prefs shuffle=${next.shuffle} unit=${next.shuffleUnit} max=${next.maxRadioQueue}") + log.i { "prefs shuffle=${next.shuffle} unit=${next.shuffleUnit} max=${next.maxRadioQueue}" } return next } @@ -152,7 +146,7 @@ class RadioEngine( fun stopRadio() { session = null poolReleases = emptyList() - Log.i(TAG, "radio stopped") + log.i { "radio stopped" } } fun noteSource(source: ColdSource?) { @@ -171,7 +165,7 @@ class RadioEngine( val max = prefs.maxRadioQueue.coerceIn(1, 500) val releases = availableReleases(excludeKeys) if (releases.isEmpty()) { - Log.i(TAG, "planBatch: no releases") + log.i { "planBatch: no releases" } return null } @@ -198,11 +192,8 @@ class RadioEngine( prefs.shuffleUnit == RadioShuffleUnit.RELEASES -> "shuffle-releases" else -> "shuffle-songs" } - Log.i( - TAG, - "planBatch '${sess.displayName}' songs=${songs.size} " + - "shuffle=${prefs.shuffle} unit=${prefs.shuffleUnit} max=$max pool=${releases.size}" - ) + log.i { "planBatch '${sess.displayName}' songs=${songs.size} " + + "shuffle=${prefs.shuffle} unit=${prefs.shuffleUnit} max=$max pool=${releases.size}" } return RadioBatch( songs = songs, source = source, @@ -225,11 +216,11 @@ class RadioEngine( val pool = songPool(availableReleases(emptySet())) .filter { songKey(it) !in alreadyQueuedKeys } if (pool.isEmpty()) { - Log.i(TAG, "restock songs: empty pool (cold=$currentColdSize max=$max)") + log.i { "restock songs: empty pool (cold=$currentColdSize max=$max)" } return emptyList() } val pick = pool.shuffled(Random(System.nanoTime())).take(need) - Log.i(TAG, "restock songs +${pick.size} (cold was $currentColdSize / $max)") + log.i { "restock songs +${pick.size} (cold was $currentColdSize / $max)" } return pick } @@ -241,18 +232,15 @@ class RadioEngine( } else { nextOrderedRelease(alreadyQueuedKeys) } ?: run { - Log.i(TAG, "restock release: no candidate (cold=$currentColdSize max=$max)") + log.i { "restock release: no candidate (cold=$currentColdSize max=$max)" } return emptyList() } val tracks = sortedTracks(next).filter { songKey(it) !in alreadyQueuedKeys } if (tracks.isEmpty()) return emptyList() memory.note(ReleaseClassifier.releaseKey(next), ReleaseClassifier.kindOf(next)) - Log.i( - TAG, - "restock release +${tracks.size} from '${next.displayName}' " + - "(cold was $currentColdSize / $max)" - ) + log.i { "restock release +${tracks.size} from '${next.displayName}' " + + "(cold was $currentColdSize / $max)" } return tracks } @@ -263,7 +251,7 @@ class RadioEngine( ): RadioBatch? { val radioActive = session?.active == true if (!radioActive && !settings.isAutoPlayRecommendedEnabled()) { - Log.d(TAG, "skip — no session & auto-play off") + log.d { "skip — no session & auto-play off" } return null } if (repeatMode != RepeatMode.OFF) return null @@ -411,6 +399,6 @@ class RadioEngine( s.path?.lowercase() ?: s.contentUri.toString() companion object { - private const val TAG = "YuriPlayer.Radio" + private val log = yuriLog("Radio") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/radio/RadioPlaybackAlgorithm.kt b/app/src/main/java/capital/yuri/yuriplayer/player/radio/RadioPlaybackAlgorithm.kt index 9f354843..7ec8b53c 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/radio/RadioPlaybackAlgorithm.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/radio/RadioPlaybackAlgorithm.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.player.radio -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.AlbumItem import capital.yuri.yuriplayer.data.artistKey import capital.yuri.yuriplayer.player.ColdSource @@ -34,14 +34,14 @@ class RadioPlaybackAlgorithm( ): RadioPick? { val focus = context.focusArtistKeys if (focus.isEmpty()) { - Log.i(TAG, "no focus artists") + log.i { "no focus artists" } return null } val all = catalog.albums().filter { it.songs.isNotEmpty() } val sameArtist = all.filter { albumMatchesFocus(it, focus) } if (sameArtist.isEmpty()) { - Log.i(TAG, "no albums for focus=$focus") + log.i { "no albums for focus=$focus" } return null } @@ -139,6 +139,6 @@ class RadioPlaybackAlgorithm( } companion object { - private const val TAG = "YuriPlayer.RadioPlay" + private val log = yuriLog("RadioPlay") } } diff --git a/app/src/main/java/capital/yuri/yuriplayer/player/radio/ReleasePoolAlgorithm.kt b/app/src/main/java/capital/yuri/yuriplayer/player/radio/ReleasePoolAlgorithm.kt index b7af3fc6..e75f3e09 100644 --- a/app/src/main/java/capital/yuri/yuriplayer/player/radio/ReleasePoolAlgorithm.kt +++ b/app/src/main/java/capital/yuri/yuriplayer/player/radio/ReleasePoolAlgorithm.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.player.radio -import android.util.Log +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.data.AlbumItem import capital.yuri.yuriplayer.data.artistKey import capital.yuri.yuriplayer.player.ColdSource @@ -31,7 +31,7 @@ class ReleasePoolAlgorithm( context.focusArtistKeys).toSet() if (artistFocus.isEmpty() && config.genreKeys.isEmpty()) { - Log.i(TAG, "empty pool") + log.i { "empty pool" } return null } @@ -55,13 +55,13 @@ class ReleasePoolAlgorithm( // Stub: merge external candidates when privacy gate opens. val remote = external.fetchCandidates(context) if (remote.isNotEmpty()) { - Log.i(TAG, "external returned ${remote.size} (not persisted)") + log.i { "external returned ${remote.size} (not persisted)" } pool = pool + remote.filter { it.songs.isNotEmpty() } } } if (pool.isEmpty()) { - Log.i(TAG, "no local releases in pool artists=$artistFocus") + log.i { "no local releases in pool artists=$artistFocus" } return null } @@ -79,7 +79,7 @@ class ReleasePoolAlgorithm( val album = filtered[Random.nextInt(filtered.size)] val key = ReleaseClassifier.releaseKey(album) val kind = ReleaseClassifier.kindOf(album) - Log.i(TAG, "pool pick '${album.displayName}' kind=$kind from ${filtered.size}") + log.i { "pool pick '${album.displayName}' kind=$kind from ${filtered.size}" } return RadioPick( album = album, source = ColdSource(ColdSourceType.ALBUM, key, album.name), @@ -99,6 +99,6 @@ class ReleasePoolAlgorithm( } companion object { - private const val TAG = "YuriPlayer.RadioPool" + private val log = yuriLog("RadioPool") } } diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.kt index eac9abc9..573dd467 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/log/YuriLog.kt @@ -7,13 +7,17 @@ package capital.yuri.yuriplayer.core.log * * ``` * val log = yuriLog("Vlcj") - * log.i { "play ${redactUrl(mrl)}" } + * log.i { "play ${redactSecrets(mrl)}" } * log.e(t) { "stream failed" } * * yuriLog("Index") { * d { "merge +$n" } * } * ``` + * + * Tags are short (`Vlcj`, `Queue`). Platforms prefix `YuriPlayer.`, so logcat is + * `YuriPlayer.Vlcj`. `YuriPlayer.` on the caller's tag is stripped so + * `yuriLog("YuriPlayer.Radio")` and `yuriLog("Radio")` log the same. */ enum class LogLevel { TRACE, DEBUG, INFO, WARN, ERROR } @@ -30,9 +34,14 @@ class YuriLogger(val tag: String) { } } -fun yuriLog(tag: String): YuriLogger = YuriLogger(tag) +fun yuriLog(tag: String): YuriLogger = YuriLogger(normalizeLogTag(tag)) + +inline fun yuriLog(tag: String, block: YuriLogger.() -> Unit) = yuriLog(tag).block() -inline fun yuriLog(tag: String, block: YuriLogger.() -> Unit) = YuriLogger(tag).block() +fun normalizeLogTag(tag: String): String { + val t = tag.removePrefix("YuriPlayer.") + return if (t.isBlank() || t == "YuriPlayer") "App" else t +} expect fun platformLog(level: LogLevel, tag: String, message: String, throwable: Throwable?) diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/log/YuriLogTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/log/YuriLogTest.kt index ac360e29..f3015a16 100644 --- a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/log/YuriLogTest.kt +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/log/YuriLogTest.kt @@ -1,6 +1,7 @@ package capital.yuri.yuriplayer.core.log import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -13,4 +14,13 @@ class YuriLogTest { assertTrue("t=***" in out) assertTrue("id=abc" in out) } + + @Test + fun normalizesTags() { + assertEquals("Radio", normalizeLogTag("YuriPlayer.Radio")) + assertEquals("Radio", normalizeLogTag("Radio")) + assertEquals("App", normalizeLogTag("YuriPlayer")) + assertEquals("App", normalizeLogTag("YuriPlayer.")) + assertEquals("Vlcj", yuriLog("YuriPlayer.Vlcj").tag) + } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt index 31013e4c..af28ddcb 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -1,6 +1,7 @@ package capital.yuri.yuriplayer.desktop import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.log.yuriLog import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -16,6 +17,8 @@ import javax.imageio.ImageIO * Secret covers stay in the file but are not the active slot after a cold start. */ class DesktopPlaylistStore(configDir: String) { + private val log = yuriLog("Playlist") + private val root = File(configDir, "playlists") private val jsonFile = File(root, "playlists.json") private val coversDir = File(root, "covers") @@ -227,7 +230,7 @@ class DesktopPlaylistStore(configDir: String) { val file = json.decodeFromString(jsonFile.readText()) _playlists.value = file.playlists.map { it.withEntries(it.orderedEntries()) } }.onFailure { - System.err.println("YuriPlayer.Playlist load failed: ${it.message}") + log.w { "load failed: ${it.message}" } } } @@ -236,7 +239,7 @@ class DesktopPlaylistStore(configDir: String) { root.mkdirs() jsonFile.writeText(json.encodeToString(PlaylistFile(_playlists.value))) }.onFailure { - System.err.println("YuriPlayer.Playlist save failed: ${it.message}") + log.w { "save failed: ${it.message}" } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt index de75be35..244bc7d2 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt @@ -1,6 +1,7 @@ package capital.yuri.yuriplayer.desktop.os.linux import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.core.os.OsMediaControls import org.freedesktop.dbus.DBusPath import org.freedesktop.dbus.annotations.DBusInterfaceName @@ -23,6 +24,8 @@ import kotlin.math.abs * GNOME/KDE media keys and `playerctl -p yuriplayer` talk to this. */ class MprisMediaControls : OsMediaControls { + private val log = yuriLog("Mpris") + private val callbacks = AtomicReference(null) private val state = AtomicReference(NowPlaying()) private var connection: DBusConnection? = null @@ -40,12 +43,11 @@ class MprisMediaControls : OsMediaControls { conn.requestBusName(BUS_NAME) connection = conn exported = obj - System.err.println("MPRIS: claimed $BUS_NAME") + log.w { "MPRIS: claimed $BUS_NAME" } announce(conn, obj) grabGnomeKeys(conn, callbacks) } catch (e: Exception) { - System.err.println("MPRIS unavailable: ${e.javaClass.simpleName}: ${e.message}") - e.printStackTrace() + log.w(e) { "MPRIS unavailable: ${e.javaClass.simpleName}: ${e.message}" } } } @@ -102,9 +104,9 @@ class MprisMediaControls : OsMediaControls { ) keys.GrabMediaPlayerKeys(APP_ID, UInt32(0)) gnomeKeys = keys - System.err.println("MPRIS: grabbed GNOME media keys") + log.w { "MPRIS: grabbed GNOME media keys" } }.onFailure { - System.err.println("MPRIS: GNOME media keys skipped (${it.message})") + log.w { "MPRIS: GNOME media keys skipped (${it.message})" } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt index 9cd2524c..b5e70227 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt @@ -1,6 +1,7 @@ package capital.yuri.yuriplayer.desktop.os.mac import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.core.os.OsMediaControls import com.sun.jna.Library import com.sun.jna.Native @@ -16,12 +17,14 @@ import java.util.concurrent.atomic.AtomicReference * accessibility is granted; otherwise the Compose window handles them. */ class MacNowPlayingControls : OsMediaControls { + private val log = yuriLog("MacMedia") + private val callbacks = AtomicReference(null) override fun attach(callbacks: OsMediaControls.Callbacks) { this.callbacks.set(callbacks) runCatching { MacMediaKeys.install(callbacks) } - .onFailure { System.err.println("macOS media keys: ${it.message}") } + .onFailure { log.w { "macOS media keys: ${it.message}" } } } override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt index dd3d5fcc..cac5c709 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt @@ -1,5 +1,6 @@ package capital.yuri.yuriplayer.desktop.player +import capital.yuri.yuriplayer.core.log.yuriLog import java.io.File /** @@ -11,6 +12,8 @@ import java.io.File * packages are still preferred when present. */ object LibVlcBootstrap { + private val log = yuriLog("LibVlc") + fun install(): File? { val dir = resolveDir() ?: return null val path = dir.canonicalPath @@ -23,7 +26,7 @@ object LibVlcBootstrap { if (plugins.isDirectory) { System.setProperty("VLC_PLUGIN_PATH", plugins.canonicalPath) } - System.err.println("LibVLC bundle: $path") + log.w { "LibVLC bundle: $path" } return dir } From 1fe8b7d39122879d9294b25f60010f57121d4641 Mon Sep 17 00:00:00 2001 From: Mey Date: Thu, 27 Aug 2026 08:53:56 -0500 Subject: [PATCH 61/69] =?UTF-8?q?chore(core):=20move=20shared=20data=20mod?= =?UTF-8?q?el=20+=20Room=20catalog;=20unify=20Android=20data=20layer=20ont?= =?UTF-8?q?o=20it;=20fix=20multi-source=20discography/artist=20dedupe.=20D?= =?UTF-8?q?esktop:=20migrate=20Track=E2=86=92Song,=20keep=20JSON=20index?= =?UTF-8?q?=20for=20now.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .idea/artifacts/components.xml | 8 + .idea/artifacts/components_jvm.xml | 8 + .idea/artifacts/core.xml | 8 + .idea/artifacts/core_jvm.xml | 8 + .idea/artifacts/mediasession_jvm.xml | 8 + .idea/compiler.xml | 2 +- .idea/gradle.xml | 4 + .idea/inspectionProfiles/Project_Default.xml | 7 +- .idea/kotlinc.xml | 7 + .idea/misc.xml | 2 +- ...yer__buildFfmpeg_.xml => Build_ffmpeg.xml} | 2 +- AGENTS.md | 224 + .../yuriplayer/activities/MainActivity.kt | 10 +- .../activities/ui/NowPlayingScreen.kt | 4 +- .../activities/ui/PlaylistDetailScreen.kt | 2 +- .../yuriplayer/activities/ui/QueuePanel.kt | 16 +- .../yuri/yuriplayer/data/AlbumArtResolver.kt | 18 +- .../yuri/yuriplayer/data/AlbumCoverPrefs.kt | 11 +- .../capital/yuri/yuriplayer/data/AlbumLog.kt | 94 +- .../yuri/yuriplayer/data/AlbumMerge.kt | 128 +- .../yuri/yuriplayer/data/AlbumResolve.kt | 49 +- .../yuri/yuriplayer/data/AlbumTrackExpand.kt | 338 +- .../yuri/yuriplayer/data/ArtistAlbumsLight.kt | 183 +- .../yuri/yuriplayer/data/ArtistCredits.kt | 170 +- .../yuri/yuriplayer/data/CatalogRepository.kt | 1066 +-- .../yuri/yuriplayer/data/CoverCandidates.kt | 4 +- .../yuriplayer/data/ExploreSearchService.kt | 4 +- .../yuri/yuriplayer/data/FilenameMetadata.kt | 79 +- .../yuri/yuriplayer/data/LibraryIndex.kt | 37 +- .../yuriplayer/data/MetadataEditService.kt | 24 +- .../data/MetadataEnrichmentService.kt | 5 +- .../yuri/yuriplayer/data/MusicRepository.kt | 12 +- .../yuriplayer/data/PlaylistRepository.kt | 8 +- .../java/capital/yuri/yuriplayer/data/Song.kt | 419 +- .../yuri/yuriplayer/data/db/CatalogDao.kt | 453 +- .../yuriplayer/data/db/CatalogEntities.kt | 172 +- .../capital/yuri/yuriplayer/data/db/Daos.kt | 220 +- .../yuri/yuriplayer/data/db/Entities.kt | 155 +- .../yuri/yuriplayer/data/db/YuriDatabase.kt | 55 +- .../yuri/yuriplayer/data/db/YuriMigrations.kt | 143 +- .../data/organize/LibraryOrganizeService.kt | 8 +- .../data/organize/OrganizeLayout.kt | 2 +- .../yuriplayer/data/source/JellyfinClient.kt | 4 +- .../yuriplayer/data/source/MusicSource.kt | 40 +- .../data/source/SourceInstanceRepository.kt | 72 +- .../yuriplayer/data/source/SubsonicClient.kt | 4 +- .../yuriplayer/data/text/StringSimilarity.kt | 253 +- .../capital/yuri/yuriplayer/di/AppModule.kt | 17 +- .../network/AndroidNetworkMonitor.kt | 41 + .../yuri/yuriplayer/player/MusicService.kt | 14 +- .../yuriplayer/player/MusicServiceAutoPlay.kt | 63 +- .../yuri/yuriplayer/player/QueueEvents.kt | 27 +- .../yuri/yuriplayer/player/QueueManager.kt | 931 +- .../yuri/yuriplayer/player/QueueModels.kt | 90 +- .../player/engine/PlaybackEngine.kt | 12 +- .../yuriplayer/player/radio/AlbumRadioSeed.kt | 96 +- .../player/radio/PlaylistRadioSeed.kt | 36 +- .../yuriplayer/player/radio/RadioAlgorithm.kt | 43 +- .../yuriplayer/player/radio/RadioEngine.kt | 403 +- .../yuriplayer/player/radio/RadioModels.kt | 133 +- .../player/radio/RadioPlaybackAlgorithm.kt | 143 +- .../player/radio/RecentReleaseMemory.kt | 46 +- .../player/radio/ReleaseClassifier.kt | 25 +- .../player/radio/ReleasePoolAlgorithm.kt | 103 +- .../yuriplayer/data/CatalogIdentityTest.kt | 3 +- .../yuriplayer/components/OfflineBanner.kt | 47 + .../yuriplayer/components/i18n/Strings.kt | 22 + .../components/layout/DesktopBentoLayout.kt | 208 +- .../yuriplayer/components/model/UiModels.kt | 57 +- .../components/player/BottomPlayerBar.kt | 22 +- .../components/player/WavySeekBar.kt | 183 + .../components/model/ArtistPageTest.kt | 93 + core/build.gradle.kts | 24 +- .../core/text/StringSimilarity.android.kt | 37 + .../data/db/YuriDatabase.android.kt | 20 + .../yuriplayer/core/library/SongIdentity.kt | 134 + .../yuriplayer/core/library/SongLibrary.kt | 172 + .../yuriplayer/core/library/SongSearch.kt | 59 + .../yuri/yuriplayer/core/library/Track.kt | 81 +- .../yuriplayer/core/library/TrackIdentity.kt | 100 +- .../yuriplayer/core/library/TrackSearch.kt | 58 +- .../yuri/yuriplayer/core/library/TrackSong.kt | 5 + .../yuriplayer/core/network/NetworkMonitor.kt | 13 + .../yuriplayer/core/os/OsMediaControls.kt | 17 +- .../yuri/yuriplayer/core/player/ColdSource.kt | 22 + .../yuriplayer/core/player/PlaybackEngine.kt | 19 +- .../yuriplayer/core/player/PlayerSession.kt | 521 +- .../yuri/yuriplayer/core/player/QueueLane.kt | 3 + .../yuri/yuriplayer/core/player/RepeatMode.kt | 14 +- .../core/source/SourceAvailabilityStore.kt | 30 + .../yuriplayer/core/text/StringSimilarity.kt | 211 + .../capital/yuri/yuriplayer/data/AlbumItem.kt | 25 + .../capital/yuri/yuriplayer/data/AlbumLog.kt | 84 + .../yuri/yuriplayer/data/AlbumMerge.kt | 129 + .../yuri/yuriplayer/data/AlbumResolve.kt | 50 + .../yuri/yuriplayer/data/AlbumTrackExpand.kt | 341 + .../yuri/yuriplayer/data/ArtistAlbumsLight.kt | 215 + .../yuri/yuriplayer/data/ArtistCredits.kt | 167 + .../yuri/yuriplayer/data/CatalogRepository.kt | 1042 +++ .../yuri/yuriplayer/data/CoverCandidate.kt | 23 + .../yuri/yuriplayer/data/CoverCandidates.kt | 79 + .../yuri/yuriplayer/data/FilenameMetadata.kt | 80 + .../yuri/yuriplayer/data/LocalLibrary.kt | 16 + .../yuri/yuriplayer/data/LocalScanner.kt | 14 + .../capital/yuri/yuriplayer/data/Song.kt | 435 + .../yuri/yuriplayer/data/db/CatalogDao.kt | 439 + .../yuriplayer/data/db/CatalogEntities.kt | 173 + .../capital/yuri/yuriplayer/data/db/Daos.kt | 221 + .../yuri/yuriplayer/data/db/Entities.kt | 156 + .../yuri/yuriplayer/data/db/YuriDatabase.kt | 52 + .../yuri/yuriplayer/data/db/YuriMigrations.kt | 137 + .../data/source/SourceInstanceRepository.kt | 72 + .../yuri/yuriplayer/data/source/SourceType.kt | 39 + .../yuriplayer/player/MusicServiceAutoPlay.kt | 64 + .../yuri/yuriplayer/player/QueueEvents.kt | 28 + .../yuri/yuriplayer/player/QueueManager.kt | 931 ++ .../yuri/yuriplayer/player/QueueModels.kt | 64 + .../yuriplayer/player/radio/AlbumRadioSeed.kt | 101 + .../player/radio/PlaylistRadioSeed.kt | 37 + .../yuriplayer/player/radio/RadioAlgorithm.kt | 44 + .../yuriplayer/player/radio/RadioEngine.kt | 400 + .../yuriplayer/player/radio/RadioModels.kt | 134 + .../player/radio/RadioPlaybackAlgorithm.kt | 144 + .../player/radio/RecentReleaseMemory.kt | 47 + .../player/radio/ReleaseClassifier.kt | 26 + .../player/radio/ReleasePoolAlgorithm.kt | 112 + .../core/library/LocalLibraryScanner.kt | 33 +- .../yuriplayer/core/source/JellyfinCatalog.kt | 26 +- .../yuriplayer/core/source/SubsonicCatalog.kt | 38 +- .../core/text/StringSimilarity.jvm.kt | 23 + .../yuriplayer/data/db/YuriDatabase.jvm.kt | 18 + .../core/library/AlbumCollapseTest.kt | 44 +- .../core/library/TrackCatalogKeyTest.kt | 28 +- .../core/library/TrackSearchTest.kt | 13 +- .../core/player/PlayerSessionTest.kt | 167 +- desktop/build.gradle.kts | 62 +- desktop/hs_err_pid1041994.log | 7724 +++++++++++++++++ .../yuriplayer/desktop/DesktopIndexStore.kt | 8 +- .../desktop/DesktopNetworkMonitor.kt | 55 + .../desktop/DesktopPlaybackStore.kt | 46 +- .../desktop/DesktopPlaylistStore.kt | 38 +- .../yuri/yuriplayer/desktop/DesktopSession.kt | 105 +- .../capital/yuri/yuriplayer/desktop/Main.kt | 19 +- .../yuri/yuriplayer/desktop/PlaylistLog.kt | 14 +- .../yuriplayer/desktop/di/DesktopModule.kt | 46 + .../desktop/os/MediasessionControls.kt | 163 + .../desktop/os/OsMediaControlsFactory.kt | 7 +- .../desktop/os/linux/MprisMediaControls.kt | 788 +- .../desktop/os/mac/MacNowPlayingControls.kt | 6 +- .../desktop/os/win/WindowsSmtcControls.kt | 6 +- .../desktop/player/DesktopPlayerHost.kt | 257 + .../desktop/player/VlcjPlaybackEngine.kt | 89 +- .../desktop/ui/AddToPlaylistDialog.kt | 61 +- .../yuriplayer/desktop/ui/DesktopExplore.kt | 57 +- .../desktop/ui/MetadataEditDialog.kt | 24 +- .../yuriplayer/desktop/ui/PlaylistPage.kt | 34 +- .../desktop/ui/SourcesPickerDialog.kt | 18 +- .../yuriplayer/desktop/ui/YuriDesktopApp.kt | 469 +- gradle.properties | 5 +- gradle/libs.versions.toml | 33 +- settings.gradle.kts | 1 + 162 files changed, 17411 insertions(+), 8184 deletions(-) create mode 100644 .idea/artifacts/components.xml create mode 100644 .idea/artifacts/components_jvm.xml create mode 100644 .idea/artifacts/core.xml create mode 100644 .idea/artifacts/core_jvm.xml create mode 100644 .idea/artifacts/mediasession_jvm.xml create mode 100644 .idea/kotlinc.xml rename .idea/runConfigurations/{YuriPlayer__buildFfmpeg_.xml => Build_ffmpeg.xml} (91%) create mode 100644 AGENTS.md create mode 100644 app/src/main/java/capital/yuri/yuriplayer/network/AndroidNetworkMonitor.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/OfflineBanner.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/i18n/Strings.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/player/WavySeekBar.kt create mode 100644 components/src/jvmTest/kotlin/capital/yuri/yuriplayer/components/model/ArtistPageTest.kt create mode 100644 core/src/androidMain/kotlin/capital/yuri/yuriplayer/core/text/StringSimilarity.android.kt create mode 100644 core/src/androidMain/kotlin/capital/yuri/yuriplayer/data/db/YuriDatabase.android.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/SongIdentity.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/SongLibrary.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/SongSearch.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/TrackSong.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/network/NetworkMonitor.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/ColdSource.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/player/QueueLane.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/source/SourceAvailabilityStore.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/text/StringSimilarity.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumItem.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumLog.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumMerge.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumResolve.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumTrackExpand.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/ArtistAlbumsLight.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/ArtistCredits.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/CatalogRepository.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/CoverCandidate.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/CoverCandidates.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/FilenameMetadata.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/LocalLibrary.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/LocalScanner.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/Song.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/db/CatalogDao.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/db/CatalogEntities.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/db/Daos.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/db/Entities.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/db/YuriDatabase.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/db/YuriMigrations.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/source/SourceInstanceRepository.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/source/SourceType.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/MusicServiceAutoPlay.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/QueueEvents.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/QueueManager.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/QueueModels.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/radio/AlbumRadioSeed.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/radio/PlaylistRadioSeed.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/radio/RadioAlgorithm.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/radio/RadioEngine.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/radio/RadioModels.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/radio/RadioPlaybackAlgorithm.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/radio/RecentReleaseMemory.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/radio/ReleaseClassifier.kt create mode 100644 core/src/commonMain/kotlin/capital/yuri/yuriplayer/player/radio/ReleasePoolAlgorithm.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/core/text/StringSimilarity.jvm.kt create mode 100644 core/src/jvmMain/kotlin/capital/yuri/yuriplayer/data/db/YuriDatabase.jvm.kt create mode 100644 desktop/hs_err_pid1041994.log create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopNetworkMonitor.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/di/DesktopModule.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/MediasessionControls.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/DesktopPlayerHost.kt diff --git a/.gitignore b/.gitignore index 13effbfd..4ad57411 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ local.properties keystore.properties *.jks *.keystore +.aider* diff --git a/.idea/artifacts/components.xml b/.idea/artifacts/components.xml new file mode 100644 index 00000000..1da95811 --- /dev/null +++ b/.idea/artifacts/components.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/components/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/components_jvm.xml b/.idea/artifacts/components_jvm.xml new file mode 100644 index 00000000..e9b6a24c --- /dev/null +++ b/.idea/artifacts/components_jvm.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/components/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/core.xml b/.idea/artifacts/core.xml new file mode 100644 index 00000000..c3439a25 --- /dev/null +++ b/.idea/artifacts/core.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/core/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/core_jvm.xml b/.idea/artifacts/core_jvm.xml new file mode 100644 index 00000000..c006f3c8 --- /dev/null +++ b/.idea/artifacts/core_jvm.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/core/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/mediasession_jvm.xml b/.idea/artifacts/mediasession_jvm.xml new file mode 100644 index 00000000..779652f2 --- /dev/null +++ b/.idea/artifacts/mediasession_jvm.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/mediasession/build/libs + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index bcf4711e..b589d56e 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 02c4aa5e..e3ada731 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -10,6 +10,10 @@ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index bd7dede3..157f4bf3 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -25,6 +25,12 @@ + + + + @@ -46,6 +52,5 @@ - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 00000000..2e4da65a --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 29a5cc0c..ca6c843f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/.idea/runConfigurations/YuriPlayer__buildFfmpeg_.xml b/.idea/runConfigurations/Build_ffmpeg.xml similarity index 91% rename from .idea/runConfigurations/YuriPlayer__buildFfmpeg_.xml rename to .idea/runConfigurations/Build_ffmpeg.xml index 3203d0b0..217d185c 100644 --- a/.idea/runConfigurations/YuriPlayer__buildFfmpeg_.xml +++ b/.idea/runConfigurations/Build_ffmpeg.xml @@ -1,5 +1,5 @@ - + (0x0000000436184108) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.331 Thread 0x00007f78d48e9190 Exception (0x00000004362371c0) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.331 Thread 0x00007f78d48e9190 Exception (0x0000000436240d48) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.351 Thread 0x00007f78d48e9190 Exception (0x00000004363316f8) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.351 Thread 0x00007f78d48e9190 Exception (0x0000000436342da8) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.358 Thread 0x00007f78d48e9190 Exception (0x00000004363a7210) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.375 Thread 0x00007f78d48e9190 Exception (0x000000043644ec98) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.377 Thread 0x00007f78d48e9190 Exception (0x00000004364a2648) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.453 Thread 0x00007f78d48e9190 Exception (0x000000045ec5def8) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.476 Thread 0x00007f78d48e9190 Exception (0x0000000436170dc8) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.476 Thread 0x00007f78d48e9190 Exception (0x00000004361812e0) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.478 Thread 0x00007f78d48e9190 Exception (0x00000004361b4d88) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.556 Thread 0x00007f78d4740c60 Exception (0x0000000436450878) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 1.556 Thread 0x00007f78d4740c60 Exception (0x00000004366558c8) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 2.495 Thread 0x00007f78d48e9190 Exception (0x00000004367fe430) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 6.469 Thread 0x00007f78d48e9190 Exception (0x000000045efb6808) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 91.755 Thread 0x00007f78d48e9190 Exception (0x000000045e33f820) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 91.869 Thread 0x00007f78d48e9190 Exception (0x000000045e456900) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 91.876 Thread 0x00007f78d48e9190 Exception (0x000000045e4ba500) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] +Event: 2047.116 Thread 0x00007f7868602f40 Exception (0x000000045e5ed720) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 882] + +VM Operations (20 events): +Event: 1800.461 Executing non-safepoint VM operation: RendezvousGCThreads +Event: 1800.461 Executing non-safepoint VM operation: RendezvousGCThreads done +Event: 1860.475 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) +Event: 1860.475 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) done +Event: 1860.475 Executing non-safepoint VM operation: RendezvousGCThreads +Event: 1860.475 Executing non-safepoint VM operation: RendezvousGCThreads done +Event: 1920.489 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) +Event: 1920.489 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) done +Event: 1920.489 Executing non-safepoint VM operation: RendezvousGCThreads +Event: 1920.489 Executing non-safepoint VM operation: RendezvousGCThreads done +Event: 1980.503 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) +Event: 1980.503 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) done +Event: 1980.503 Executing non-safepoint VM operation: RendezvousGCThreads +Event: 1980.503 Executing non-safepoint VM operation: RendezvousGCThreads done +Event: 2040.517 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) +Event: 2040.517 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) done +Event: 2040.517 Executing non-safepoint VM operation: RendezvousGCThreads +Event: 2040.517 Executing non-safepoint VM operation: RendezvousGCThreads done +Event: 2047.114 Executing non-safepoint VM operation: HandshakeAllThreads (Deoptimize) +Event: 2047.114 Executing non-safepoint VM operation: HandshakeAllThreads (Deoptimize) done + +Memory protections (20 events): +Event: 4.438 Protecting memory [0x00007f784db00000,0x00007f784db04000] with protection modes 0 +Event: 4.532 Protecting memory [0x00007f78a05f7000,0x00007f78a05fb000] with protection modes 0 +Event: 4.599 Protecting memory [0x00007f78a04f6000,0x00007f78a04fa000] with protection modes 0 +Event: 6.501 Protecting memory [0x00007f78a07f9000,0x00007f78a07fd000] with protection modes 0 +Event: 7.043 Protecting memory [0x00007f784db00000,0x00007f784db04000] with protection modes 0 +Event: 7.043 Protecting memory [0x00007f784d9ff000,0x00007f784da03000] with protection modes 0 +Event: 8.667 Protecting memory [0x00007f78a05f7000,0x00007f78a05fb000] with protection modes 0 +Event: 89.808 Protecting memory [0x00007f78a03f5000,0x00007f78a03f9000] with protection modes 0 +Event: 89.808 Protecting memory [0x00007f78a04f6000,0x00007f78a04fa000] with protection modes 0 +Event: 90.946 Protecting memory [0x00007f782e453000,0x00007f782e457000] with protection modes 0 +Event: 91.173 Protecting memory [0x00007f78a07f9000,0x00007f78a07fd000] with protection modes 0 +Event: 91.173 Protecting memory [0x00007f784db00000,0x00007f784db04000] with protection modes 0 +Event: 91.173 Protecting memory [0x00007f784d9ff000,0x00007f784da03000] with protection modes 0 +Event: 91.762 Protecting memory [0x00007f782e453000,0x00007f782e457000] with protection modes 0 +Event: 93.671 Protecting memory [0x00007f78a07f9000,0x00007f78a07fd000] with protection modes 0 +Event: 174.477 Protecting memory [0x00007f78a04f6000,0x00007f78a04fa000] with protection modes 0 +Event: 174.477 Protecting memory [0x00007f78a03f5000,0x00007f78a03f9000] with protection modes 0 +Event: 2046.028 Protecting memory [0x00007f78a04f6000,0x00007f78a04fa000] with protection modes 0 +Event: 2046.028 Protecting memory [0x00007f78a03f5000,0x00007f78a03f9000] with protection modes 0 +Event: 2047.124 Protecting memory [0x00007f783ceff000,0x00007f783cf03000] with protection modes 0 + +Nmethod flushes (20 events): +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78ccbfa008 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78ccbfaa08 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78ccbfba08 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5025e08 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5026f08 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5027588 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5027e08 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5073808 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5188108 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c518af08 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c518ca88 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c518d608 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c518f608 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5190308 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5190808 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5029588 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5078b88 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c507be08 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c516e588 +Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c51f8508 + +Events (20 events): +Event: 93.192 Thread 0x00007f786991e5e0 Thread exited: 0x00007f786991e5e0 +Event: 93.254 Thread 0x00007f7869673800 Thread exited: 0x00007f7869673800 +Event: 93.542 Thread 0x00007f786800b8e0 Thread exited: 0x00007f786800b8e0 +Event: 93.671 Thread 0x00007f78d418f3f0 Thread added: 0x00007f78748739a0 +Event: 94.853 Thread 0x00007f78748739a0 Thread exited: 0x00007f78748739a0 +Event: 155.251 Thread 0x00007f78699c8f70 Thread exited: 0x00007f78699c8f70 +Event: 155.257 Thread 0x00007f788001eb20 Thread exited: 0x00007f788001eb20 +Event: 174.477 Thread 0x00007f78d48e9190 Thread added: 0x00007f78699c8f70 +Event: 174.477 Thread 0x00007f78699c8f70 Thread added: 0x00007f78ac012d10 +Event: 234.667 Thread 0x00007f78ac012d10 Thread exited: 0x00007f78ac012d10 +Event: 234.673 Thread 0x00007f78699c8f70 Thread exited: 0x00007f78699c8f70 +Event: 2046.028 Thread 0x00007f78d48e9190 Thread added: 0x00007f78699c8f70 +Event: 2046.028 Thread 0x00007f78699c8f70 Thread added: 0x000055ebc500c030 +Event: 2047.116 Thread 0x00007f78800142d0 Thread exited: 0x00007f78800142d0 +Event: 2047.116 Thread 0x00007f7880008ef0 Thread exited: 0x00007f7880008ef0 +Event: 2047.116 Thread 0x00007f78800157c0 Thread exited: 0x00007f78800157c0 +Event: 2047.116 Thread 0x00007f7880013080 Thread exited: 0x00007f7880013080 +Event: 2047.116 Thread 0x00007f78686100d0 Thread exited: 0x00007f78686100d0 +Event: 2047.116 Thread 0x00007f7868602f40 Thread exited: 0x00007f7868602f40 +Event: 2047.124 Thread 0x00007f78d418f3f0 Thread added: 0x00007f78743b0610 + + +Dynamic libraries: +21000000-21080000 rw-p 00000000 00:00 0 +21080000-210c0000 rw-p 00000000 00:00 0 +210c0000-21140000 rw-p 00000000 00:00 0 +21140000-21200000 rw-p 00000000 00:00 0 +21200000-21240000 rw-p 00000000 00:00 0 +21240000-21280000 rw-p 00000000 00:00 0 +21280000-21340000 rw-p 00000000 00:00 0 +21340000-21380000 rw-p 00000000 00:00 0 +21380000-21440000 rw-p 00000000 00:00 0 +21440000-21460000 rw-p 00000000 00:00 0 +21460000-21480000 ---p 00000000 00:00 0 +21480000-215f0000 rw-p 00000000 00:00 0 +215f0000-61000000 ---p 00000000 00:00 0 +61000000-610af000 rw-p 00000000 00:00 0 +610af000-610b1000 r-xs 00000000 00:01 796506 /memfd:/.glXXXXXX (deleted) +421000000-423800000 rw-p 00000000 00:00 0 +423800000-436000000 ---p 00000000 00:00 0 +436000000-436800000 rw-p 00000000 00:00 0 +436800000-45e000000 ---p 00000000 00:00 0 +45e000000-45f000000 rw-p 00000000 00:00 0 +45f000000-800000000 ---p 00000000 00:00 0 +55eb8a0cf000-55eb8a0d0000 r--p 00000000 103:05 53876768 /opt/android-studio/jbr/bin/java +55eb8a0d0000-55eb8a0d1000 r-xp 00000000 103:05 53876768 /opt/android-studio/jbr/bin/java +55eb8a0d1000-55eb8a0d3000 r--p 00000000 103:05 53876768 /opt/android-studio/jbr/bin/java +55eb8a0d3000-55eb8a0d4000 rw-p 00001000 103:05 53876768 /opt/android-studio/jbr/bin/java +55ebc4ec1000-55ebc508f000 rw-p 00000000 00:00 0 [heap] +7f782e452000-7f782e453000 rw-p 00000000 00:00 0 +7f782e453000-7f782e457000 ---p 00000000 00:00 0 +7f782e457000-7f782e553000 rw-p 00000000 00:00 0 +7f782e553000-7f782e753000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f782fb29000-7f7831d7f000 rw-p 00000000 00:00 0 +7f7831d7f000-7f783217f000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f783217f000-7f783257f000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f783257f000-7f783277f000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f783277f000-7f783297f000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f783297f000-7f7832b7f000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f7832bff000-7f7832c00000 rw-p 00000000 00:00 0 +7f7832c00000-7f7832c04000 ---p 00000000 00:00 0 +7f7832c04000-7f7832d01000 rw-p 00000000 00:00 0 +7f7832d01000-7f7832d05000 ---p 00000000 00:00 0 +7f7832d05000-7f7832e02000 rw-p 00000000 00:00 0 +7f7832e02000-7f7832e06000 ---p 00000000 00:00 0 +7f7832e06000-7f7832f02000 rw-p 00000000 00:00 0 +7f7832f02000-7f7832f9a000 r--p 00000000 103:05 69220070 /usr/share/fonts/noto/NotoSans-Medium.ttf +7f7832f9a000-7f7833054000 r--p 00000000 103:05 68061758 /usr/share/fonts/TTF/DejaVuSans.ttf +7f7833054000-7f78330ec000 r--p 00000000 103:05 69220086 /usr/share/fonts/noto/NotoSans-Regular.ttf +7f78330ec000-7f7833152000 r--p 00000000 103:05 68166300 /usr/share/fonts/liberation/LiberationSans-Bold.ttf +7f7833152000-7f7833200000 r--p 00000000 103:05 68061753 /usr/share/fonts/TTF/DejaVuSans-Bold.ttf +7f7833200000-7f783355b000 r--p 00000000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 +7f783355b000-7f7837af8000 r-xp 0035b000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 +7f7837af8000-7f78397e8000 r--p 048f8000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 +7f78397e8000-7f7839a3a000 r--p 065e7000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 +7f7839a3a000-7f7839bc5000 rw-p 06839000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 +7f7839bc5000-7f7839c80000 rw-p 00000000 00:00 0 +7f7839c80000-7f7839ce5000 r--p 00000000 103:05 68166303 /usr/share/fonts/liberation/LiberationSans-Regular.ttf +7f7839ce5000-7f7839d80000 r--p 00000000 103:05 69220021 /usr/share/fonts/noto/NotoSans-Bold.ttf +7f7839d80000-7f7839e00000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f7839e00000-7f783a050000 r-xp 00000000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 +7f783a050000-7f783a071000 rwxp 00250000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 +7f783a071000-7f783a200000 r-xp 00271000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 +7f783a200000-7f783ae00000 r-xp 00400000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 +7f783ae00000-7f783c3f9000 r-xp 01000000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 +7f783c3f9000-7f783c3fa000 ---p 025f9000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 +7f783c3fa000-7f783c572000 r--p 025f9000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 +7f783c572000-7f783c5d5000 rw-p 02771000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 +7f783c5d5000-7f783c5f5000 rw-p 00000000 00:00 0 +7f783c70d000-7f783c78d000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f783c78d000-7f783c7cd000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f783c7cd000-7f783c81e000 r--p 00000000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 +7f783c81e000-7f783c87b000 r-xp 00051000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 +7f783c87b000-7f783c8b0000 rwxp 000ae000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 +7f783c8b0000-7f783c8b1000 r-xp 000e3000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 +7f783c8b1000-7f783c8d1000 r--p 000e4000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 +7f783c8d1000-7f783c8f1000 r--p 00103000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 +7f783c8f1000-7f783c8f6000 rw-p 00123000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 +7f783c8f6000-7f783c8f8000 rw-p 00000000 00:00 0 +7f783c8f8000-7f783c8f9000 rw-p 00000000 00:00 0 +7f783c8f9000-7f783c8fd000 ---p 00000000 00:00 0 +7f783c8fd000-7f783c9fa000 rw-p 00000000 00:00 0 +7f783c9fa000-7f783c9fe000 ---p 00000000 00:00 0 +7f783c9fe000-7f783cafb000 rw-p 00000000 00:00 0 +7f783cafb000-7f783caff000 ---p 00000000 00:00 0 +7f783caff000-7f783cbfc000 rw-p 00000000 00:00 0 +7f783cbfc000-7f783cc00000 ---p 00000000 00:00 0 +7f783cc00000-7f783ccfd000 rw-p 00000000 00:00 0 +7f783ccfd000-7f783cd01000 ---p 00000000 00:00 0 +7f783cd01000-7f783cdfe000 rw-p 00000000 00:00 0 +7f783cdfe000-7f783ce02000 ---p 00000000 00:00 0 +7f783ce02000-7f783ceff000 rw-p 00000000 00:00 0 +7f783ceff000-7f783cf03000 ---p 00000000 00:00 0 +7f783cf03000-7f783d800000 rw-p 00000000 00:00 0 +7f7841820000-7f7841823000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f7841823000-7f7841825000 rw-s 00000000 00:01 796506 /memfd:/.glXXXXXX (deleted) +7f7841829000-7f784183c000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f784183c000-7f784183e000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f784183e000-7f784187e000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f784187e000-7f78418fe000 rw-s 00000000 00:07 1598 /dev/nvidia0 +7f78418fe000-7f7842200000 rw-p 00000000 00:00 0 +7f7846200000-7f7846201000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f7846201000-7f7846221000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f7846221000-7f7846241000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f7846241000-7f7846281000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f784629c000-7f784629e000 r--p 00000000 103:05 54040293 /opt/android-studio/jbr/lib/libextnet.so +7f784629e000-7f784629f000 r-xp 00001000 103:05 54040293 /opt/android-studio/jbr/lib/libextnet.so +7f784629f000-7f78462a0000 r--p 00001000 103:05 54040293 /opt/android-studio/jbr/lib/libextnet.so +7f78462a0000-7f78462a1000 rw-p 00001000 103:05 54040293 /opt/android-studio/jbr/lib/libextnet.so +7f78462a1000-7f78462a4000 r--p 00000000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 +7f78462a4000-7f78462b8000 r-xp 00003000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 +7f78462b8000-7f78462c2000 r--p 00017000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 +7f78462c2000-7f78462c3000 r--p 00020000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 +7f78462c3000-7f78462c4000 rw-p 00021000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 +7f78462c4000-7f78462c6000 rw-p 00000000 00:00 0 +7f78462c6000-7f78462cb000 r--p 00000000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 +7f78462cb000-7f78462d7000 r-xp 00005000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 +7f78462d7000-7f78462db000 r--p 00011000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 +7f78462db000-7f78462dc000 r--p 00015000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 +7f78462dc000-7f78462dd000 rw-p 00016000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 +7f78462dd000-7f78462ed000 rw-s 00000000 00:07 1598 /dev/nvidia0 +7f78462ed000-7f78462f0000 r--p 00000000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 +7f78462f0000-7f78462f3000 r-xp 00003000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 +7f78462f3000-7f78462f4000 r--p 00006000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 +7f78462f4000-7f78462f5000 r--p 00007000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 +7f78462f5000-7f78462f6000 rw-p 00008000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 +7f78462f6000-7f78462f9000 r--p 00000000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 +7f78462f9000-7f78462fc000 r-xp 00003000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 +7f78462fc000-7f78462fd000 r--p 00006000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 +7f78462fd000-7f78462fe000 r--p 00007000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 +7f78462fe000-7f78462ff000 rw-p 00008000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 +7f78462ff000-7f7846b00000 rw-p 00000000 00:00 0 +7f7846b00000-7f7846b05000 r--p 00000000 103:05 67898329 /usr/lib/libmvec.so.1 +7f7846b05000-7f7846b27000 r-xp 00005000 103:05 67898329 /usr/lib/libmvec.so.1 +7f7846b27000-7f7846bfe000 r--p 00027000 103:05 67898329 /usr/lib/libmvec.so.1 +7f7846bfe000-7f7846bff000 r--p 000fe000 103:05 67898329 /usr/lib/libmvec.so.1 +7f7846bff000-7f7846c00000 rw-p 000ff000 103:05 67898329 /usr/lib/libmvec.so.1 +7f7846c00000-7f7846c03000 r--p 00000000 103:05 67984015 /usr/lib/libopus.so.0.11.1 +7f7846c03000-7f7846ccb000 r-xp 00003000 103:05 67984015 /usr/lib/libopus.so.0.11.1 +7f7846ccb000-7f784704a000 r--p 000cb000 103:05 67984015 /usr/lib/libopus.so.0.11.1 +7f784704a000-7f784704e000 r--p 0044a000 103:05 67984015 /usr/lib/libopus.so.0.11.1 +7f784704e000-7f784704f000 rw-p 0044e000 103:05 67984015 /usr/lib/libopus.so.0.11.1 +7f784704f000-7f7847053000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f7847053000-7f784705f000 r--p 00000000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 +7f784705f000-7f7847069000 r-xp 0000c000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 +7f7847069000-7f784706e000 r--p 00016000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 +7f784706e000-7f7847070000 r--p 0001b000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 +7f7847070000-7f7847071000 rw-p 0001d000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 +7f7847071000-7f7847081000 r--p 00000000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 +7f7847081000-7f78470e1000 r-xp 00010000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 +7f78470e1000-7f78470fe000 r--p 00070000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 +7f78470fe000-7f7847104000 r--p 0008c000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 +7f7847104000-7f7847105000 rw-p 00092000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 +7f7847105000-7f784710d000 rw-p 00000000 00:00 0 +7f784710d000-7f7847111000 r--p 00000000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 +7f7847111000-7f7847146000 r-xp 00004000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 +7f7847146000-7f7847158000 r--p 00039000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 +7f7847158000-7f7847159000 r--p 0004b000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 +7f7847159000-7f784715a000 rw-p 0004c000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 +7f784715a000-7f784715f000 r--p 00000000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 +7f784715f000-7f7847188000 r-xp 00005000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 +7f7847188000-7f7847195000 r--p 0002e000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 +7f7847195000-7f7847196000 r--p 0003b000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 +7f7847196000-7f7847197000 rw-p 0003c000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 +7f7847197000-7f78471a8000 rw-p 00000000 00:00 0 +7f78471a8000-7f78471ac000 r--p 00000000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 +7f78471ac000-7f78471ea000 r-xp 00004000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 +7f78471ea000-7f7847205000 r--p 00042000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 +7f7847205000-7f7847206000 r--p 0005d000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 +7f7847206000-7f7847207000 rw-p 0005e000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 +7f7847207000-7f7847210000 r--p 00000000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 +7f7847210000-7f784726b000 r-xp 00009000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 +7f784726b000-7f7847274000 r--p 00064000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 +7f7847274000-7f7847275000 r--p 0006d000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 +7f7847275000-7f7847276000 rw-p 0006e000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 +7f7847276000-7f7847277000 r--p 00000000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 +7f7847277000-7f784727c000 r-xp 00001000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 +7f784727c000-7f78472f3000 r--p 00006000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 +7f78472f3000-7f784730f000 r--p 0007c000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 +7f784730f000-7f7847310000 rw-p 00098000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 +7f7847310000-7f7847315000 r--p 00000000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 +7f7847315000-7f78473b7000 r-xp 00005000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 +7f78473b7000-7f78473d4000 r--p 000a7000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 +7f78473d4000-7f78473d7000 r--p 000c4000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 +7f78473d7000-7f78473d8000 rw-p 000c7000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 +7f78473d8000-7f78473da000 rw-p 00000000 00:00 0 +7f78473da000-7f78477de000 rw-p 00000000 00:00 0 +7f78477de000-7f78477f0000 r--p 00000000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 +7f78477f0000-7f78478b6000 r-xp 00012000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 +7f78478b6000-7f78478f0000 r--p 000d8000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 +7f78478f0000-7f78478f6000 r--p 00111000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 +7f78478f6000-7f78478f7000 rw-p 00117000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 +7f78478f7000-7f78478f9000 rw-p 00000000 00:00 0 +7f78478f9000-7f7848000000 rw-p 00000000 00:00 0 +7f7848000000-7f784811b000 rw-p 00000000 00:00 0 +7f784811b000-7f784c000000 ---p 00000000 00:00 0 +7f784c000000-7f784c001000 r--p 00000000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 +7f784c001000-7f784c002000 r-xp 00001000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 +7f784c002000-7f784c003000 r--p 00002000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 +7f784c003000-7f784c004000 r--p 00002000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 +7f784c004000-7f784c005000 rw-p 00003000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 +7f784c005000-7f784c006000 r--p 00000000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 +7f784c006000-7f784c007000 r-xp 00001000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 +7f784c007000-7f784c008000 r--p 00002000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 +7f784c008000-7f784c009000 r--p 00002000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 +7f784c009000-7f784c00a000 rw-p 00003000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 +7f784c00a000-7f784c01e000 r--p 00000000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so +7f784c01e000-7f784c070000 r-xp 00014000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so +7f784c070000-7f784c096000 r--p 00066000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so +7f784c096000-7f784c098000 r--p 0008c000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so +7f784c098000-7f784c099000 rw-p 0008e000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so +7f784c099000-7f784c0a5000 r--p 00000000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 +7f784c0a5000-7f784c0dc000 r-xp 0000c000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 +7f784c0dc000-7f784c0f2000 r--p 00043000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 +7f784c0f2000-7f784c0f4000 r--p 00059000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 +7f784c0f4000-7f784c0f5000 rw-p 0005b000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 +7f784c0f5000-7f784cb00000 rw-p 00000000 00:00 0 +7f784cb00000-7f784cb04000 ---p 00000000 00:00 0 +7f784cb04000-7f784cc00000 rw-p 00000000 00:00 0 +7f784cc00000-7f784cc1e000 r-xp 00000000 103:05 96629338 /home/mey/.cache/JNA/temp/jna12348363668190973279.tmp (deleted) +7f784cc1e000-7f784ce1d000 ---p 0001e000 103:05 96629338 /home/mey/.cache/JNA/temp/jna12348363668190973279.tmp (deleted) +7f784ce1d000-7f784ce1e000 rw-p 0001d000 103:05 96629338 /home/mey/.cache/JNA/temp/jna12348363668190973279.tmp (deleted) +7f784ce1e000-7f784ce1f000 rw-p 00000000 00:00 0 +7f784ce1f000-7f784ce20000 rw-s 00000000 00:07 1598 /dev/nvidia0 +7f784ce20000-7f784ce22000 r--p 00000000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 +7f784ce22000-7f784ce24000 r-xp 00002000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 +7f784ce24000-7f784ce25000 r--p 00004000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 +7f784ce25000-7f784ce26000 r--p 00004000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 +7f784ce26000-7f784ce27000 rw-p 00005000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 +7f784ce27000-7f784ce2e000 r--p 00000000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 +7f784ce2e000-7f784ce34000 r-xp 00007000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 +7f784ce34000-7f784ce37000 r--p 0000d000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 +7f784ce37000-7f784ce38000 r--p 00010000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 +7f784ce38000-7f784ce39000 rw-p 00011000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 +7f784ce39000-7f784ce3a000 r--p 00000000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 +7f784ce3a000-7f784ce3c000 r-xp 00001000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 +7f784ce3c000-7f784ce3d000 r--p 00003000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 +7f784ce3d000-7f784ce3e000 r--p 00003000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 +7f784ce3e000-7f784ce3f000 rw-p 00004000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 +7f784ce3f000-7f784ce46000 r--s 00000000 103:05 67988359 /usr/lib/gconv/gconv-modules.cache +7f784ce46000-7f784ce48000 r--p 00000000 103:05 67940946 /usr/lib/libogg.so.0.8.6 +7f784ce48000-7f784ce4c000 r-xp 00002000 103:05 67940946 /usr/lib/libogg.so.0.8.6 +7f784ce4c000-7f784ce4f000 r--p 00006000 103:05 67940946 /usr/lib/libogg.so.0.8.6 +7f784ce4f000-7f784ce50000 r--p 00009000 103:05 67940946 /usr/lib/libogg.so.0.8.6 +7f784ce50000-7f784ce51000 rw-p 0000a000 103:05 67940946 /usr/lib/libogg.so.0.8.6 +7f784cee7000-7f784cee8000 rw-p 00000000 00:00 0 +7f784cee8000-7f784ceec000 ---p 00000000 00:00 0 +7f784ceec000-7f784cfe9000 rw-p 00000000 00:00 0 +7f784cfe9000-7f784cfed000 ---p 00000000 00:00 0 +7f784cfed000-7f784d0ea000 rw-p 00000000 00:00 0 +7f784d0ea000-7f784d0ee000 ---p 00000000 00:00 0 +7f784d0ee000-7f784d1eb000 rw-p 00000000 00:00 0 +7f784d1eb000-7f784d1ef000 ---p 00000000 00:00 0 +7f784d1ef000-7f784d2ec000 rw-p 00000000 00:00 0 +7f784d2ec000-7f784d2f0000 ---p 00000000 00:00 0 +7f784d2f0000-7f784d3ed000 rw-p 00000000 00:00 0 +7f784d3ed000-7f784d3f1000 ---p 00000000 00:00 0 +7f784d3f1000-7f784d4ee000 rw-p 00000000 00:00 0 +7f784d4ee000-7f784d4f2000 ---p 00000000 00:00 0 +7f784d4f2000-7f784d5ef000 rw-p 00000000 00:00 0 +7f784d5ef000-7f784d5f3000 ---p 00000000 00:00 0 +7f784d5f3000-7f784d6f0000 rw-p 00000000 00:00 0 +7f784d6f0000-7f784d6f4000 ---p 00000000 00:00 0 +7f784d6f4000-7f784d7f0000 rw-p 00000000 00:00 0 +7f784d7f0000-7f784d8e5000 r--s 00000000 103:05 3171936 /var/cache/fontconfig/6d006ae94db972f3bc99bc2999c017b0-le64.cache-12 +7f784d8e5000-7f784d9fe000 r--s 00000000 103:05 3171934 /var/cache/fontconfig/923e285e415b1073c8df160bee08820f-le64.cache-12 +7f784d9fe000-7f784d9ff000 rw-p 00000000 00:00 0 +7f784d9ff000-7f784da03000 ---p 00000000 00:00 0 +7f784da03000-7f784db00000 rw-p 00000000 00:00 0 +7f784db00000-7f784db04000 ---p 00000000 00:00 0 +7f784db04000-7f784dc00000 rw-p 00000000 00:00 0 +7f784dc00000-7f784fb94000 r--p 00000000 103:05 67913512 /usr/lib/libicudata.so.78.3 +7f784fb94000-7f784fb95000 r--p 01f93000 103:05 67913512 /usr/lib/libicudata.so.78.3 +7f784fb95000-7f784fb96000 r--p 00000000 103:05 54040303 /opt/android-studio/jbr/lib/libjawt.so +7f784fb96000-7f784fb97000 r-xp 00000000 103:05 54040303 /opt/android-studio/jbr/lib/libjawt.so +7f784fb97000-7f784fb98000 r--p 00000000 103:05 54040303 /opt/android-studio/jbr/lib/libjawt.so +7f784fb98000-7f784fb99000 rw-p 00000000 103:05 54040303 /opt/android-studio/jbr/lib/libjawt.so +7f784fb99000-7f784fb9b000 r--p 00000000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 +7f784fb9b000-7f784fb9e000 r-xp 00002000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 +7f784fb9e000-7f784fb9f000 r--p 00005000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 +7f784fb9f000-7f784fba0000 r--p 00005000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 +7f784fba0000-7f784fba1000 rw-p 00006000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 +7f784fbb4000-7f784fbbd000 r--p 00000000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 +7f784fbbd000-7f784fbd4000 r-xp 00009000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 +7f784fbd4000-7f784fbdb000 r--p 00020000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 +7f784fbdb000-7f784fbdc000 r--p 00027000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 +7f784fbdc000-7f784fbdd000 rw-p 00028000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 +7f784fbdd000-7f784fbde000 rw-p 00000000 00:00 0 +7f784fbde000-7f784fbe2000 ---p 00000000 00:00 0 +7f784fbe2000-7f784fc00000 rw-p 00000000 00:00 0 +7f784fc00000-7f784fc48000 r--p 00000000 103:05 67913537 /usr/lib/libicuuc.so.78.3 +7f784fc48000-7f784fda7000 r-xp 00048000 103:05 67913537 /usr/lib/libicuuc.so.78.3 +7f784fda7000-7f784fe43000 r--p 001a7000 103:05 67913537 /usr/lib/libicuuc.so.78.3 +7f784fe43000-7f784fe56000 r--p 00243000 103:05 67913537 /usr/lib/libicuuc.so.78.3 +7f784fe56000-7f784fe57000 rw-p 00256000 103:05 67913537 /usr/lib/libicuuc.so.78.3 +7f784fe57000-7f784fe59000 rw-p 00000000 00:00 0 +7f784fe59000-7f784fe5a000 r--p 00000000 103:05 54040300 /opt/android-studio/jbr/lib/libjaas.so +7f784fe5a000-7f784fe5b000 r-xp 00000000 103:05 54040300 /opt/android-studio/jbr/lib/libjaas.so +7f784fe5b000-7f784fe5c000 r--p 00000000 103:05 54040300 /opt/android-studio/jbr/lib/libjaas.so +7f784fe5c000-7f784fe5d000 rw-p 00000000 103:05 54040300 /opt/android-studio/jbr/lib/libjaas.so +7f784fe67000-7f784fe71000 r--p 00000000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 +7f784fe71000-7f784ffc0000 r-xp 0000a000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 +7f784ffc0000-7f784fff7000 r--p 00159000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 +7f784fff7000-7f784fffb000 r--p 00190000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 +7f784fffb000-7f7850000000 rw-p 00194000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 +7f7850000000-7f78501d3000 rw-p 00000000 00:00 0 +7f78501d3000-7f7854000000 ---p 00000000 00:00 0 +7f7854000000-7f7854001000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f7854001000-7f7854002000 rw-s 00000000 00:07 1596 /dev/nvidiactl +7f785400c000-7f785401c000 r--s 00000000 103:05 3171928 /var/cache/fontconfig/491f45a0a771fef1c10b9b647a97fb82-le64.cache-12 +7f785401c000-7f78540ca000 r--s 00000000 103:05 3171352 /var/cache/fontconfig/f6b893a7224233d96cb72fd88691c0b4-le64.cache-12 +7f78540ca000-7f78540e4000 r--p 00000000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 +7f78540e4000-7f78541b8000 r-xp 0001a000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 +7f78541b8000-7f78541f5000 r--p 000ee000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 +7f78541f5000-7f78541fe000 r--p 0012b000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 +7f78541fe000-7f78541ff000 rw-p 00134000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 +7f78541ff000-7f7854200000 rw-p 00000000 00:00 0 +7f7854200000-7f78542c3000 r--p 00000000 103:05 67983832 /usr/lib/libglycin-2.so.0 +7f78542c3000-7f78545e0000 r-xp 000c2000 103:05 67983832 /usr/lib/libglycin-2.so.0 +7f78545e0000-7f78545f9000 r--p 003de000 103:05 67983832 /usr/lib/libglycin-2.so.0 +7f78545f9000-7f78545fd000 rw-p 003f6000 103:05 67983832 /usr/lib/libglycin-2.so.0 +7f78545fd000-7f78545fe000 r--s 00000000 00:07 1596 /dev/nvidiactl +7f78545fe000-7f78545ff000 rw-p 00000000 00:00 0 +7f78545ff000-7f7854605000 r--s 00000000 103:05 3171931 /var/cache/fontconfig/b67b32625a2bb51b023d3814a918f351-le64.cache-12 +7f7854605000-7f7854613000 r--s 00000000 103:05 3171920 /var/cache/fontconfig/210c0516121708a580e22e6b1f9a103a-le64.cache-12 +7f7854613000-7f785461f000 r--s 00000000 103:05 3171347 /var/cache/fontconfig/4b31aef11cb0687b6d280d34cbe23592-le64.cache-12 +7f785461f000-7f7854621000 r--p 00000000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 +7f7854621000-7f7854631000 r-xp 00002000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 +7f7854631000-7f785464a000 r--p 00012000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 +7f785464a000-7f785464b000 r--p 0002b000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 +7f785464b000-7f785464c000 rw-p 0002c000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 +7f785464c000-7f7854655000 r--p 00000000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 +7f7854655000-7f785469c000 r-xp 00009000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 +7f785469c000-7f78546b1000 r--p 00050000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 +7f78546b1000-7f78546b4000 r--p 00064000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 +7f78546b4000-7f78546b7000 rw-p 00067000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 +7f78546b7000-7f78546b8000 rw-p 00000000 00:00 0 +7f78546b8000-7f78546ba000 r--p 00000000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 +7f78546ba000-7f78546be000 r-xp 00002000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 +7f78546be000-7f78546bf000 r--p 00006000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 +7f78546bf000-7f78546c0000 r--p 00006000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 +7f78546c0000-7f78546c1000 rw-p 00007000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 +7f78546c1000-7f78546ca000 r--p 00000000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 +7f78546ca000-7f78546e3000 r-xp 00009000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 +7f78546e3000-7f78546ec000 r--p 00022000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 +7f78546ec000-7f78546ed000 r--p 0002b000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 +7f78546ed000-7f78546ee000 rw-p 0002c000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 +7f78546ee000-7f78546fc000 r--p 00000000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 +7f78546fc000-7f7854734000 r-xp 0000e000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 +7f7854734000-7f7854746000 r--p 00046000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 +7f7854746000-7f7854748000 r--p 00057000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 +7f7854748000-7f7854749000 rw-p 00059000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 +7f7854749000-7f7854757000 r--p 00000000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 +7f7854757000-7f7854772000 r-xp 0000e000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 +7f7854772000-7f7854780000 r--p 00029000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 +7f7854780000-7f7854784000 r--p 00037000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 +7f7854784000-7f7854785000 rw-p 0003b000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 +7f7854785000-7f7854787000 r--p 00000000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 +7f7854787000-7f78547a0000 r-xp 00002000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 +7f78547a0000-7f78547a5000 r--p 0001b000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 +7f78547a5000-7f78547a7000 r--p 0001f000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 +7f78547a7000-7f78547a8000 rw-p 00021000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 +7f78547a8000-7f78547ad000 r--p 00000000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 +7f78547ad000-7f785491d000 r-xp 00005000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 +7f785491d000-7f7854932000 r--p 00175000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 +7f7854932000-7f785493a000 r--p 0018a000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 +7f785493a000-7f785493b000 rw-p 00192000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 +7f785493b000-7f785493c000 r--p 00000000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 +7f785493c000-7f785493d000 r-xp 00001000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 +7f785493d000-7f785493e000 r--p 00002000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 +7f785493e000-7f785493f000 r--p 00002000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 +7f785493f000-7f7854940000 rw-p 00003000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 +7f7854940000-7f7854946000 r--p 00000000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 +7f7854946000-7f785494c000 r-xp 00006000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 +7f785494c000-7f785494e000 r--p 0000c000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 +7f785494e000-7f785494f000 r--p 0000e000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 +7f785494f000-7f7854950000 rw-p 0000f000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 +7f7854950000-7f7854952000 r--p 00000000 103:05 67983934 /usr/lib/libthai.so.0.3.2 +7f7854952000-7f7854958000 r-xp 00002000 103:05 67983934 /usr/lib/libthai.so.0.3.2 +7f7854958000-7f785495b000 r--p 00008000 103:05 67983934 /usr/lib/libthai.so.0.3.2 +7f785495b000-7f785495c000 r--p 0000a000 103:05 67983934 /usr/lib/libthai.so.0.3.2 +7f785495c000-7f785495d000 rw-p 0000b000 103:05 67983934 /usr/lib/libthai.so.0.3.2 +7f785495d000-7f785495f000 r--p 00000000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 +7f785495f000-7f7854969000 r-xp 00002000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 +7f7854969000-7f785496b000 r--p 0000c000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 +7f785496b000-7f785496c000 r--p 0000d000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 +7f785496c000-7f785496d000 rw-p 0000e000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 +7f785496d000-7f785496e000 r--p 00000000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 +7f785496e000-7f785496f000 r-xp 00001000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 +7f785496f000-7f7854970000 r--p 00002000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 +7f7854970000-7f7854971000 r--p 00002000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 +7f7854971000-7f7854972000 rw-p 00003000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 +7f7854972000-7f7854973000 r--p 00000000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 +7f7854973000-7f7854974000 r-xp 00001000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 +7f7854974000-7f7854975000 r--p 00002000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 +7f7854975000-7f7854976000 r--p 00002000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 +7f7854976000-7f7854977000 rw-p 00003000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 +7f7854977000-7f785497b000 r--p 00000000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 +7f785497b000-7f78549aa000 r-xp 00004000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 +7f78549aa000-7f78549d3000 r--p 00033000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 +7f78549d3000-7f78549d5000 r--p 0005b000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 +7f78549d5000-7f78549d6000 rw-p 0005d000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 +7f78549d6000-7f78549e6000 r--p 00000000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 +7f78549e6000-7f7854a65000 r-xp 00010000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 +7f7854a65000-7f7854a9a000 r--p 0008f000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 +7f7854a9a000-7f7854aa9000 r--p 000c3000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 +7f7854aa9000-7f7854aaa000 rw-p 000d2000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 +7f7854aaa000-7f7854ab1000 r--p 00000000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 +7f7854ab1000-7f7854abb000 r-xp 00007000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 +7f7854abb000-7f7854ac0000 r--p 00011000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 +7f7854ac0000-7f7854ac1000 r--p 00016000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 +7f7854ac1000-7f7854ac2000 rw-p 00017000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 +7f7854ac2000-7f7854acd000 r--p 00000000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 +7f7854acd000-7f7854ae6000 r-xp 0000b000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 +7f7854ae6000-7f7854afa000 r--p 00024000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 +7f7854afa000-7f7854afc000 r--p 00037000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 +7f7854afc000-7f7854afe000 rw-p 00039000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 +7f7854afe000-7f7854b48000 r--p 00000000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 +7f7854b48000-7f7854bb3000 r-xp 0004a000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 +7f7854bb3000-7f7854c10000 r--p 000b5000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 +7f7854c10000-7f7854c18000 r--p 00112000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 +7f7854c18000-7f7854c1f000 rw-p 0011a000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 +7f7854c1f000-7f7854c27000 r--p 00000000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 +7f7854c27000-7f7854c37000 r-xp 00008000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 +7f7854c37000-7f7854c42000 r--p 00018000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 +7f7854c42000-7f7854c45000 r--p 00023000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 +7f7854c45000-7f7854c46000 rw-p 00026000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 +7f7854c46000-7f7854c4d000 r--p 00000000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 +7f7854c4d000-7f7854c6f000 r-xp 00007000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 +7f7854c6f000-7f7854c7d000 r--p 00029000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 +7f7854c7d000-7f7854c7f000 r--p 00036000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 +7f7854c7f000-7f7854c80000 rw-p 00038000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 +7f7854c80000-7f7854c82000 r--p 00000000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 +7f7854c82000-7f7854c8a000 r-xp 00002000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 +7f7854c8a000-7f7854ca1000 r--p 0000a000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 +7f7854ca1000-7f7854ca2000 r--p 00021000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 +7f7854ca2000-7f7854ca3000 rw-p 00022000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 +7f7854ca3000-7f7854cab000 r--p 00000000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 +7f7854cab000-7f7854cbb000 r-xp 00008000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 +7f7854cbb000-7f7854cc0000 r--p 00018000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 +7f7854cc0000-7f7854cc1000 r--p 0001d000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 +7f7854cc1000-7f7854cc2000 rw-p 0001e000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 +7f7854cc2000-7f7854ccf000 r--p 00000000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 +7f7854ccf000-7f7854dc1000 r-xp 0000d000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 +7f7854dc1000-7f7854df5000 r--p 000ff000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 +7f7854df5000-7f7854df7000 r--p 00132000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 +7f7854df7000-7f7854df8000 rw-p 00134000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 +7f7854df8000-7f7854e06000 r--p 00000000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 +7f7854e06000-7f7854f4c000 r-xp 0000e000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 +7f7854f4c000-7f7854f8a000 r--p 00154000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 +7f7854f8a000-7f7854f8e000 r--p 00192000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 +7f7854f8e000-7f7854f90000 rw-p 00196000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 +7f7854f90000-7f7854f91000 rw-p 00000000 00:00 0 +7f7854f91000-7f7854f9f000 r--p 00000000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 +7f7854f9f000-7f7854fdf000 r-xp 0000e000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 +7f7854fdf000-7f7854ffb000 r--p 0004e000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 +7f7854ffb000-7f7854fff000 r--p 00069000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 +7f7854fff000-7f7855000000 rw-p 0006d000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 +7f7855000000-7f7855070000 r--p 00000000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 +7f7855070000-7f7855427000 r-xp 00070000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 +7f7855427000-7f78556e5000 r--p 00427000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 +7f78556e5000-7f78556f7000 r--p 006e5000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 +7f78556f7000-7f78556f9000 rw-p 006f7000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 +7f78556f9000-7f78556fe000 rw-p 00000000 00:00 0 +7f78556fe000-7f78556ff000 r-xp 00000000 00:2f 21007 /tmp/ffi7UxbMI (deleted) +7f78556ff000-7f7855700000 rw-p 00000000 00:00 0 +7f7855700000-7f7855701000 r--p 00000000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 +7f7855701000-7f7855702000 r-xp 00001000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 +7f7855702000-7f7855703000 r--p 00002000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 +7f7855703000-7f7855704000 r--p 00002000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 +7f7855704000-7f7855705000 rw-p 00003000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 +7f7855705000-7f7855708000 r--p 00000000 103:05 67934087 /usr/lib/libXi.so.6.1.0 +7f7855708000-7f7855715000 r-xp 00003000 103:05 67934087 /usr/lib/libXi.so.6.1.0 +7f7855715000-7f7855718000 r--p 00010000 103:05 67934087 /usr/lib/libXi.so.6.1.0 +7f7855718000-7f7855719000 r--p 00012000 103:05 67934087 /usr/lib/libXi.so.6.1.0 +7f7855719000-7f785571a000 rw-p 00013000 103:05 67934087 /usr/lib/libXi.so.6.1.0 +7f785571a000-7f7855737000 r--p 00000000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 +7f7855737000-7f78557b9000 r-xp 0001d000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 +7f78557b9000-7f78557fb000 r--p 0009f000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 +7f78557fb000-7f7855805000 r--p 000e0000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 +7f7855805000-7f7855807000 rw-p 000ea000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 +7f7855807000-7f7855808000 rw-p 00000000 00:00 0 +7f7855808000-7f785580c000 ---p 00000000 00:00 0 +7f785580c000-7f7855908000 rw-p 00000000 00:00 0 +7f7855908000-7f785591d000 r--p 00000000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 +7f785591d000-7f78559f6000 r-xp 00015000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 +7f78559f6000-7f7855a45000 r--p 000ee000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 +7f7855a45000-7f7855a4e000 r--p 0013c000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 +7f7855a4e000-7f7855a4f000 rw-p 00145000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 +7f7855a4f000-7f7855a50000 rw-p 00000000 00:00 0 +7f7855a50000-7f7855a55000 r--p 00000000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 +7f7855a55000-7f7855a7e000 r-xp 00005000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 +7f7855a7e000-7f7855a89000 r--p 0002e000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 +7f7855a89000-7f7855a8f000 r--p 00038000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 +7f7855a8f000-7f7855a90000 rw-p 0003e000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 +7f7855a90000-7f7855a93000 r--p 00000000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 +7f7855a93000-7f7855b2a000 r-xp 00003000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 +7f7855b2a000-7f7855b59000 r--p 0009a000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 +7f7855b59000-7f7855b5a000 r--p 000c8000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 +7f7855b5a000-7f7855b5b000 rw-p 000c9000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 +7f7855b5b000-7f7855b65000 r--p 00000000 103:05 67902806 /usr/lib/libmount.so.1.1.0 +7f7855b65000-7f7855ba3000 r-xp 0000a000 103:05 67902806 /usr/lib/libmount.so.1.1.0 +7f7855ba3000-7f7855bb4000 r--p 00048000 103:05 67902806 /usr/lib/libmount.so.1.1.0 +7f7855bb4000-7f7855bb6000 r--p 00059000 103:05 67902806 /usr/lib/libmount.so.1.1.0 +7f7855bb6000-7f7855bb7000 rw-p 0005b000 103:05 67902806 /usr/lib/libmount.so.1.1.0 +7f7855bb7000-7f7855bc3000 r--p 00000000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 +7f7855bc3000-7f7855bfc000 r-xp 0000c000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 +7f7855bfc000-7f7855c16000 r--p 00045000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 +7f7855c16000-7f7855c19000 r--p 0005e000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 +7f7855c19000-7f7855c1a000 rw-p 00061000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 +7f7855c1a000-7f7855c38000 r--p 00000000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 +7f7855c38000-7f7855ce6000 r-xp 0001e000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 +7f7855ce6000-7f7855d7a000 r--p 000cc000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 +7f7855d7a000-7f7855d7b000 r--p 00160000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 +7f7855d7b000-7f7855d7c000 rw-p 00161000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 +7f7855d7c000-7f7855d7d000 rw-p 00000000 00:00 0 +7f7855d7d000-7f7855daf000 r--p 00000000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 +7f7855daf000-7f7855ed6000 r-xp 00032000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 +7f7855ed6000-7f7855f50000 r--p 00159000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 +7f7855f50000-7f7855f59000 r--p 001d2000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 +7f7855f59000-7f7855f5a000 rw-p 001db000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 +7f7855f5a000-7f7855f5c000 rw-p 00000000 00:00 0 +7f7855f5c000-7f7855fa4000 r--p 00000000 103:05 54040294 /opt/android-studio/jbr/lib/libfontmanager.so +7f7855fa4000-7f78560a6000 r-xp 00047000 103:05 54040294 /opt/android-studio/jbr/lib/libfontmanager.so +7f78560a6000-7f78560a8000 r--p 00148000 103:05 54040294 /opt/android-studio/jbr/lib/libfontmanager.so +7f78560a8000-7f78560aa000 rw-p 00149000 103:05 54040294 /opt/android-studio/jbr/lib/libfontmanager.so +7f78560aa000-7f78560ab000 rw-p 00000000 00:00 0 +7f78560ab000-7f78560d9000 r--p 00000000 103:05 54040291 /opt/android-studio/jbr/lib/libawt_xawt.so +7f78560d9000-7f7856121000 r-xp 0002d000 103:05 54040291 /opt/android-studio/jbr/lib/libawt_xawt.so +7f7856121000-7f7856126000 r--p 00074000 103:05 54040291 /opt/android-studio/jbr/lib/libawt_xawt.so +7f7856126000-7f785612a000 rw-p 00078000 103:05 54040291 /opt/android-studio/jbr/lib/libawt_xawt.so +7f785612a000-7f785612b000 rw-p 00000000 00:00 0 +7f785612b000-7f7856156000 r--p 00000000 103:05 54040288 /opt/android-studio/jbr/lib/libawt.so +7f7856156000-7f78561ce000 r-xp 0002a000 103:05 54040288 /opt/android-studio/jbr/lib/libawt.so +7f78561ce000-7f78561cf000 r--p 000a1000 103:05 54040288 /opt/android-studio/jbr/lib/libawt.so +7f78561cf000-7f78561db000 rw-p 000a1000 103:05 54040288 /opt/android-studio/jbr/lib/libawt.so +7f78561db000-7f7856200000 rw-p 00000000 00:00 0 +7f7856200000-7f785629a000 r--p 00000000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 +7f785629a000-7f785641d000 r-xp 0009a000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 +7f785641d000-7f78564bd000 r--p 0021d000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 +7f78564bd000-7f78564ce000 r--p 002bc000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 +7f78564ce000-7f78564cf000 rw-p 002cd000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 +7f78564cf000-7f78564d3000 rw-p 00000000 00:00 0 +7f78564d3000-7f78564d6000 r--s 00000000 103:05 3171923 /var/cache/fontconfig/6ba42ae0000f58711b5caaf10d690066-le64.cache-12 +7f78564d6000-7f78564d7000 r--p 00000000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 +7f78564d7000-7f78564d9000 r-xp 00001000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 +7f78564d9000-7f78564da000 r--p 00003000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 +7f78564da000-7f78564db000 r--p 00003000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 +7f78564db000-7f78564df000 rw-p 00004000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 +7f78564df000-7f78564e4000 r--p 00000000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 +7f78564e4000-7f78564ed000 r-xp 00005000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 +7f78564ed000-7f78564ef000 r--p 0000e000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 +7f78564ef000-7f78564f0000 r--p 00010000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 +7f78564f0000-7f78564f1000 rw-p 00011000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 +7f78564f1000-7f78564f4000 r--p 00000000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 +7f78564f4000-7f78564fb000 r-xp 00003000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 +7f78564fb000-7f78564fd000 r--p 0000a000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 +7f78564fd000-7f78564fe000 r--p 0000c000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 +7f78564fe000-7f78564ff000 rw-p 0000d000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 +7f78564ff000-7f7856500000 rw-p 00000000 00:00 0 +7f7856500000-7f7856504000 ---p 00000000 00:00 0 +7f7856504000-7f7856600000 rw-p 00000000 00:00 0 +7f7856600000-7f7857f1a000 r-xp 00000000 103:05 98306206 /home/mey/.skiko/skiko-linux-x64-434f7633c843870c0839a1137b128f6c3db3c5d58dcad3545e90827e3e341afb/libskiko-linux-x64.so +7f7857f1a000-7f7857f52000 r--p 01919000 103:05 98306206 /home/mey/.skiko/skiko-linux-x64-434f7633c843870c0839a1137b128f6c3db3c5d58dcad3545e90827e3e341afb/libskiko-linux-x64.so +7f7857f52000-7f7857f55000 rw-p 01951000 103:05 98306206 /home/mey/.skiko/skiko-linux-x64-434f7633c843870c0839a1137b128f6c3db3c5d58dcad3545e90827e3e341afb/libskiko-linux-x64.so +7f7857f55000-7f7857f5f000 rw-p 00000000 00:00 0 +7f7857f5f000-7f7857f62000 r--s 00000000 103:05 3171350 /var/cache/fontconfig/f349e9996a5320f6dd491cedd2b1f964-le64.cache-12 +7f7857f62000-7f7857f64000 r--p 00000000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 +7f7857f64000-7f7857f66000 r-xp 00002000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 +7f7857f66000-7f7857f68000 r--p 00004000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 +7f7857f68000-7f7857f6a000 r--p 00006000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 +7f7857f6a000-7f7857f6b000 rw-p 00008000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 +7f7857f6b000-7f7857f86000 r--p 00000000 103:05 67919883 /usr/share/glib-2.0/schemas/gschemas.compiled +7f7857f86000-7f7857f8a000 r--p 00000000 103:05 67932369 /usr/lib/libXext.so.6.4.0 +7f7857f8a000-7f7857f9a000 r-xp 00004000 103:05 67932369 /usr/lib/libXext.so.6.4.0 +7f7857f9a000-7f7857f9e000 r--p 00014000 103:05 67932369 /usr/lib/libXext.so.6.4.0 +7f7857f9e000-7f7857f9f000 r--p 00017000 103:05 67932369 /usr/lib/libXext.so.6.4.0 +7f7857f9f000-7f7857fa0000 rw-p 00018000 103:05 67932369 /usr/lib/libXext.so.6.4.0 +7f7857fa0000-7f7857fa1000 r--p 00000000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 +7f7857fa1000-7f7857fa2000 r-xp 00001000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 +7f7857fa2000-7f7857fc1000 r--p 00002000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 +7f7857fc1000-7f7857fc2000 r--p 00021000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 +7f7857fc2000-7f7857fc3000 rw-p 00022000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 +7f7857fc3000-7f7857fc9000 r--p 00000000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 +7f7857fc9000-7f7857ff4000 r-xp 00006000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 +7f7857ff4000-7f7857ffe000 r--p 00031000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 +7f7857ffe000-7f7857fff000 r--p 0003a000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 +7f7857fff000-7f7858000000 rw-p 0003b000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 +7f7858000000-7f7858ffc000 rw-p 00000000 00:00 0 +7f7858ffc000-7f785c000000 ---p 00000000 00:00 0 +7f785c000000-7f785d1fd000 rw-p 00000000 00:00 0 +7f785d1fd000-7f7860000000 ---p 00000000 00:00 0 +7f7860000000-7f7860021000 rw-p 00000000 00:00 0 +7f7860021000-7f7864000000 ---p 00000000 00:00 0 +7f7864000000-7f78651c4000 rw-p 00000000 00:00 0 +7f78651c4000-7f7868000000 ---p 00000000 00:00 0 +7f7868000000-7f786a381000 rw-p 00000000 00:00 0 +7f786a381000-7f786c000000 ---p 00000000 00:00 0 +7f786c000000-7f786ca91000 rw-p 00000000 00:00 0 +7f786ca91000-7f7870000000 ---p 00000000 00:00 0 +7f7870000000-7f787065c000 rw-p 00000000 00:00 0 +7f787065c000-7f7874000000 ---p 00000000 00:00 0 +7f7874000000-7f7875ffe000 rw-p 00000000 00:00 0 +7f7875ffe000-7f7878000000 ---p 00000000 00:00 0 +7f7878000000-7f7878057000 rw-p 00000000 00:00 0 +7f7878057000-7f787c000000 ---p 00000000 00:00 0 +7f787c000000-7f787c08e000 rw-p 00000000 00:00 0 +7f787c08e000-7f7880000000 ---p 00000000 00:00 0 +7f7880000000-7f7880027000 rw-p 00000000 00:00 0 +7f7880027000-7f7884000000 ---p 00000000 00:00 0 +7f7884000000-7f788414a000 rw-p 00000000 00:00 0 +7f788414a000-7f7888000000 ---p 00000000 00:00 0 +7f7888000000-7f788827d000 rw-p 00000000 00:00 0 +7f788827d000-7f788c000000 ---p 00000000 00:00 0 +7f788c000000-7f788c021000 rw-p 00000000 00:00 0 +7f788c021000-7f7890000000 ---p 00000000 00:00 0 +7f7890000000-7f7890500000 rw-p 00000000 00:00 0 +7f7890500000-7f7890540000 rw-p 00000000 00:00 0 +7f7890540000-7f7890580000 rw-p 00000000 00:00 0 +7f7890580000-7f78906a0000 rw-p 00000000 00:00 0 +7f78906a0000-7f78906c0000 rw-p 00000000 00:00 0 +7f78906c0000-7f7890700000 rw-p 00000000 00:00 0 +7f7890700000-7f7890840000 rw-p 00000000 00:00 0 +7f7890840000-7f7890880000 rw-p 00000000 00:00 0 +7f7890880000-7f7890900000 rw-p 00000000 00:00 0 +7f7890900000-7f7890b00000 rw-p 00000000 00:00 0 +7f7890b00000-7f7890b80000 rw-p 00000000 00:00 0 +7f7890b80000-7f7890d00000 rw-p 00000000 00:00 0 +7f7890d00000-7f7890e00000 rw-p 00000000 00:00 0 +7f7890e00000-7f7891100000 rw-p 00000000 00:00 0 +7f7891100000-7f7891200000 rw-p 00000000 00:00 0 +7f7891200000-7f7891300000 rw-p 00000000 00:00 0 +7f7891300000-7f7891400000 rw-p 00000000 00:00 0 +7f7891400000-7f7891500000 rw-p 00000000 00:00 0 +7f7891500000-7f7891600000 rw-p 00000000 00:00 0 +7f7891600000-7f7891780000 rw-p 00000000 00:00 0 +7f7891780000-7f7891800000 rw-p 00000000 00:00 0 +7f7891800000-7f7891900000 rw-p 00000000 00:00 0 +7f7891900000-7f7891a00000 rw-p 00000000 00:00 0 +7f7891a00000-7f7891b80000 rw-p 00000000 00:00 0 +7f7891b80000-7f7891c00000 rw-p 00000000 00:00 0 +7f7891c00000-7f7891f00000 rw-p 00000000 00:00 0 +7f7891f00000-7f7892200000 rw-p 00000000 00:00 0 +7f7892200000-7f7892b00000 rw-p 00000000 00:00 0 +7f7892b00000-7f7892c00000 rw-p 00000000 00:00 0 +7f7892c00000-7f7892e30000 rw-p 00000000 00:00 0 +7f7892e30000-7f7892f00000 ---p 00000000 00:00 0 +7f7892f00000-7f7892f30000 rw-p 00000000 00:00 0 +7f7892f30000-7f7894000000 ---p 00000000 00:00 0 +7f7894000000-7f7894024000 rw-p 00000000 00:00 0 +7f7894024000-7f7898000000 ---p 00000000 00:00 0 +7f7898000000-7f789803c000 rw-p 00000000 00:00 0 +7f789803c000-7f789c000000 ---p 00000000 00:00 0 +7f789c000000-7f789c021000 rw-p 00000000 00:00 0 +7f789c021000-7f78a0000000 ---p 00000000 00:00 0 +7f78a0000000-7f78a0002000 r--p 00000000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 +7f78a0002000-7f78a0005000 r-xp 00002000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 +7f78a0005000-7f78a0006000 r--p 00005000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 +7f78a0006000-7f78a0007000 r--p 00006000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 +7f78a0007000-7f78a0008000 rw-p 00007000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 +7f78a0008000-7f78a000a000 r--p 00000000 103:05 67925758 /usr/lib/libffi.so.8.5.0 +7f78a000a000-7f78a0012000 r-xp 00002000 103:05 67925758 /usr/lib/libffi.so.8.5.0 +7f78a0012000-7f78a0014000 r--p 0000a000 103:05 67925758 /usr/lib/libffi.so.8.5.0 +7f78a0014000-7f78a0015000 r--p 0000b000 103:05 67925758 /usr/lib/libffi.so.8.5.0 +7f78a0015000-7f78a0016000 rw-p 0000c000 103:05 67925758 /usr/lib/libffi.so.8.5.0 +7f78a0016000-7f78a0019000 r--p 00000000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng +7f78a0019000-7f78a0046000 r-xp 00003000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng +7f78a0046000-7f78a0050000 r--p 00030000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng +7f78a0050000-7f78a0051000 r--p 00039000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng +7f78a0051000-7f78a0052000 rw-p 0003a000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng +7f78a0052000-7f78a0058000 r--p 00000000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 +7f78a0058000-7f78a00f0000 r-xp 00006000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 +7f78a00f0000-7f78a0119000 r--p 0009e000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 +7f78a0119000-7f78a0121000 r--p 000c6000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 +7f78a0121000-7f78a0122000 rw-p 000ce000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 +7f78a0122000-7f78a0124000 r--p 00000000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 +7f78a0124000-7f78a0127000 r-xp 00002000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 +7f78a0127000-7f78a0162000 r-xp 00005000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 +7f78a0162000-7f78a0163000 r-xp 00040000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 +7f78a0163000-7f78a0176000 r--p 00041000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 +7f78a0176000-7f78a0192000 r--p 00054000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 +7f78a0192000-7f78a0193000 rw-p 00070000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 +7f78a0193000-7f78a019b000 rw-p 00000000 00:00 0 +7f78a019b000-7f78a01a1000 r--p 00000000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 +7f78a01a1000-7f78a01d6000 r-xp 00006000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 +7f78a01d6000-7f78a020c000 r--p 0003b000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 +7f78a020c000-7f78a0215000 r--p 00071000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 +7f78a0215000-7f78a0216000 rw-p 0007a000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 +7f78a0216000-7f78a022d000 r--p 00000000 103:05 67931562 /usr/lib/libX11.so.6.4.0 +7f78a022d000-7f78a02e6000 r-xp 00017000 103:05 67931562 /usr/lib/libX11.so.6.4.0 +7f78a02e6000-7f78a037b000 r--p 000d0000 103:05 67931562 /usr/lib/libX11.so.6.4.0 +7f78a037b000-7f78a037e000 r--p 00164000 103:05 67931562 /usr/lib/libX11.so.6.4.0 +7f78a037e000-7f78a0381000 rw-p 00167000 103:05 67931562 /usr/lib/libX11.so.6.4.0 +7f78a0381000-7f78a0382000 rw-p 00000000 00:00 0 +7f78a0382000-7f78a03b1000 r--p 00000000 103:05 67932688 /usr/lib/libGL.so.1.7.0 +7f78a03b1000-7f78a03b4000 r-xp 0002f000 103:05 67932688 /usr/lib/libGL.so.1.7.0 +7f78a03b4000-7f78a03cf000 r-xp 00032000 103:05 67932688 /usr/lib/libGL.so.1.7.0 +7f78a03cf000-7f78a03d0000 r-xp 0004d000 103:05 67932688 /usr/lib/libGL.so.1.7.0 +7f78a03d0000-7f78a03e4000 r--p 0004e000 103:05 67932688 /usr/lib/libGL.so.1.7.0 +7f78a03e4000-7f78a03f2000 r--p 00062000 103:05 67932688 /usr/lib/libGL.so.1.7.0 +7f78a03f2000-7f78a03f3000 rw-p 00070000 103:05 67932688 /usr/lib/libGL.so.1.7.0 +7f78a03f3000-7f78a03f4000 rw-p 00000000 00:00 0 +7f78a03f4000-7f78a03f5000 rw-p 00000000 00:00 0 +7f78a03f5000-7f78a03f9000 ---p 00000000 00:00 0 +7f78a03f9000-7f78a04f6000 rw-p 00000000 00:00 0 +7f78a04f6000-7f78a04fa000 ---p 00000000 00:00 0 +7f78a04fa000-7f78a05f7000 rw-p 00000000 00:00 0 +7f78a05f7000-7f78a05fb000 ---p 00000000 00:00 0 +7f78a05fb000-7f78a06f8000 rw-p 00000000 00:00 0 +7f78a06f8000-7f78a06fc000 ---p 00000000 00:00 0 +7f78a06fc000-7f78a07f9000 rw-p 00000000 00:00 0 +7f78a07f9000-7f78a07fd000 ---p 00000000 00:00 0 +7f78a07fd000-7f78a08fa000 rw-p 00000000 00:00 0 +7f78a08fa000-7f78a08fe000 ---p 00000000 00:00 0 +7f78a08fe000-7f78a09fb000 rw-p 00000000 00:00 0 +7f78a09fb000-7f78a09ff000 ---p 00000000 00:00 0 +7f78a09ff000-7f78a0afc000 rw-p 00000000 00:00 0 +7f78a0afc000-7f78a0b00000 ---p 00000000 00:00 0 +7f78a0b00000-7f78a0bfd000 rw-p 00000000 00:00 0 +7f78a0bfd000-7f78a0c01000 ---p 00000000 00:00 0 +7f78a0c01000-7f78a0cfe000 rw-p 00000000 00:00 0 +7f78a0cfe000-7f78a0d02000 ---p 00000000 00:00 0 +7f78a0d02000-7f78a0dff000 rw-p 00000000 00:00 0 +7f78a0dff000-7f78a0e03000 ---p 00000000 00:00 0 +7f78a0e03000-7f78a0f00000 rw-p 00000000 00:00 0 +7f78a0f00000-7f78a0f04000 ---p 00000000 00:00 0 +7f78a0f04000-7f78a1000000 rw-p 00000000 00:00 0 +7f78a1000000-7f78a12ed000 r--p 00000000 103:05 67988358 /usr/lib/locale/locale-archive +7f78a12ed000-7f78a12ee000 rw-p 00000000 00:00 0 +7f78a12ee000-7f78a12ef000 r--p 00000000 00:00 0 +7f78a12ef000-7f78a12f2000 r--p 00000000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 +7f78a12f2000-7f78a12f9000 r-xp 00003000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 +7f78a12f9000-7f78a12fc000 r--p 0000a000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 +7f78a12fc000-7f78a12fe000 r--p 0000c000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 +7f78a12fe000-7f78a12ff000 rw-p 0000e000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 +7f78a12ff000-7f78a1303000 r--p 00000000 103:05 67980018 /usr/lib/libgcc_s.so.1 +7f78a1303000-7f78a1326000 r-xp 00004000 103:05 67980018 /usr/lib/libgcc_s.so.1 +7f78a1326000-7f78a132a000 r--p 00027000 103:05 67980018 /usr/lib/libgcc_s.so.1 +7f78a132a000-7f78a132b000 r--p 0002b000 103:05 67980018 /usr/lib/libgcc_s.so.1 +7f78a132b000-7f78a132c000 rw-p 0002c000 103:05 67980018 /usr/lib/libgcc_s.so.1 +7f78a132c000-7f78a132e000 r--p 00000000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 +7f78a132e000-7f78a134e000 r-xp 00002000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 +7f78a134e000-7f78a1357000 r--p 00022000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 +7f78a1357000-7f78a1359000 r--p 0002a000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 +7f78a1359000-7f78a135a000 rw-p 0002c000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 +7f78a135a000-7f78a135b000 rw-p 00000000 00:00 0 +7f78a135b000-7f78a135f000 ---p 00000000 00:00 0 +7f78a135f000-7f78a165d000 rw-p 00000000 00:00 0 +7f78a165d000-7f78a17f8000 rw-p 00000000 00:00 0 +7f78a17f8000-7f78a19fa000 rw-p 00000000 00:00 0 +7f78a19fa000-7f78a423e000 rw-p 00000000 00:00 0 +7f78a423e000-7f78a8000000 ---p 00000000 00:00 0 +7f78a8000000-7f78a8491000 rw-p 00000000 00:00 0 +7f78a8491000-7f78ac000000 ---p 00000000 00:00 0 +7f78ac000000-7f78ad191000 rw-p 00000000 00:00 0 +7f78ad191000-7f78b0000000 ---p 00000000 00:00 0 +7f78b0000000-7f78b0002000 r--p 00000000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 +7f78b0002000-7f78b000a000 r-xp 00002000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 +7f78b000a000-7f78b000c000 r--p 0000a000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 +7f78b000c000-7f78b000d000 r--p 0000c000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 +7f78b000d000-7f78b000e000 rw-p 0000d000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 +7f78b000e000-7f78b001a000 r--p 00000000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 +7f78b001a000-7f78b0030000 r-xp 0000c000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 +7f78b0030000-7f78b0039000 r--p 00022000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 +7f78b0039000-7f78b003a000 r--p 0002a000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 +7f78b003a000-7f78b003b000 rw-p 0002b000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 +7f78b003b000-7f78b0c47000 rw-p 00000000 00:00 0 +7f78b0c47000-7f78b0f4a000 rw-p 00000000 00:00 0 +7f78b0f4a000-7f78b10e6000 rw-p 00000000 00:00 0 +7f78b10e6000-7f78b1586000 ---p 00000000 00:00 0 +7f78b1586000-7f78b15a6000 rw-p 00000000 00:00 0 +7f78b15a6000-7f78b1f86000 ---p 00000000 00:00 0 +7f78b1f86000-7f78b1fc6000 rw-p 00000000 00:00 0 +7f78b1fc6000-7f78c0806000 ---p 00000000 00:00 0 +7f78c0806000-7f78c081a000 rw-p 00000000 00:00 0 +7f78c081a000-7f78c08ae000 ---p 00000000 00:00 0 +7f78c08ae000-7f78c08b2000 rw-p 00000000 00:00 0 +7f78c08b2000-7f78c09ee000 ---p 00000000 00:00 0 +7f78c09ee000-7f78c09f6000 rw-p 00000000 00:00 0 +7f78c09f6000-7f78c26fe000 ---p 00000000 00:00 0 +7f78c26fe000-7f78c2712000 rw-p 00000000 00:00 0 +7f78c2712000-7f78c27a6000 ---p 00000000 00:00 0 +7f78c27a6000-7f78c27aa000 rw-p 00000000 00:00 0 +7f78c27aa000-7f78c28e6000 ---p 00000000 00:00 0 +7f78c28e6000-7f78c28ee000 rw-p 00000000 00:00 0 +7f78c28ee000-7f78c45f6000 ---p 00000000 00:00 0 +7f78c45f6000-7f78c5000000 rw-p 00000000 00:00 0 +7f78c5000000-7f78c5bb0000 rwxp 00000000 00:00 0 +7f78c5bb0000-7f78cc45f000 ---p 00000000 00:00 0 +7f78cc45f000-7f78cca2f000 rwxp 00000000 00:00 0 +7f78cca2f000-7f78ccb9f000 ---p 00000000 00:00 0 +7f78ccb9f000-7f78ccf2f000 rwxp 00000000 00:00 0 +7f78ccf2f000-7f78d4000000 ---p 00000000 00:00 0 +7f78d4000000-7f78d4913000 rw-p 00000000 00:00 0 +7f78d4913000-7f78d8000000 ---p 00000000 00:00 0 +7f78d8000000-7f78d8001000 rw-p 00000000 00:00 0 +7f78d8001000-7f78d8002000 r--p 00000000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 +7f78d8002000-7f78d800c000 r-xp 00001000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 +7f78d800c000-7f78d800f000 r--p 0000b000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 +7f78d800f000-7f78d8010000 r--p 0000d000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 +7f78d8010000-7f78d8011000 rw-p 0000e000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 +7f78d8011000-7f78d8013000 r--p 00000000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 +7f78d8013000-7f78d8022000 r-xp 00002000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 +7f78d8022000-7f78d8024000 r--p 00011000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 +7f78d8024000-7f78d8025000 r--p 00012000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 +7f78d8025000-7f78d8026000 rw-p 00013000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 +7f78d8026000-7f78d8836000 rw-p 00000000 00:00 0 +7f78d8836000-7f78d8917000 ---p 00000000 00:00 0 +7f78d8917000-7f78d892f000 rw-p 00000000 00:00 0 +7f78d892f000-7f78d8a00000 ---p 00000000 00:00 0 +7f78d8a00000-7f78dbef3000 r--s 00000000 103:05 54040325 /opt/android-studio/jbr/lib/modules +7f78dbef3000-7f78dbef4000 r-xp 00000000 00:2f 21007 /tmp/ffi7UxbMI (deleted) +7f78dbef4000-7f78dbef5000 rw-p 00000000 00:00 0 +7f78dbef5000-7f78dbef7000 r--p 00000000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 +7f78dbef7000-7f78dbef9000 r-xp 00002000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 +7f78dbef9000-7f78dbefa000 r--p 00004000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 +7f78dbefa000-7f78dbefb000 r--p 00004000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 +7f78dbefb000-7f78dbefc000 rw-p 00005000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 +7f78dbefc000-7f78dbefe000 r--p 00000000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 +7f78dbefe000-7f78dbf18000 r-xp 00002000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 +7f78dbf18000-7f78dbf1b000 r--p 0001c000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 +7f78dbf1b000-7f78dbf1c000 r--p 0001f000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 +7f78dbf1c000-7f78dbf1d000 rw-p 00020000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 +7f78dbf1d000-7f78dbfc9000 rw-p 00000000 00:00 0 +7f78dbfc9000-7f78dbfcd000 ---p 00000000 00:00 0 +7f78dbfcd000-7f78dc0c9000 rw-p 00000000 00:00 0 +7f78dc0c9000-7f78dc0d9000 r--p 00000000 103:05 67898319 /usr/lib/libm.so.6 +7f78dc0d9000-7f78dc175000 r-xp 00010000 103:05 67898319 /usr/lib/libm.so.6 +7f78dc175000-7f78dc1fe000 r--p 000ac000 103:05 67898319 /usr/lib/libm.so.6 +7f78dc1fe000-7f78dc1ff000 r--p 00134000 103:05 67898319 /usr/lib/libm.so.6 +7f78dc1ff000-7f78dc200000 rw-p 00135000 103:05 67898319 /usr/lib/libm.so.6 +7f78dc200000-7f78dc81f000 r--p 00000000 103:05 54040334 /opt/android-studio/jbr/lib/server/libjvm.so +7f78dc81f000-7f78dd5c0000 r-xp 0061e000 103:05 54040334 /opt/android-studio/jbr/lib/server/libjvm.so +7f78dd5c0000-7f78dd6ae000 r--p 013be000 103:05 54040334 /opt/android-studio/jbr/lib/server/libjvm.so +7f78dd6ae000-7f78dd6cb000 rw-p 014ab000 103:05 54040334 /opt/android-studio/jbr/lib/server/libjvm.so +7f78dd6cb000-7f78dd75e000 rw-p 00000000 00:00 0 +7f78dd75e000-7f78dd75f000 r--s 00000000 103:05 3171939 /var/cache/fontconfig/787b69f04c7448ed6c581b040220c91f-le64.cache-12 +7f78dd75f000-7f78dd760000 r--s 00000000 103:05 96460852 /home/mey/.cache/fontconfig/3e2c0d130455d44a3f0cd652a06f4a14-le64.cache-12 +7f78dd760000-7f78dd761000 rw-p 00000000 00:00 0 +7f78dd761000-7f78dd762000 rw-p 00000000 00:00 0 +7f78dd762000-7f78dd764000 r--p 00000000 103:05 54040295 /opt/android-studio/jbr/lib/libfontmanager_xawt.so +7f78dd764000-7f78dd765000 r-xp 00001000 103:05 54040295 /opt/android-studio/jbr/lib/libfontmanager_xawt.so +7f78dd765000-7f78dd766000 r--p 00001000 103:05 54040295 /opt/android-studio/jbr/lib/libfontmanager_xawt.so +7f78dd766000-7f78dd768000 rw-p 00001000 103:05 54040295 /opt/android-studio/jbr/lib/libfontmanager_xawt.so +7f78dd768000-7f78dd76a000 r--p 00000000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 +7f78dd76a000-7f78dd76c000 r-xp 00002000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 +7f78dd76c000-7f78dd76e000 r--p 00004000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 +7f78dd76e000-7f78dd76f000 r--p 00005000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 +7f78dd76f000-7f78dd770000 rw-p 00006000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 +7f78dd770000-7f78dd771000 r--p 00000000 103:05 67925070 /usr/lib/libXau.so.6.0.0 +7f78dd771000-7f78dd773000 r-xp 00001000 103:05 67925070 /usr/lib/libXau.so.6.0.0 +7f78dd773000-7f78dd774000 r--p 00003000 103:05 67925070 /usr/lib/libXau.so.6.0.0 +7f78dd774000-7f78dd775000 r--p 00003000 103:05 67925070 /usr/lib/libXau.so.6.0.0 +7f78dd775000-7f78dd776000 rw-p 00004000 103:05 67925070 /usr/lib/libXau.so.6.0.0 +7f78dd776000-7f78dd77a000 r--p 00000000 103:05 54040314 /opt/android-studio/jbr/lib/libnet.so +7f78dd77a000-7f78dd782000 r-xp 00003000 103:05 54040314 /opt/android-studio/jbr/lib/libnet.so +7f78dd782000-7f78dd783000 r--p 0000a000 103:05 54040314 /opt/android-studio/jbr/lib/libnet.so +7f78dd783000-7f78dd784000 rw-p 0000a000 103:05 54040314 /opt/android-studio/jbr/lib/libnet.so +7f78dd784000-7f78dd800000 rw-p 00000000 00:00 0 +7f78dd800000-7f78dd824000 r--p 00000000 103:05 67898298 /usr/lib/libc.so.6 +7f78dd824000-7f78dd99f000 r-xp 00024000 103:05 67898298 /usr/lib/libc.so.6 +7f78dd99f000-7f78dda15000 r--p 0019f000 103:05 67898298 /usr/lib/libc.so.6 +7f78dda15000-7f78dda19000 r--p 00214000 103:05 67898298 /usr/lib/libc.so.6 +7f78dda19000-7f78dda1b000 rw-p 00218000 103:05 67898298 /usr/lib/libc.so.6 +7f78dda1b000-7f78dda23000 rw-p 00000000 00:00 0 +7f78dda23000-7f78dda24000 r--p 00000000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 +7f78dda24000-7f78dda25000 r-xp 00001000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 +7f78dda25000-7f78dda26000 r--p 00002000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 +7f78dda26000-7f78dda27000 r--p 00002000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 +7f78dda27000-7f78dda28000 rw-p 00003000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 +7f78dda28000-7f78dda33000 r--p 00000000 103:05 54040315 /opt/android-studio/jbr/lib/libnio.so +7f78dda33000-7f78dda3c000 r-xp 0000a000 103:05 54040315 /opt/android-studio/jbr/lib/libnio.so +7f78dda3c000-7f78dda3d000 r--p 00012000 103:05 54040315 /opt/android-studio/jbr/lib/libnio.so +7f78dda3d000-7f78dda3e000 rw-p 00012000 103:05 54040315 /opt/android-studio/jbr/lib/libnio.so +7f78dda3e000-7f78dda47000 r--p 00000000 103:05 54040324 /opt/android-studio/jbr/lib/libzip.so +7f78dda47000-7f78dda5c000 r-xp 00008000 103:05 54040324 /opt/android-studio/jbr/lib/libzip.so +7f78dda5c000-7f78dda5d000 r--p 0001c000 103:05 54040324 /opt/android-studio/jbr/lib/libzip.so +7f78dda5d000-7f78dda5e000 rw-p 0001c000 103:05 54040324 /opt/android-studio/jbr/lib/libzip.so +7f78dda5e000-7f78ddaa8000 rw-p 00000000 00:00 0 +7f78ddaa8000-7f78ddaab000 ---p 00000000 00:00 0 +7f78ddaab000-7f78ddabc000 r--p 00000000 103:05 54040301 /opt/android-studio/jbr/lib/libjava.so +7f78ddabc000-7f78ddacb000 r-xp 00010000 103:05 54040301 /opt/android-studio/jbr/lib/libjava.so +7f78ddacb000-7f78ddacd000 r--p 0001e000 103:05 54040301 /opt/android-studio/jbr/lib/libjava.so +7f78ddacd000-7f78ddacf000 rw-p 0001f000 103:05 54040301 /opt/android-studio/jbr/lib/libjava.so +7f78ddacf000-7f78ddad7000 rw-s 00000000 00:2f 21006 /tmp/hsperfdata_mey/1041994 +7f78ddad7000-7f78ddad8000 ---p 00000000 00:00 0 +7f78ddad8000-7f78ddad9000 r--p 00000000 00:00 0 +7f78ddad9000-7f78ddada000 ---p 00000000 00:00 0 +7f78ddada000-7f78ddae3000 r--p 00000000 103:05 54040305 /opt/android-studio/jbr/lib/libjimage.so +7f78ddae3000-7f78ddaf7000 r-xp 00008000 103:05 54040305 /opt/android-studio/jbr/lib/libjimage.so +7f78ddaf7000-7f78ddaf9000 r--p 0001b000 103:05 54040305 /opt/android-studio/jbr/lib/libjimage.so +7f78ddaf9000-7f78ddafa000 rw-p 0001c000 103:05 54040305 /opt/android-studio/jbr/lib/libjimage.so +7f78ddafa000-7f78ddafb000 r--p 00000000 103:05 67898353 /usr/lib/librt.so.1 +7f78ddafb000-7f78ddafc000 r-xp 00001000 103:05 67898353 /usr/lib/librt.so.1 +7f78ddafc000-7f78ddafd000 r--p 00002000 103:05 67898353 /usr/lib/librt.so.1 +7f78ddafd000-7f78ddafe000 r--p 00002000 103:05 67898353 /usr/lib/librt.so.1 +7f78ddafe000-7f78ddaff000 rw-p 00003000 103:05 67898353 /usr/lib/librt.so.1 +7f78ddaff000-7f78ddb01000 rw-p 00000000 00:00 0 +7f78ddb01000-7f78ddb02000 r--p 00000000 103:05 67898348 /usr/lib/libpthread.so.0 +7f78ddb02000-7f78ddb03000 r-xp 00001000 103:05 67898348 /usr/lib/libpthread.so.0 +7f78ddb03000-7f78ddb04000 r--p 00002000 103:05 67898348 /usr/lib/libpthread.so.0 +7f78ddb04000-7f78ddb05000 r--p 00002000 103:05 67898348 /usr/lib/libpthread.so.0 +7f78ddb05000-7f78ddb06000 rw-p 00003000 103:05 67898348 /usr/lib/libpthread.so.0 +7f78ddb06000-7f78ddb07000 r--p 00000000 103:05 67898309 /usr/lib/libdl.so.2 +7f78ddb07000-7f78ddb08000 r-xp 00001000 103:05 67898309 /usr/lib/libdl.so.2 +7f78ddb08000-7f78ddb09000 r--p 00002000 103:05 67898309 /usr/lib/libdl.so.2 +7f78ddb09000-7f78ddb0a000 r--p 00002000 103:05 67898309 /usr/lib/libdl.so.2 +7f78ddb0a000-7f78ddb0b000 rw-p 00003000 103:05 67898309 /usr/lib/libdl.so.2 +7f78ddb0b000-7f78ddb14000 r--p 00000000 103:05 54040306 /opt/android-studio/jbr/lib/libjli.so +7f78ddb14000-7f78ddb23000 r-xp 00008000 103:05 54040306 /opt/android-studio/jbr/lib/libjli.so +7f78ddb23000-7f78ddb24000 r--p 00016000 103:05 54040306 /opt/android-studio/jbr/lib/libjli.so +7f78ddb24000-7f78ddb25000 rw-p 00016000 103:05 54040306 /opt/android-studio/jbr/lib/libjli.so +7f78ddb25000-7f78ddb28000 rw-p 00000000 00:00 0 +7f78ddb28000-7f78ddb6d000 r--p 00000000 103:05 71566212 /etc/ld.so.cache +7f78ddb6d000-7f78ddb71000 r--p 00000000 00:00 0 [vvar] +7f78ddb71000-7f78ddb73000 r--p 00000000 00:00 0 [vvar_vclock] +7f78ddb73000-7f78ddb75000 r-xp 00000000 00:00 0 [vdso] +7f78ddb75000-7f78ddb76000 r--p 00000000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 +7f78ddb76000-7f78ddba3000 r-xp 00001000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 +7f78ddba3000-7f78ddbb2000 r--p 0002e000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 +7f78ddbb2000-7f78ddbb4000 r--p 0003d000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 +7f78ddbb4000-7f78ddbb5000 rw-p 0003f000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 +7f78ddbb5000-7f78ddbb6000 rw-p 00000000 00:00 0 +7ffdf5441000-7ffdf5465000 rw-p 00000000 00:00 0 [stack] +ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] +Total number of mappings: 922 + +JVMTI agents: none + + +VM Arguments: +jvm_args: -Dcompose.application.configure.swing.globals=true -Dcompose.application.resources.dir=/home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/desktop/build/compose/tmp/prepareAppResources -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -Dawt.toolkit.name=XToolkit +java_command: capital.yuri.yuriplayer.desktop.MainKt +java_class_path (initial): /home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/desktop/build/libs/desktop.jar:/home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/components/build/libs/components-jvm.jar:/home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/core/build/libs/core-jvm.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-client-cio-jvm/3.5.2/cd0673816e97d9a658efae6892cf9f93df2591f1/ktor-client-cio-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.desktop/desktop-jvm/1.8.2/61be96a019ac35e8751b08b33856344a24586648/desktop-jvm-1.8.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-network-ktor3-jvm/3.5.0/d85dbeb646cf44c53a733fcb63cc82f6a6d54f16/coil-network-ktor3-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-compose-jvm/3.5.0/820523ee39decd98eecdb61a7fc03412eedbbabc/coil-compose-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-jvm/3.5.0/602087c06fa4b1795a1572a26b174516c4d932e5/coil-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-compose-core-jvm/3.5.0/8ebdd416d1157820ca47dc6a7644c56716a9c8fa/coil-compose-core-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-network-core-jvm/3.5.0/5a995a2262efe8e8569b40fee8ce223aaa5599d8/coil-network-core-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-core-jvm/3.5.0/5e82ee4827a88516057cb4ebd22871f6672643a8/coil-core-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-client-core-jvm/3.5.2/d6525cc26aa12e341b14a37dea698fcb4eca54cb/ktor-client-core-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-http-cio-jvm/3.5.2/4d8f22cce83a2c865e0d3e64d4abfd4ea13a8e6c/ktor-http-cio-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-websocket-serialization-jvm/3.5.2/d391929e7082af2e6f0cddcf7df4b8f934b0a297/ktor-websocket-serialization-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-serialization-jvm/3.5.2/beed828bb6d5388782ee3d7ab9e17f19888a60a9/ktor-serialization-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-websockets-jvm/3.5.2/9906c60d014018f83dd0f4f6eff306956dc1fe09/ktor-websockets-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-network-tls-jvm/3.5.2/893f2bffbb2308e2fca22ec9b35a72ce3e3f8738/ktor-network-tls-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-slf4j/1.11.0/556153d93573d016dd4e5df37e3d03d07d8d303/kotlinx-coroutines-slf4j-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material3/material3-desktop/1.8.2/1f1f838506ceb2c25499517559f120517770b014/material3-desktop-1.8.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-desktop/1.8.2/7aae6f749a060718d931957a27d495f26b2d4f37/material-desktop-1.8.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-ripple-desktop/1.8.2/5c9c96a95930f802de1b611fb610db4a0eff49cf/material-ripple-desktop-1.8.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.animation/animation-core-desktop/1.11.1/1bf633ecd1a38a1cf7a41630bc56cba0da9cc25b/animation-core-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.animation/animation-desktop/1.11.1/f873fb28f1a08d45a4dd20a0130b3da8cadf383c/animation-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.foundation/foundation-layout-desktop/1.11.1/853fc5d01388da6515ce40b719798914012f6469/foundation-layout-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.foundation/foundation-desktop/1.11.1/6df316e7af2c0c97f200de289925f62ca8fce53d/foundation-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-icons-extended-desktop/1.7.3/c7c5465dd754043076875b32bce0dd7f5a354fe8/material-icons-extended-desktop-1.7.3.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-icons-core-desktop/1.7.3/414b3096050fe647b0638832eace80f22c06b85c/material-icons-core-desktop-1.7.3.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-tooling-preview-desktop/1.11.1/97ec578d33adc34adcaa63e9af7bb2328c6c29b7/ui-tooling-preview-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-backhandler-desktop/1.11.1/bd3540bb1d6abd60c5e58f32663b2f3e89406cd1/ui-backhandler-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-unit-desktop/1.11.1/f6c06f98da206d01dc40c1587c6bc4c40cbef623/ui-unit-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-graphics-desktop/1.11.1/6ea106d02adf29694eedf12fdfb621947d3621bf/ui-graphics-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-geometry-desktop/1.11.1/5e430152487375359973ac9874653d335dcc220/ui-geometry-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-util-desktop/1.11.1/4302c582878b82ab24e1f34e6e90f967f7b416ed/ui-util-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-text-desktop/1.11.1/6ded685e2601260d6231c859a849b96ee6c60368/ui-text-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-desktop/1.11.1/24c40c0d60c77a6fb34f930f13bfa4a3ff62c0f/ui-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.skiko/skiko-awt-runtime-linux-x64/0.144.6/291f8626a2fecd06e9f585424360560189ab7303/skiko-awt-runtime-linux-x64-0.144.6.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.skiko/skiko-awt/0.144.6/c9bec39e3c517ed5591c71c7db0cbbe64b6be849/skiko-awt-0.144.6.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-http-jvm/3.5.2/d28bf8120efdc33354d91d99c0842187759e3c70/ktor-http-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-events-jvm/3.5.2/5e64959ec6f91dff8ea281cfa102ae73bce000f7/ktor-events-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-sse-jvm/3.5.2/84a9273210b25e32403252149f7bcd16b7ab9fbb/ktor-sse-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-network-jvm/3.5.2/1d4a6deadcc3a30de665ea26f98eea3fc52750bb/ktor-network-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-utils-jvm/3.5.2/dc58dadecf1646d013ba0dda5dffab0b71a3fefd/ktor-utils-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-io-jvm/3.5.2/b27620278e10b206310a22b9303c5274f239872d/ktor-io-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.navigationevent/navigationevent-desktop/1.0.1/ddb1e7fe97ed51c3d35bba493f97fcf12398719/navigationevent-desktop-1.0.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.compose.runtime/runtime-annotation-jvm/1.11.2/db4aa964627ac96331b72e231b9ad54318d112f4/runtime-annotation-jvm-1.11.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.compose.runtime/runtime-retain-desktop/1.11.2/3aa3ec26ef2a3d26bc556b51b33d09efc1a198e8/runtime-retain-desktop-1.11.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.compose.runtime/runtime-saveable-desktop/1.11.2/1618fae89791692a224fe7aa79edbd7a8f5b7522/runtime-saveable-desktop-1.11.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.runtime/runtime-saveable-desktop/1.11.1/b7c8a8ed985a0f15b4d8d206de1fc6125b499388/runtime-saveable-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.androidx.lifecycle/lifecycle-runtime-compose-desktop/2.9.6/7b6b6793f3c4c5996b20d1d99cf728e5fac00364/lifecycle-runtime-compose-desktop-2.9.6.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.androidx.savedstate/savedstate-compose-desktop/1.3.6/63692375ffe667f12cb3ea8aa1e605d659ed2add/savedstate-compose-desktop-1.3.6.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.runtime/runtime-desktop/1.11.1/1941336b045aac4b87cc3be81d9fdb9977c88c61/runtime-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-runtime-desktop/2.9.4/1cc820319ec0139e76b8a2fe4be0b356b4e624c4/lifecycle-runtime-desktop-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-jvm/2.9.4/ab739bccdb3541983385af169565c7c035d897e7/lifecycle-common-jvm-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-runtime-compose-desktop/2.9.4/86a4ffec845d4e488078118a0b5095bd1fabcafe/lifecycle-runtime-compose-desktop-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-viewmodel-desktop/2.9.4/93fee6d78f0df0e38f36e0fa2eff74f8da6d5481/lifecycle-viewmodel-desktop-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-viewmodel-savedstate-desktop/2.9.4/72a1919b08c5a4da224d8a65ce1589aa69934e8a/lifecycle-viewmodel-savedstate-desktop-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.savedstate/savedstate-desktop/1.4.0/3b049a0f81643d6b4b900de95bc23efc085c32d5/savedstate-desktop-1.4.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.savedstate/savedstate-compose-desktop/1.4.0/d3d6e0e2108528eb6080dc250da925a572be4202/savedstate-compose-desktop-1.4.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.compose.runtime/runtime-desktop/1.11.2/e93d95553cdd86c1cb8c02f50260cc1c8e341da8/runtime-desktop-1.11.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.11.0/3d57dc678bd8d72a60e7adf0eca5c54e3f4f4b79/kotlinx-coroutines-core-jvm-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-swing/1.11.0/3cf134b8aefd813622edc37e60d66b939b55cb36/kotlinx-coroutines-swing-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-serialization-core-jvm/1.11.0/c1905c9bf7452b045d5af9248835abe64ddad6bf/kotlinx-serialization-core-jvm-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-serialization-json-jvm/1.11.0/e7e045e99e56f07599bb9ccb708c2190e2af0606/kotlinx-serialization-json-jvm-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/2.1.21/fb62497994f5cfa4ad41fcff05020c35976307c6/kotlin-stdlib-jdk8-2.1.21.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-datetime-jvm/0.6.0/a6362032556c0fa4a06be6105a21e944f120fd2d/kotlinx-datetime-jvm-0.6.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.collection/collection-jvm/1.5.0/7ba2c69414d46ebc2dd76598bdd0a75c54281a57/collection-jvm-1.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.2.0/5e1b8b81dfd5f52c56a8d53b18ca759c19a301f3/core-common-2.2.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation-jvm/1.9.1/b17951747e38bf3986a24431b9ba0d039958aa5f/annotation-jvm-1.9.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/2.1.21/f5e72fe5f9501e0d27ec633beb3996c29641b877/kotlin-stdlib-jdk7-2.1.21.jar:/home/mey/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio-jvm/3.17.0/a12d684ec0df9541b3869ae60a4a5257fd3613df/okio-jvm-3.17.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/atomicfu-jvm/0.28.0/6a9ac536f4b2144e57c176fd4e26821eb12084f4/atomicfu-jvm-0.28.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-io-core-jvm/0.9.1/8c926478f17f8109e01e68adee93795c68dcad3c/kotlinx-io-core-jvm-0.9.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-io-bytestring-jvm/0.9.1/7a28d93fce9fc8b663a6fe19cb2ae14a6ade7e8a/kotlinx-io-bytestring-jvm-0.9.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/2.4.0/4e9f4b531dce5c7dee58ed2de9a8b9a763c8dc6c/kotlin-stdlib-2.4.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/uk.co.caprica/vlcj/4.10.1/958b353e8b85450e92df5ba56f5cf734fce2d1c2/vlcj-4.10.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/com.github.hypfvieh/dbus-java-transport-native-unixsocket/5.1.1/40db43e95df145299a9dc97f611c8a4e1f7477a3/dbus-java-transport-native-unixsocket-5.1.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/com.github.hypfvieh/dbus-java-core/5.1.1/36af749ddaefd567bd4e8a41c24845a584ce76d9/dbus-java-core-5.1.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna-platform/5.17.0/a4934c44d25a9d8c2ddf4203affd20330cb3426f/jna-platform-5.17.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna/5.17.0/33d12735bef894440780fce64f9758d420c7bae2/jna-5.17.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/23.0.0/8cc20c07506ec18e0834947b84a864bfc094484e/annotations-23.0.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/com.github.goxr3plus/jaudiotagger/2.2.7/2d229776d40b33aca730f2e31157079189431cbf/jaudiotagger-2.2.7.jar:/home/mey/.gradle/caches/modules-2/files-2.1/uk.co.caprica/vlcj-natives/4.8.3/2a84facef1dac138801fdd3d85ffb77d3d1b10a7/vlcj-natives-4.8.3.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.18/78a9e7a37cd6360e0b818e86341b24123d28d4df/slf4j-api-2.0.18.jar:/home/mey/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna-platform-jpms/5.14.0/24fde384a42c2c75c46116edda48895e5763fc85/jna-platform-jpms-5.14.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna-jpms/5.14.0/9513388696e34db7c9b564aaec0ccbd7395e9389/jna-jpms-5.14.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.runtime/jbr-api/1.9.0/2bc161f5472b5fd5610740e4d6ebe896c811cb0e/jbr-api-1.9.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jspecify/jspecify/1.0.0/7425a601c1c7ec76645a78d22b8c6a627edee507/jspecify-1.0.0.jar +Launcher Type: SUN_STANDARD + +[Global flags] + intx CICompilerCount = 12 {product} {ergonomic} + uint ConcGCThreads = 5 {product} {ergonomic} + uint G1ConcRefinementThreads = 18 {product} {ergonomic} + size_t G1HeapRegionSize = 8388608 {product} {ergonomic} + size_t InitialHeapSize = 1040187392 {product} {ergonomic} + size_t MarkStackSize = 4194304 {product} {ergonomic} + size_t MarkStackSizeMax = 536870912 {product} {ergonomic} + size_t MaxHeapSize = 16626221056 {product} {ergonomic} + size_t MaxNewSize = 9974054912 {product} {ergonomic} + size_t MinHeapDeltaBytes = 8388608 {product} {ergonomic} + size_t MinHeapSize = 8388608 {product} {ergonomic} + uintx NonNMethodCodeHeapSize = 7602176 {pd product} {ergonomic} + uintx NonProfiledCodeHeapSize = 122032128 {pd product} {ergonomic} + uintx ProfiledCodeHeapSize = 122023936 {pd product} {ergonomic} + uintx ReservedCodeCacheSize = 251658240 {pd product} {ergonomic} + bool SegmentedCodeCache = true {product} {ergonomic} + size_t SoftMaxHeapSize = 16626221056 {manageable} {ergonomic} + bool UseCompressedOops = true {product lp64_product} {ergonomic} + bool UseG1GC = true {product} {ergonomic} + +Logging: +Log output configuration: + #0: stdout all=warning uptime,level,tags foldmultilines=false + #1: stderr all=off uptime,level,tags foldmultilines=false + +Release file: +JAVA_VERSION="25.0.2" +MODULES="java.base java.compiler java.datatransfer java.xml java.prefs java.desktop java.instrument java.logging java.management java.security.sasl java.naming java.rmi java.management.rmi java.net.http java.scripting java.security.jgss java.transaction.xa java.sql java.sql.rowset java.xml.crypto java.se java.smartcardio jdk.accessibility jdk.internal.jvmstat jdk.attach jdk.charsets jdk.internal.opt jdk.zipfs jdk.compiler jdk.crypto.cryptoki jdk.crypto.ec jdk.dynalink jdk.hotspot.agent jdk.httpserver jdk.internal.ed jdk.internal.le jdk.internal.md jdk.internal.vm.ci jdk.jartool jdk.javadoc jdk.jcmd jdk.jdeps jdk.jdwp.agent jdk.jdi jdk.jfr jdk.jlink jdk.localedata jdk.management jdk.management.agent jdk.management.jfr jdk.naming.dns jdk.naming.rmi jdk.net jdk.sctp jdk.security.auth jdk.security.jgss jdk.unsupported jdk.unsupported.desktop jdk.xml.dom" +IMPLEMENTOR="JetBrains s.r.o." +JAVA_RUNTIME_VERSION="25.0.2+-15348964-b329.117" +LIBC="gnu" +OS_ARCH="x86_64" +OS_NAME="Linux" +SOURCE=".:git:3cf398e84a39" + +Environment Variables: +PATH=/home/mey/.local/share/../bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/opt/cuda/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/rustup/bin:/home/mey/.local/share/JetBrains/Toolbox/scripts:/home/mey/.lmstudio/bin +SHELL=/usr/bin/fish +DISPLAY=:0 +WAYLAND_DISPLAY=wayland-0 +LANG=en_US.UTF-8 +TERM=linux + +Active Locale: +LC_ALL=en_US.UTF-8 +LC_COLLATE=en_US.UTF-8 +LC_CTYPE=en_US.UTF-8 +LC_MESSAGES=en_US.UTF-8 +LC_MONETARY=en_US.UTF-8 +LC_NUMERIC=en_US.UTF-8 +LC_TIME=en_US.UTF-8 + +Signal Handlers: + SIGSEGV: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGBUS: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGFPE: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGPIPE: javaSignalHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked + SIGXFSZ: javaSignalHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked + SIGILL: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGUSR2: SR_handler in libjvm.so, mask=00000000000000000000000000000000, flags=SA_RESTART|SA_SIGINFO, blocked + SIGHUP: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked + SIGINT: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked + SIGTERM: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked + SIGQUIT: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked + SIGTRAP: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked + + +Compilation memory statistics disabled. + +Periodic native trim disabled + +JNI global refs: +JNI global refs: 371, weak refs: 55 + +JNI global refs memory usage: 4019, weak refs: 1489 + +Process memory usage: +Resident Set Size: 576188K (0% of 64925104K total physical memory with 27131332K free physical memory) + +OOME stack traces (most recent first): +Classloader memory used: +Loader jdk.internal.loader.ClassLoaders$AppClassLoader : 30410 K +Loader bootstrap : 22525 K +Loader jdk.internal.loader.ClassLoaders$PlatformClassLoader : 27088 B + +--------------- S Y S T E M --------------- + +OS: +DISTRIB_ID="Arch" +DISTRIB_RELEASE="rolling" +DISTRIB_DESCRIPTION="Arch Linux" +uname: Linux 7.1.8-arch1-3 #1 SMP PREEMPT_DYNAMIC Tue, 11 Aug 2026 09:16:08 +0000 x86_64 +OS uptime: 2 days 8:29 hours +libc: glibc 2.44 NPTL 2.44 +rlimit (soft/hard): STACK 8192k/infinity , CORE infinity/infinity , NPROC 252603/252603 , NOFILE 524288/524288 , AS infinity/infinity , CPU infinity/infinity , DATA infinity/infinity , FSIZE infinity/infinity , MEMLOCK 8192k/8192k +load average: 2.19 1.44 2.00 + +/proc/meminfo: +MemTotal: 64925104 kB +MemFree: 1446336 kB +MemAvailable: 27130844 kB +Buffers: 2096932 kB +Cached: 24164020 kB +SwapCached: 126880 kB +Active: 21758016 kB +Inactive: 35235200 kB +Active(anon): 17947668 kB +Inactive(anon): 14684956 kB +Active(file): 3810348 kB +Inactive(file): 20550244 kB +Unevictable: 11528 kB +Mlocked: 11528 kB +SwapTotal: 244197372 kB +SwapFree: 243609164 kB +Zswap: 99928 kB +Zswapped: 384652 kB +Dirty: 15344 kB +Writeback: 0 kB +AnonPages: 30008184 kB +Mapped: 8290788 kB +Shmem: 1892888 kB +KReclaimable: 2047956 kB +Slab: 2721332 kB +SReclaimable: 2047956 kB +SUnreclaim: 673376 kB +KernelStack: 53312 kB +PageTables: 390472 kB +SecPageTables: 2488 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 276659924 kB +Committed_AS: 76815368 kB +VmallocTotal: 34359738367 kB +VmallocUsed: 320400 kB +VmallocChunk: 0 kB +Percpu: 21312 kB +HardwareCorrupted: 0 kB +AnonHugePages: 12435456 kB +ShmemHugePages: 0 kB +ShmemPmdMapped: 0 kB +FileHugePages: 2408448 kB +FilePmdMapped: 1605632 kB +CmaTotal: 0 kB +CmaFree: 0 kB +Unaccepted: 0 kB +Balloon: 0 kB +GPUActive: 15884 kB +GPUReclaim: 0 kB +HugePages_Total: 0 +HugePages_Free: 0 +HugePages_Rsvd: 0 +HugePages_Surp: 0 +Hugepagesize: 2048 kB +Hugetlb: 0 kB +DirectMap4k: 33897344 kB +DirectMap2M: 32294912 kB +DirectMap1G: 1048576 kB + +/sys/kernel/mm/transparent_hugepage/enabled: [always] madvise never +/sys/kernel/mm/transparent_hugepage/hpage_pmd_size: 2097152 +/sys/kernel/mm/transparent_hugepage/shmem_enabled: always within_size [advise] never deny force +/sys/kernel/mm/transparent_hugepage/defrag (defrag/compaction efforts parameter): always defer defer+madvise [madvise] never + +Process Memory: +Virtual Size: 20007456K (peak: 20162384K) +Resident Set Size: 576184K (peak: 576184K) (anon: 383780K, file: 192352K, shmem: 52K) +Swapped out: 0K +C-Heap outstanding allocations: 132885K, retained: 125414K +glibc malloc tunables: (default) + +/proc/sys/kernel/threads-max (system-wide limit on the number of threads): 505206 +/proc/sys/vm/max_map_count (maximum number of memory map areas a process may have): 1048576 +/proc/sys/vm/swappiness (control to define how aggressively the kernel swaps out anonymous memory): 60 +/proc/sys/kernel/pid_max (system-wide limit on number of process identifiers): 4194304 + +container information not found. +Steal ticks since vm start: 0 +Steal ticks percentage since vm start: 0.000 + +CPU: total 24 (initial active 24) (24 cores per cpu, 2 threads per core) family 25 model 97 stepping 2 microcode 0xa60120a, cx8, cmov, fxsr, ht, mmx, 3dnowpref, sse, sse2, sse3, ssse3, sse4a, sse4.1, sse4.2, popcnt, lzcnt, tsc, tscinvbit, avx, avx2, aes, erms, clmul, bmi1, bmi2, adx, avx512f, avx512dq, avx512cd, avx512bw, avx512vl, sha, fma, vzeroupper, avx512_vpopcntdq, avx512_vpclmulqdq, avx512_vaes, avx512_vnni, clflush, clflushopt, clwb, avx512_vbmi2, avx512_vbmi, rdtscp, rdpid, fsrm, gfni, avx512_bitalg, f16c, pku, ospke, cet_ss, avx512_ifma +CPU Model and flags from /proc/cpuinfo: +model name : AMD Ryzen 9 7900X 12-Core Processor +flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good amd_lbr_v2 nopl xtopology nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpuid_fault cpb cat_l3 cdp_l3 hw_pstate ssbd mba perfmon_v2 ibrs ibpb stibp ibrs_enhanced vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local user_shstk avx512_bf16 clzero irperf xsaveerptr rdpru wbnoinvd cppc arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic vgif x2avic v_spec_ctrl vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid overflow_recov succor smca fsrm flush_l1d amd_lbr_pmc_freeze + +Online cpus: 0-23 +Offline cpus: +BIOS frequency limitation: +Frequency switch latency (ns): 0 +Available cpu frequencies: +Current governor: powersave +Core performance/turbo boost: 1 + +Memory: 4k page, physical 64925104k(27130844k free), swap 244197372k(243609164k free) +Page Sizes: 4k + +vm_info: OpenJDK 64-Bit Server VM (25.0.2+-15348964-b329.117) for linux-amd64 JRE (25.0.2+-15348964-b329.117), built on 2026-05-05T19:01:01Z with clang Android (14488419, +pgo, +bolt, +lto, +mlgo, based on r584948) Clang 22.0.0 (https://android.googlesource.com/toolchain/llvm-project 2d65e4108033380e6fe8e08b1f1826cd2bfb0c99) + +END. diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt index 7eaeffbc..8285159e 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopIndexStore.kt @@ -1,6 +1,6 @@ package capital.yuri.yuriplayer.desktop -import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.data.Song import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -16,14 +16,14 @@ class DesktopIndexStore(cacheDir: String) { private val tracksFile = File(cacheDir, "index-tracks.json") private val cpFile = File(cacheDir, "index-checkpoints.json") - fun loadTracks(): List { + fun loadTracks(): List { if (!tracksFile.isFile) return emptyList() return runCatching { - json.decodeFromString>(tracksFile.readText()) + json.decodeFromString>(tracksFile.readText()) }.getOrDefault(emptyList()) } - fun saveTracks(tracks: List) { + fun saveTracks(tracks: List) { runCatching { tracksFile.parentFile?.mkdirs() val tmp = File(tracksFile.parentFile, "${tracksFile.name}.tmp") diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopNetworkMonitor.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopNetworkMonitor.kt new file mode 100644 index 00000000..30d7ad6d --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopNetworkMonitor.kt @@ -0,0 +1,55 @@ +package capital.yuri.yuriplayer.desktop + +import capital.yuri.yuriplayer.core.log.yuriLog +import capital.yuri.yuriplayer.core.network.NetworkMonitor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import java.net.InetSocketAddress +import java.net.Socket + +/** + * Desktop has no ConnectivityManager, so "online" is a periodic TCP reachability + * probe to a well-known anycast host. Retries every [PROBE_INTERVAL_MS]. + */ +class DesktopNetworkMonitor : NetworkMonitor { + private val log = yuriLog("Network") + + private val _isOnline = MutableStateFlow(true) + override val isOnline: StateFlow = _isOnline.asStateFlow() + + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + init { + scope.launch { + while (isActive) { + _isOnline.value = probe() + delay(PROBE_INTERVAL_MS) + } + } + } + + private fun probe(): Boolean { + return runCatching { + Socket().use { s -> + s.connect(InetSocketAddress(PROBE_HOST, PROBE_PORT), PROBE_TIMEOUT_MS) + } + true + }.onFailure { t -> + log.w { "offline probe failed: ${t.message}" } + }.getOrDefault(false) + } + + companion object { + private const val PROBE_HOST = "1.1.1.1" + private const val PROBE_PORT = 443 + private const val PROBE_TIMEOUT_MS = 3_000 + private const val PROBE_INTERVAL_MS = 15_000L + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt index eb855be4..76c2a18e 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaybackStore.kt @@ -1,17 +1,18 @@ package capital.yuri.yuriplayer.desktop -import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.player.ColdSource import capital.yuri.yuriplayer.core.player.PlaybackSnapshot import capital.yuri.yuriplayer.core.player.QueueLane import capital.yuri.yuriplayer.core.player.RepeatMode +import capital.yuri.yuriplayer.data.Song import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.File /** - * Same job as Android [capital.yuri.yuriplayer.player.PlaybackStateStore]: - * queue + position survive restart. Restore is always paused. + * Same job as Android PlaybackStateStore: queue + position survive restart. + * Restore is always paused. */ class DesktopPlaybackStore(configDir: String) { private val file = File(configDir, "playback_state.json") @@ -36,6 +37,7 @@ class DesktopPlaybackStore(configDir: String) { hotQueue = snap.hotQueue, coldQueue = snap.coldQueue, coldOriginal = snap.coldOriginal, + coldSource = snap.coldSource, lane = snap.lane.name, indexInLane = snap.indexInLane, savedAt = System.currentTimeMillis() @@ -56,9 +58,25 @@ class DesktopPlaybackStore(configDir: String) { if (dto.queue.isEmpty() && dto.coldQueue.isEmpty() && dto.hotQueue.isEmpty()) { return@runCatching null } + + // Legacy files only carried `queue` (flattened, queue[0] = current) + // and `linear` (full context). Reconstruct the lanes so the current + // track is preserved instead of always pointing at linear[0]. + val isLegacy = dto.coldQueue.isEmpty() && dto.hotQueue.isEmpty() + val cold = dto.coldQueue.ifEmpty { dto.linear.ifEmpty { dto.queue } } + val coldOrig = dto.coldOriginal.ifEmpty { dto.linear.ifEmpty { dto.queue } } + val indexInLane = if (isLegacy) { + val currentId = dto.queue.firstOrNull()?.songKey + cold.indexOfFirst { it.songKey == currentId } + .takeIf { it >= 0 } + ?: dto.indexInLane.coerceIn(cold.indices) + } else { + dto.indexInLane.coerceIn(cold.indices) + } + PlaybackSnapshot( queue = dto.queue, - linear = dto.linear.ifEmpty { dto.coldOriginal.ifEmpty { dto.queue } }, + linear = dto.linear.ifEmpty { coldOrig }, index = dto.index, history = dto.history, shuffle = dto.shuffle, @@ -66,10 +84,11 @@ class DesktopPlaybackStore(configDir: String) { volume = dto.volume.coerceIn(0f, 1f), positionMs = dto.positionMs, hotQueue = dto.hotQueue, - coldQueue = dto.coldQueue.ifEmpty { dto.linear.ifEmpty { dto.queue } }, - coldOriginal = dto.coldOriginal.ifEmpty { dto.linear.ifEmpty { dto.queue } }, + coldQueue = cold, + coldOriginal = coldOrig, + coldSource = dto.coldSource, lane = runCatching { QueueLane.valueOf(dto.lane) }.getOrDefault(QueueLane.COLD), - indexInLane = dto.indexInLane + indexInLane = indexInLane ) }.getOrNull() } @@ -85,12 +104,13 @@ class DesktopPlaybackStore(configDir: String) { val shuffle: Boolean = false, val repeat: String = "OFF", val volume: Float = 1f, - val queue: List = emptyList(), - val linear: List = emptyList(), - val history: List = emptyList(), - val hotQueue: List = emptyList(), - val coldQueue: List = emptyList(), - val coldOriginal: List = emptyList(), + val queue: List = emptyList(), + val linear: List = emptyList(), + val history: List = emptyList(), + val hotQueue: List = emptyList(), + val coldQueue: List = emptyList(), + val coldOriginal: List = emptyList(), + val coldSource: ColdSource? = null, val lane: String = "COLD", val indexInLane: Int = 0, val savedAt: Long = 0L diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt index af28ddcb..0628f932 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopPlaylistStore.kt @@ -1,7 +1,9 @@ package capital.yuri.yuriplayer.desktop -import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.indexKeys +import capital.yuri.yuriplayer.core.library.playlistKeys import capital.yuri.yuriplayer.core.log.yuriLog +import capital.yuri.yuriplayer.data.Song import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -64,7 +66,7 @@ class DesktopPlaylistStore(configDir: String) { fun setTracks(id: String, trackIds: List) { update(id) { pl -> - val byId = pl.orderedEntries().associateBy { it.snapshot.id } + val byId = pl.orderedEntries().associateBy { it.snapshot.songKey } val next = trackIds.mapIndexedNotNull { i, key -> byId[key]?.copy(ordinal = i) } @@ -72,7 +74,7 @@ class DesktopPlaylistStore(configDir: String) { } } - fun addTracks(id: String, tracks: List) { + fun addTracks(id: String, tracks: List) { if (tracks.isEmpty()) return update(id) { pl -> val current = pl.orderedEntries().toMutableList() @@ -97,7 +99,7 @@ class DesktopPlaylistStore(configDir: String) { } } - fun remember(tracks: List) { + fun remember(tracks: List) { if (tracks.isEmpty()) return var any = false val next = _playlists.value.map { pl -> @@ -121,16 +123,16 @@ class DesktopPlaylistStore(configDir: String) { } fun playlistsContaining(trackId: String): Set = - playlistsContaining(Track(id = trackId, uri = "")) + playlistsContaining(Song(id = 0, contentUri = "", path = trackId)) - fun playlistsContaining(track: Track): Set { + fun playlistsContaining(track: Song): Set { val keys = track.indexKeys() return _playlists.value.filter { pl -> pl.orderedEntries().any { e -> e.allKeys().any { it in keys } } }.map { it.id }.toSet() } - fun removeTracks(id: String, tracks: List) { + fun removeTracks(id: String, tracks: List) { val drop = tracks.flatMap { it.indexKeys() }.toHashSet() update(id) { pl -> pl.withEntries( @@ -267,13 +269,13 @@ data class DesktopPlaylist( val activeCoverId: String? = null, val createdAtMs: Long = 0L, val updatedAtMs: Long = 0L, - val snapshots: List = emptyList(), + val snapshots: List = emptyList(), val entries: List = emptyList() ) { - fun artworkUri(library: List): String? { + fun artworkUri(library: List): String? { val active = covers.firstOrNull { it.id == activeCoverId } ?: covers.firstOrNull { !it.isSecret } if (active != null) return File(active.path).toURI().toString() - return tracks(library).firstOrNull()?.artworkUri + return tracks(library).firstOrNull()?.albumArtUri } fun orderedEntries(): List { @@ -287,19 +289,19 @@ data class DesktopPlaylist( } return copy( entries = compact, - trackIds = compact.map { it.snapshot.id }, + trackIds = compact.map { it.snapshot.songKey }, snapshots = compact.map { it.snapshot } ) } - fun tracks(library: List): List { - val index = HashMap() + fun tracks(library: List): List { + val index = HashMap() for (t in library) { for (k in t.indexKeys()) index.putIfAbsent(k, t) } val ordered = orderedEntries() val seen = HashSet() - val out = ArrayList(ordered.size) + val out = ArrayList(ordered.size) for (entry in ordered) { val live = entry.allKeys().firstNotNullOfOrNull { index[it] } ?: entry.snapshot if (live.indexKeys().any { it in seen }) continue @@ -310,13 +312,13 @@ data class DesktopPlaylist( } private fun migrateLegacyEntries(): List { - val byKey = HashMap() + val byKey = HashMap() for (t in snapshots) { for (k in t.indexKeys()) byKey.putIfAbsent(k, t) } val used = HashSet() val out = ArrayList() - fun take(t: Track) { + fun take(t: Song) { if (t.indexKeys().any { it in used }) return used += t.indexKeys() out += PlaylistEntry( @@ -337,7 +339,7 @@ data class DesktopPlaylist( @Serializable data class PlaylistEntry( val ordinal: Int, - val snapshot: Track, + val snapshot: Song, val keys: List = emptyList() ) { fun allKeys(): Set = buildSet { @@ -345,7 +347,7 @@ data class PlaylistEntry( addAll(snapshot.indexKeys()) } - fun matches(track: Track): Boolean { + fun matches(track: Song): Boolean { val theirs = track.indexKeys() return allKeys().any { it in theirs } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt index 6f51bdd1..e3e0970c 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/DesktopSession.kt @@ -5,18 +5,20 @@ import capital.yuri.yuriplayer.core.artist.ArtistInfoClient import capital.yuri.yuriplayer.core.artist.ArtistProfileStore import capital.yuri.yuriplayer.core.library.CoverPixels import capital.yuri.yuriplayer.core.library.LocalLibraryScanner -import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.indexKeys +import capital.yuri.yuriplayer.core.library.rawSourceId +import capital.yuri.yuriplayer.core.network.NetworkMonitor import capital.yuri.yuriplayer.core.os.OsMediaControls import capital.yuri.yuriplayer.core.platform.appDirectories -import capital.yuri.yuriplayer.core.player.PlayerSession import capital.yuri.yuriplayer.core.player.PlaybackSnapshot +import capital.yuri.yuriplayer.core.player.RepeatMode import capital.yuri.yuriplayer.core.source.JellyfinCatalog import capital.yuri.yuriplayer.core.source.LibrarySourceStore import capital.yuri.yuriplayer.core.source.RemoteAccount import capital.yuri.yuriplayer.core.source.SourceKind import capital.yuri.yuriplayer.core.source.SubsonicCatalog -import capital.yuri.yuriplayer.desktop.os.createOsMediaControls -import capital.yuri.yuriplayer.desktop.player.VlcjPlaybackEngine +import capital.yuri.yuriplayer.data.Song +import capital.yuri.yuriplayer.desktop.player.DesktopPlayerHost import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import kotlinx.coroutines.async @@ -55,18 +57,19 @@ data class DesktopScanSource( val totalHint: Int? = null ) -class DesktopSession { +class DesktopSession( + val player: DesktopPlayerHost, + private val media: OsMediaControls +) { private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Swing) - private val engine = VlcjPlaybackEngine() - val player = PlayerSession(engine) - private val media: OsMediaControls = createOsMediaControls() private val http = HttpClient(CIO) + val network: NetworkMonitor = DesktopNetworkMonitor() - private val _engineMessage = MutableStateFlow(engine.nativeError) + private val _engineMessage = MutableStateFlow(null) val engineMessage: StateFlow = _engineMessage.asStateFlow() - private val _tracks = MutableStateFlow>(emptyList()) - val tracks: StateFlow> = _tracks.asStateFlow() + private val _tracks = MutableStateFlow>(emptyList()) + val tracks: StateFlow> = _tracks.asStateFlow() private val _scanMessage = MutableStateFlow("Scanning…") val scanMessage: StateFlow = _scanMessage.asStateFlow() @@ -116,11 +119,6 @@ class DesktopSession { var onRaise: (() -> Unit)? = null init { - engine.addListener(object : capital.yuri.yuriplayer.core.player.PlaybackEngine.Listener { - override fun onError(message: String, recoverable: Boolean) { - _engineMessage.value = message - } - }) media.attach(object : OsMediaControls.Callbacks { override fun onPlay() { if (!player.isPlaying.value) player.togglePlay() @@ -132,6 +130,8 @@ class DesktopSession { override fun onPrevious() = player.previous() override fun onSeek(positionMs: Long) = player.seekTo(positionMs) override fun onVolume(value: Float) = player.setVolume(value) + override fun onLoop(mode: RepeatMode) = player.setRepeat(mode) + override fun onShuffle(enabled: Boolean) = player.setShuffle(enabled) override fun onRaise() { onRaise?.invoke() } override fun onQuit() = release() }) @@ -146,12 +146,14 @@ class DesktopSession { durationMs = _durationMs.value, volume = player.volume.value ) + media.setLoop(player.repeat.value) + media.setShuffle(player.shuffle.value) delay(250) } } scope.launch(Dispatchers.IO) { player.current.collect { track -> - _coverPixels.value = CoverPixels.argb(track?.artworkUri, track?.path) + _coverPixels.value = CoverPixels.argb(track?.albumArtUri, track?.path) } } scope.launch { @@ -167,6 +169,8 @@ class DesktopSession { durationMs = player.durationMs(), volume = player.volume.value ) + media.setLoop(player.repeat.value) + media.setShuffle(player.shuffle.value) startPersist() } @@ -205,16 +209,16 @@ class DesktopSession { _durationMs.value = player.durationMs() } - private fun freshMedia(track: Track): Track { + private fun freshMedia(track: Song): Song { val remote = sources.remotes.value.firstOrNull { it.id == track.sourceId && it.enabled } ?: return track val raw = track.rawSourceId() ?: return track return when (remote.kind) { SourceKind.SUBSONIC -> track.copy( - uri = subsonic.streamUrl(remote, raw), - artworkUri = track.artworkUri ?: subsonic.coverUrl(remote, raw) + contentUri = subsonic.streamUrl(remote, raw), + albumArtUri = track.albumArtUri ?: subsonic.coverUrl(remote, raw) ) - SourceKind.JELLYFIN -> track.copy(uri = jellyfin.streamUrl(remote, raw)) + SourceKind.JELLYFIN -> track.copy(contentUri = jellyfin.streamUrl(remote, raw)) SourceKind.LOCAL -> track } } @@ -222,11 +226,11 @@ class DesktopSession { private fun resolveSnap(snap: PlaybackSnapshot): PlaybackSnapshot { val lib = _tracks.value if (lib.isEmpty()) return snap - val byKey = HashMap(lib.size * 2) + val byKey = HashMap(lib.size * 2) for (t in lib) { t.indexKeys().forEach { byKey.putIfAbsent(it, t) } } - fun resolve(list: List) = list.map { t -> + fun resolve(list: List) = list.map { t -> t.indexKeys().firstNotNullOfOrNull { byKey[it] } ?: t } return snap.copy( @@ -252,16 +256,16 @@ class DesktopSession { playbackStore.save(player.snapshot()) } - fun playTrack(track: Track) { + fun playTrack(track: Song) { val list = _tracks.value - val i = list.indexOfFirst { it.id == track.id }.coerceAtLeast(0) + val i = list.indexOfFirst { it.songKey == track.songKey }.coerceAtLeast(0) player.play(if (list.isNotEmpty()) list else listOf(track), i) } - fun replaceTracks(updated: List) { + fun replaceTracks(updated: List) { if (updated.isEmpty()) return - val byId = updated.associateBy { it.id } - _tracks.value = _tracks.value.map { byId[it.id] ?: it } + val byId = updated.associateBy { it.songKey } + _tracks.value = _tracks.value.map { byId[it.songKey] ?: it } } fun addFolder(path: String) { @@ -392,16 +396,16 @@ class DesktopSession { requestScan(force = true, sourceId = account.id) } - private fun mergeTracks(incoming: List) { + private fun mergeTracks(incoming: List) { if (incoming.isEmpty()) return val before = _tracks.value.size - _tracks.value = (_tracks.value + incoming).distinctBy { it.id } + _tracks.value = (_tracks.value + incoming).distinctBy { it.songKey } PlaylistLog.index("merge +${incoming.size} $before→${_tracks.value.size}") } - fun ensureTracks(incoming: List) { + fun ensureTracks(incoming: List) { if (incoming.isEmpty()) return - PlaylistLog.index("ensure ${incoming.map { "${it.displayTitle}/${it.id}" }}") + PlaylistLog.index("ensure ${incoming.map { "${it.displayTitle}/${it.songKey}" }}") mergeTracks(incoming) persistIndex() playlists.remember(incoming) @@ -412,25 +416,25 @@ class DesktopSession { pl.trackIds + pl.snapshots.flatMap { it.indexKeys() } }.toHashSet() - private fun replaceSourceTracks(sourceId: String, incoming: List) { + private fun replaceSourceTracks(sourceId: String, incoming: List) { val referenced = playlistRefs() val existing = _tracks.value - val incomingIds = incoming.map { it.id }.toHashSet() + val incomingIds = incoming.map { it.songKey }.toHashSet() val orphans = existing.filter { t -> (t.sourceId ?: LOCAL_SCAN_ID) == sourceId && - t.id !in incomingIds && + t.songKey !in incomingIds && t.indexKeys().any { it in referenced } } val rest = existing.filterNot { (it.sourceId ?: LOCAL_SCAN_ID) == sourceId } val dropped = existing.count { (it.sourceId ?: LOCAL_SCAN_ID) == sourceId && - it.id !in incomingIds && + it.songKey !in incomingIds && it.indexKeys().none { k -> k in referenced } } PlaylistLog.index( "replace source=$sourceId incoming=${incoming.size} orphans=${orphans.size} dropped=$dropped refs=${referenced.size}" ) - _tracks.value = (rest + incoming + orphans).distinctBy { it.id } + _tracks.value = (rest + incoming + orphans).distinctBy { it.songKey } val snaps = playlists.playlists.value.flatMap { it.snapshots } if (snaps.isNotEmpty()) mergeTracks(snaps) } @@ -537,7 +541,7 @@ class DesktopSession { } if (halted()) return@coroutineScope - val remoteTracks = mutableListOf() + val remoteTracks = mutableListOf() for (remote in remotes) { if (halted()) break val got = scanOneSource(remote.id, remote.name, force, onlySourceId) { @@ -556,7 +560,7 @@ class DesktopSession { val leftovers = _tracks.value.filterNot { (it.sourceId ?: LOCAL_SCAN_ID) in keepIds } - _tracks.value = (leftovers + localTracks + remoteTracks).distinctBy { it.id } + _tracks.value = (leftovers + localTracks + remoteTracks).distinctBy { it.songKey } } else if (onlySourceId != null && force) { replaceSourceTracks(onlySourceId, localTracks + remoteTracks) } else { @@ -581,9 +585,6 @@ class DesktopSession { } finally { currentSourceId.set(null) sourceJob = null - if (!pauseAll.get()) { - // keep isScanning true only while a source child is in flight - } _isScanning.value = false } } @@ -615,8 +616,8 @@ class DesktopSession { name: String, force: Boolean, onlySourceId: String?, - block: suspend () -> List - ): List { + block: suspend () -> List + ): List { if (!shouldScan(id, force, onlySourceId)) { val src = _scanSources.value.firstOrNull { it.id == id } if (src?.status == DesktopScanStatus.DONE) { @@ -679,7 +680,7 @@ class DesktopSession { } } - private fun scanLocalLibrary(): List { + private fun scanLocalLibrary(): List { val defaultRoots = LocalLibraryScanner.defaultRoots() val extra = sources.extraFolders.value.map { File(it) }.filter { it.isDirectory } val roots = (defaultRoots + extra).distinctBy { it.absolutePath } @@ -691,7 +692,7 @@ class DesktopSession { return if (roots.isEmpty()) emptyList() else LocalLibraryScanner.scan(roots) } - private suspend fun fetchRemote(remote: RemoteAccount, force: Boolean): List { + private suspend fun fetchRemote(remote: RemoteAccount, force: Boolean): List { val src = _scanSources.value.firstOrNull { it.id == remote.id } val known = src?.count ?: _tracks.value.count { it.sourceId == remote.id } val already = known > 0 && src?.status == DesktopScanStatus.DONE @@ -735,10 +736,10 @@ class DesktopSession { remote: RemoteAccount, startFrom: Int, priorDelivered: Int - ): List { + ): List { var cursor = startFrom var delivered = priorDelivered - val collected = ArrayList() + val collected = ArrayList() val result = jellyfin.listTracks(authed, startFrom = startFrom) { page, next, total -> collected += page cursor = next @@ -768,10 +769,10 @@ class DesktopSession { remote: RemoteAccount, startAlbumOffset: Int, priorDelivered: Int - ): List { + ): List { var cursor = startAlbumOffset var delivered = priorDelivered - val collected = ArrayList() + val collected = ArrayList() val result = subsonic.listTracks(remote, startAlbumOffset = startAlbumOffset) { page, next, _ -> collected += page cursor = next @@ -856,12 +857,12 @@ class DesktopSession { return out } - suspend fun searchRemotes(query: String, sourceIds: Set?): List { + suspend fun searchRemotes(query: String, sourceIds: Set?): List { val q = query.trim() if (q.isEmpty()) return emptyList() val remotes = sources.remotes.value.filter { it.enabled } .filter { sourceIds == null || it.id in sourceIds } - val out = ArrayList() + val out = ArrayList() for (remote in remotes) { val result = when (remote.kind) { SourceKind.JELLYFIN -> jellyfin.searchTracks(remote, q) diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt index 84560c9b..365b0a0d 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/Main.kt @@ -15,9 +15,11 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import capital.yuri.yuriplayer.components.theme.isDark +import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.core.platform.HostOs import capital.yuri.yuriplayer.core.platform.appDirectories import capital.yuri.yuriplayer.core.platform.hostOs +import capital.yuri.yuriplayer.desktop.di.desktopModule import capital.yuri.yuriplayer.desktop.os.linux.LinuxDesktopIntegration import capital.yuri.yuriplayer.desktop.player.LibVlcBootstrap import capital.yuri.yuriplayer.desktop.ui.YuriDesktopApp @@ -28,13 +30,28 @@ import coil3.network.ktor3.KtorNetworkFetcherFactory import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import okio.Path.Companion.toOkioPath +import org.koin.core.context.GlobalContext +import org.koin.core.context.startKoin import java.io.File +private val log = yuriLog("Main") + fun main() { + // Capture any uncaught throwable so crashes are visible in the log instead + // of being swallowed by the Swing/AWT event loop. + Thread.setDefaultUncaughtExceptionHandler { t, e -> + log.e(e) { "uncaught exception on ${t.name}" } + } + if (hostOs() == HostOs.LINUX) LinuxDesktopIntegration.install() val dirs = appDirectories() System.setProperty("LOG_DIR", dirs.cacheDir) LibVlcBootstrap.install() + + startKoin { + modules(desktopModule) + } + application { val dirs = appDirectories() setSingletonImageLoaderFactory { context -> @@ -47,7 +64,7 @@ fun main() { } .build() } - val session = remember { DesktopSession() } + val session = remember { GlobalContext.get().get() } val state = rememberWindowState(width = 1280.dp, height = 800.dp) Window( onCloseRequest = { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt index 8314f726..161ad552 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/PlaylistLog.kt @@ -1,15 +1,17 @@ package capital.yuri.yuriplayer.desktop -import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.catalogKey +import capital.yuri.yuriplayer.core.library.looseKey import capital.yuri.yuriplayer.core.log.yuriLog +import capital.yuri.yuriplayer.data.Song internal object PlaylistLog { private val playlist = yuriLog("Playlist") private val index = yuriLog("Index") - fun add(name: String, track: Track) { + fun add(name: String, track: Song) { playlist.d { - "add '$name' title='${track.displayTitle}' id=${track.id} " + + "add '$name' title='${track.displayTitle}' id=${track.songKey} " + "source=${track.sourceId} catalog=${track.catalogKey()} loose=${track.looseKey()}" } } @@ -17,8 +19,8 @@ internal object PlaylistLog { fun resolve( playlistName: String, trackIds: List, - snapshots: List, - out: List, + snapshots: List, + out: List, missed: List ) { playlist.w { @@ -28,4 +30,4 @@ internal object PlaylistLog { } fun index(msg: String) = index.d { msg } -} \ No newline at end of file +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/di/DesktopModule.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/di/DesktopModule.kt new file mode 100644 index 00000000..01f2fe00 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/di/DesktopModule.kt @@ -0,0 +1,46 @@ +package capital.yuri.yuriplayer.desktop.di + +import capital.yuri.yuriplayer.core.os.OsMediaControls +import capital.yuri.yuriplayer.core.player.PlaybackEngine +import capital.yuri.yuriplayer.desktop.DesktopSession +import capital.yuri.yuriplayer.desktop.os.createOsMediaControls +import capital.yuri.yuriplayer.desktop.player.DesktopPlayerHost +import capital.yuri.yuriplayer.desktop.player.VlcjPlaybackEngine +import capital.yuri.yuriplayer.player.MusicServiceAutoPlay +import capital.yuri.yuriplayer.player.QueueManager +import capital.yuri.yuriplayer.player.radio.RadioEngine +import capital.yuri.yuriplayer.player.radio.RadioPlaybackAlgorithm +import capital.yuri.yuriplayer.player.radio.ReleasePoolAlgorithm +import org.koin.dsl.module + +/** + * Desktop DI, mirroring the Android player wiring: the shared core queue + * objects are singletons here too, and platform seams (engine + OS media + * controls) are provided per-platform. + */ +val desktopModule = module { + single { VlcjPlaybackEngine() } + single { createOsMediaControls() } + + // Shared queue / radio — same singletons Android uses. + single { RadioPlaybackAlgorithm() } + single { ReleasePoolAlgorithm() } + single { + RadioEngine( + catalog = { emptyList() }, + autoPlayEnabled = { false }, + playbackAlgo = get(), + poolAlgo = get() + ) + } + single { MusicServiceAutoPlay(get()) } + + single(createdAtStart = true) { + val queueManager = QueueManager() + queueManager.autoPlayHelper = get() + queueManager + } + + single { DesktopPlayerHost(get(), get()) } + single { DesktopSession(get(), get()) } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/MediasessionControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/MediasessionControls.kt new file mode 100644 index 00000000..179f9086 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/MediasessionControls.kt @@ -0,0 +1,163 @@ +package capital.yuri.yuriplayer.desktop.os + +import capital.yuri.yuriplayer.core.log.yuriLog +import capital.yuri.yuriplayer.core.os.OsMediaControls +import capital.yuri.yuriplayer.core.player.RepeatMode +import capital.yuri.yuriplayer.data.Song +import dev.toastbits.mediasession.MediaSession +import dev.toastbits.mediasession.MediaSessionLoopMode +import dev.toastbits.mediasession.MediaSessionMetadata +import dev.toastbits.mediasession.MediaSessionPlaybackStatus +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.atomic.AtomicReference + +/** + * Cross-platform system media session backed by dev.toastbits:mediasession. + * + * Linux -> MPRIS (`playerctl`, GNOME/KDE media keys); Windows -> SMTC. + * The library returns null on macOS, which keeps its own JNA Now Playing impl + * (see [capital.yuri.yuriplayer.desktop.os.mac.MacNowPlayingControls]). + */ +class MediasessionControls : OsMediaControls { + private val log = yuriLog("Mediasession") + + private val positionMs = AtomicLong(0L) + private val lastTrackId = AtomicReference(null) + private val lastStatus = AtomicReference(null) + private val lastVolume = AtomicReference(null) + private val lastLoop = AtomicReference(null) + private val lastShuffle = AtomicReference(null) + + private val session: MediaSession? = runCatching { + MediaSession.create { positionMs.get() } + }.onFailure { t -> + log.w(t) { "mediasession unavailable: ${t.message}" } + }.getOrNull() + + override fun attach(callbacks: OsMediaControls.Callbacks) { + val s = session ?: return + runCatching { + s.onRaise = { callbacks.onRaise() } + s.onQuit = { callbacks.onQuit() } + s.onNext = { callbacks.onNext() } + s.onPrevious = { callbacks.onPrevious() } + s.onPause = { callbacks.onPause() } + s.onPlayPause = { callbacks.onPlayPause() } + s.onStop = { callbacks.onStop() } + s.onPlay = { callbacks.onPlay() } + // The vendored library converts MPRIS microseconds to ms here. + s.onSeek = { byMs -> callbacks.onSeek((positionMs.get() + byMs).coerceAtLeast(0)) } + s.onSetPosition = { toMs -> callbacks.onSeek(toMs.coerceAtLeast(0)) } + s.onSetLoop = { mode -> callbacks.onLoop(mode.toRepeatMode()) } + s.onSetShuffle = { enabled -> callbacks.onShuffle(enabled) } + s.onSetVolume = { v -> callbacks.onVolume(v) } + s.setIdentity(APP_ID) + s.setDesktopEntry(APP_ID) + s.setSupportedUriSchemes(SUPPORTED_SCHEMES) + s.setSupportedMimeTypes(SUPPORTED_MIME_TYPES) + s.setEnabled(true) + log.i { "mediasession enabled as $APP_ID" } + }.onFailure { t -> + log.w(t) { "mediasession attach failed: ${t.message}" } + } + } + + override fun update( + track: Song?, + playing: Boolean, + positionMs: Long, + durationMs: Long, + volume: Float + ) { + val s = session ?: return + this.positionMs.set(positionMs) + + // mediasession 0.1.1 is rough around the edges (e.g. Float vs Double in + // setVolume); never let a marshalling bug crash the ticker coroutine. + runCatching { + val status = when { + playing -> MediaSessionPlaybackStatus.PLAYING + track != null -> MediaSessionPlaybackStatus.PAUSED + else -> MediaSessionPlaybackStatus.STOPPED + } + if (lastStatus.getAndSet(status) != status) { + s.setPlaybackStatus(status) + } + + val trackId = track?.songKey + if (lastTrackId.getAndSet(trackId) != trackId) { + s.setMetadata(track?.toMediaSessionMetadata(durationMs) ?: MediaSessionMetadata()) + } + + val v = volume.coerceIn(0f, 1f) + if (lastVolume.getAndSet(v) != v) { + s.setVolume(v) + } + }.onFailure { t -> + log.w(t) { "mediasession update failed: ${t.message}" } + } + } + + override fun setLoop(mode: RepeatMode) { + val s = session ?: return + if (lastLoop.getAndSet(mode) == mode) return + runCatching { s.setLoopMode(mode.toMediaSessionLoopMode()) } + .onFailure { t -> log.w(t) { "mediasession setLoop failed: ${t.message}" } } + } + + override fun setShuffle(enabled: Boolean) { + val s = session ?: return + if (lastShuffle.getAndSet(enabled) == enabled) return + runCatching { s.setShuffle(enabled) } + .onFailure { t -> log.w(t) { "mediasession setShuffle failed: ${t.message}" } } + } + + override fun release() { + runCatching { session?.setEnabled(false) } + } + + companion object { + const val APP_ID = "yuriplayer" + private val SUPPORTED_SCHEMES = listOf("file", "http", "https") + private val SUPPORTED_MIME_TYPES = listOf( + "audio/mpeg", "audio/flac", "audio/ogg", "audio/mp4" + ) + } +} + +private fun Song.toMediaSessionMetadata(durationMs: Long): MediaSessionMetadata = + MediaSessionMetadata( + track_id = mprisTrackId(songKey), + title = displayTitle, + artist = displayArtist, + album = displayAlbum, + album_artists = listOf(displayArtist), + length_ms = durationMs.takeIf { it > 0 }, + art_url = albumArtUri?.takeIf { it.startsWith("file:") || it.startsWith("http") } + ) + +/** + * MPRIS `mpris:trackid` must be a valid DBus object path: starts with `/` and + * every non-empty segment matches `[A-Za-z0-9_]+`. Any other character is + * collapsed to `_`. + */ +private fun mprisTrackId(id: String): String { + val safe = id + .map { c -> if (c.isLetterOrDigit() || c == '_') c else '_' } + .joinToString("") + .takeLast(48) + .ifBlank { "track" } + return "/org/mpris/MediaPlayer2/Track/$safe" +} + +private fun RepeatMode.toMediaSessionLoopMode(): MediaSessionLoopMode = when (this) { + RepeatMode.OFF -> MediaSessionLoopMode.NONE + RepeatMode.COLD -> MediaSessionLoopMode.ALL + RepeatMode.ONE -> MediaSessionLoopMode.ONE +} + +private fun MediaSessionLoopMode.toRepeatMode(): RepeatMode = when (this) { + MediaSessionLoopMode.NONE -> RepeatMode.OFF + MediaSessionLoopMode.ONE -> RepeatMode.ONE + MediaSessionLoopMode.ALL -> RepeatMode.COLD +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/OsMediaControlsFactory.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/OsMediaControlsFactory.kt index 6a992ab8..5044476e 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/OsMediaControlsFactory.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/OsMediaControlsFactory.kt @@ -4,13 +4,12 @@ import capital.yuri.yuriplayer.core.os.NoOpMediaControls import capital.yuri.yuriplayer.core.os.OsMediaControls import capital.yuri.yuriplayer.core.platform.HostOs import capital.yuri.yuriplayer.core.platform.hostOs -import capital.yuri.yuriplayer.desktop.os.linux.MprisMediaControls import capital.yuri.yuriplayer.desktop.os.mac.MacNowPlayingControls -import capital.yuri.yuriplayer.desktop.os.win.WindowsSmtcControls fun createOsMediaControls(): OsMediaControls = when (hostOs()) { - HostOs.LINUX -> MprisMediaControls() + // dev.toastbits:mediasession handles both MPRIS (Linux) and SMTC (Windows). + HostOs.LINUX, HostOs.WINDOWS -> MediasessionControls() + // The library returns null on macOS; keep the JNA Now Playing bridge. HostOs.MACOS -> MacNowPlayingControls() - HostOs.WINDOWS -> WindowsSmtcControls() else -> NoOpMediaControls } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt index 244bc7d2..f37edd81 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/linux/MprisMediaControls.kt @@ -1,395 +1,395 @@ package capital.yuri.yuriplayer.desktop.os.linux - -import capital.yuri.yuriplayer.core.library.Track -import capital.yuri.yuriplayer.core.log.yuriLog -import capital.yuri.yuriplayer.core.os.OsMediaControls -import org.freedesktop.dbus.DBusPath -import org.freedesktop.dbus.annotations.DBusInterfaceName -import org.freedesktop.dbus.connections.impl.DBusConnection -import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder -import org.freedesktop.dbus.exceptions.DBusExecutionException -import org.freedesktop.dbus.interfaces.DBusInterface -import org.freedesktop.dbus.interfaces.Introspectable -import org.freedesktop.dbus.interfaces.Properties -import org.freedesktop.dbus.messages.DBusSignal -import org.freedesktop.dbus.types.UInt32 -import org.freedesktop.dbus.types.Variant -import java.net.URLEncoder -import java.nio.charset.StandardCharsets -import java.util.concurrent.atomic.AtomicReference -import kotlin.math.abs - -/** - * org.mpris.MediaPlayer2.yuriplayer on the session bus. - * GNOME/KDE media keys and `playerctl -p yuriplayer` talk to this. - */ -class MprisMediaControls : OsMediaControls { - private val log = yuriLog("Mpris") - - private val callbacks = AtomicReference(null) - private val state = AtomicReference(NowPlaying()) - private var connection: DBusConnection? = null - private var exported: MprisObject? = null - private var gnomeKeys: GnomeMediaKeys? = null - - override fun attach(callbacks: OsMediaControls.Callbacks) { - this.callbacks.set(callbacks) - try { - val conn = DBusConnectionBuilder.forSessionBus() - .withShared(false) - .build() - val obj = MprisObject(callbacks, state) - conn.exportObject(OBJECT_PATH, obj) - conn.requestBusName(BUS_NAME) - connection = conn - exported = obj - log.w { "MPRIS: claimed $BUS_NAME" } - announce(conn, obj) - grabGnomeKeys(conn, callbacks) - } catch (e: Exception) { - log.w(e) { "MPRIS unavailable: ${e.javaClass.simpleName}: ${e.message}" } - } - } - - private fun announce(conn: DBusConnection, obj: MprisObject) { - val now = state.get() - runCatching { - conn.sendMessage( - Properties.PropertiesChanged( - OBJECT_PATH, - ROOT_IFACE, - mapOf( - "Identity" to Variant("Yuri Player"), - "DesktopEntry" to Variant(APP_ID), - "CanRaise" to Variant(true) - ), - emptyList() - ) - ) - conn.sendMessage( - Properties.PropertiesChanged( - OBJECT_PATH, - PLAYER_IFACE, - mapOf( - "PlaybackStatus" to Variant(obj.playbackStatus(now)), - "Metadata" to Variant(obj.metadata(now)), - "CanControl" to Variant(true), - "CanPlay" to Variant(true), - "CanPause" to Variant(true), - "CanGoNext" to Variant(true), - "CanGoPrevious" to Variant(true) - ), - emptyList() - ) - ) - } - } - - private fun grabGnomeKeys(conn: DBusConnection, cb: OsMediaControls.Callbacks) { - runCatching { - conn.addSigHandler(GnomeMediaKeys.MediaPlayerKeyPressed::class.java) { sig -> - if (sig.application != APP_ID) return@addSigHandler - when (sig.key) { - "Play", "PlayPause" -> cb.onPlayPause() - "Pause" -> cb.onPause() - "Stop" -> cb.onStop() - "Next" -> cb.onNext() - "Previous" -> cb.onPrevious() - } - } - val keys = conn.getRemoteObject( - "org.gnome.SettingsDaemon.MediaKeys", - "/org/gnome/SettingsDaemon/MediaKeys", - GnomeMediaKeys::class.java - ) - keys.GrabMediaPlayerKeys(APP_ID, UInt32(0)) - gnomeKeys = keys - log.w { "MPRIS: grabbed GNOME media keys" } - }.onFailure { - log.w { "MPRIS: GNOME media keys skipped (${it.message})" } - } - } - - override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { - val next = NowPlaying(track, playing, positionMs, durationMs, volume.coerceIn(0f, 1f)) - val prev = state.getAndSet(next) - val conn = connection ?: return - val obj = exported ?: return - val changed = LinkedHashMap>() - if (prev.playing != next.playing || (prev.track == null) != (next.track == null)) { - changed["PlaybackStatus"] = Variant(obj.playbackStatus(next)) - } - if (prev.track?.id != next.track?.id) { - changed["Metadata"] = Variant(obj.metadata(next)) - } - if (abs(prev.volume - next.volume) > 0.01f) { - changed["Volume"] = Variant(next.volume.toDouble()) - } - if (changed.isNotEmpty()) { - runCatching { - conn.sendMessage( - Properties.PropertiesChanged( - OBJECT_PATH, - PLAYER_IFACE, - changed, - emptyList() - ) - ) - } - } - val jump = abs(next.positionMs - prev.positionMs) - if (jump > 2_000 && prev.track?.id == next.track?.id && next.track != null) { - runCatching { - conn.sendMessage(MprisPlayer.Seeked(OBJECT_PATH, next.positionMs * 1000)) - } - } - } - - override fun release() { - runCatching { gnomeKeys?.ReleaseMediaPlayerKeys(APP_ID) } - runCatching { connection?.unExportObject(OBJECT_PATH) } - runCatching { connection?.releaseBusName(BUS_NAME) } - runCatching { connection?.disconnect() } - gnomeKeys = null - connection = null - exported = null - } - - companion object { - const val APP_ID = "yuriplayer" - const val BUS_NAME = "org.mpris.MediaPlayer2.yuriplayer" - const val OBJECT_PATH = "/org/mpris/MediaPlayer2" - const val ROOT_IFACE = "org.mpris.MediaPlayer2" - const val PLAYER_IFACE = "org.mpris.MediaPlayer2.Player" - } -} - -internal data class NowPlaying( - val track: Track? = null, - val playing: Boolean = false, - val positionMs: Long = 0L, - val durationMs: Long = 0L, - val volume: Float = 1f -) - -@DBusInterfaceName("org.mpris.MediaPlayer2") -interface MediaPlayer2 : DBusInterface { - fun Raise() - fun Quit() -} - -@DBusInterfaceName("org.mpris.MediaPlayer2.Player") -interface MprisPlayer : DBusInterface { - fun Next() - fun Previous() - fun Pause() - fun PlayPause() - fun Stop() - fun Play() - fun Seek(Offset: Long) - fun SetPosition(TrackId: DBusPath, Position: Long) - fun OpenUri(Uri: String) - - class Seeked(path: String, val Position: Long) : DBusSignal(path, Position) -} - -@DBusInterfaceName("org.gnome.SettingsDaemon.MediaKeys") -interface GnomeMediaKeys : DBusInterface { - fun GrabMediaPlayerKeys(application: String, time: UInt32) - fun ReleaseMediaPlayerKeys(application: String) - - class MediaPlayerKeyPressed( - path: String, - val application: String, - val key: String - ) : DBusSignal(path, application, key) -} - -internal class MprisObject( - private val callbacks: OsMediaControls.Callbacks, - private val state: AtomicReference -) : MediaPlayer2, MprisPlayer, Properties, Introspectable { - - override fun isRemote(): Boolean = false - override fun getObjectPath(): String = MprisMediaControls.OBJECT_PATH - - override fun Raise() = callbacks.onRaise() - override fun Quit() = callbacks.onQuit() - override fun Next() = callbacks.onNext() - override fun Previous() = callbacks.onPrevious() - override fun Pause() = callbacks.onPause() - override fun PlayPause() = callbacks.onPlayPause() - override fun Stop() = callbacks.onStop() - override fun Play() = callbacks.onPlay() - override fun Seek(Offset: Long) { - val now = state.get() - callbacks.onSeek((now.positionMs + Offset / 1000).coerceAtLeast(0)) - } - - override fun SetPosition(TrackId: DBusPath, Position: Long) { - callbacks.onSeek((Position / 1000).coerceAtLeast(0)) - } - - override fun OpenUri(Uri: String) {} - - override fun Get(interface_name: String, property_name: String): A { - val variant = propertiesFor(interface_name, state.get())[property_name] - ?: throw DBusExecutionException("Unknown property $property_name") - @Suppress("UNCHECKED_CAST") - return variant as A - } - - override fun GetAll(interface_name: String): Map> = - propertiesFor(interface_name, state.get()) - - override fun Set(interface_name: String, property_name: String, value: A) { - if (property_name != "Volume") return - val raw = when (value) { - is Variant<*> -> value.value - else -> value - } - val v = (raw as? Number)?.toDouble() ?: return - callbacks.onVolume(v.coerceIn(0.0, 1.0).toFloat()) - } - - override fun Introspect(): String = INTROSPECT_XML - - fun playbackStatus(now: NowPlaying): String = when { - now.playing -> "Playing" - now.track != null -> "Paused" - else -> "Stopped" - } - - fun metadata(now: NowPlaying): Map> { - val track = now.track ?: return mapOf( - "mpris:trackid" to Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack")) - ) - val safe = URLEncoder.encode(track.id.takeLast(48), StandardCharsets.UTF_8) - .replace("%", "_") - .replace("+", "_") - .replace(".", "_") - val id = "/org/mpris/MediaPlayer2/Track/$safe" - val map = linkedMapOf>( - "mpris:trackid" to Variant(DBusPath(id)), - "xesam:title" to Variant(track.displayTitle), - "xesam:artist" to Variant(listOf(track.displayArtist)), - "xesam:album" to Variant(track.displayAlbum) - ) - if (now.durationMs > 0) { - map["mpris:length"] = Variant(now.durationMs * 1000L) - } - track.artworkUri?.takeIf { it.startsWith("file:") || it.startsWith("http") }?.let { - map["mpris:artUrl"] = Variant(it) - } - return map - } - - private fun propertiesFor(iface: String, now: NowPlaying): Map> = - when (iface) { - MprisMediaControls.ROOT_IFACE -> mapOf( - "CanQuit" to Variant(true), - "CanRaise" to Variant(true), - "HasTrackList" to Variant(false), - "Identity" to Variant("Yuri Player"), - "DesktopEntry" to Variant("yuriplayer"), - "SupportedUriSchemes" to Variant(listOf("file", "http", "https")), - "SupportedMimeTypes" to Variant( - listOf("audio/mpeg", "audio/flac", "audio/ogg", "audio/mp4") - ) - ) - MprisMediaControls.PLAYER_IFACE -> playerProps(now) - else -> emptyMap() - } - - private fun playerProps(now: NowPlaying): Map> = mapOf( - "PlaybackStatus" to Variant(playbackStatus(now)), - "LoopStatus" to Variant("None"), - "Rate" to Variant(1.0), - "Shuffle" to Variant(false), - "Metadata" to Variant(metadata(now)), - "Volume" to Variant(now.volume.toDouble()), - "Position" to Variant(now.positionMs * 1000L), - "MinimumRate" to Variant(1.0), - "MaximumRate" to Variant(1.0), - "CanGoNext" to Variant(true), - "CanGoPrevious" to Variant(true), - "CanPlay" to Variant(true), - "CanPause" to Variant(true), - "CanSeek" to Variant(true), - "CanControl" to Variant(true) - ) - - companion object { - private val INTROSPECT_XML = """ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - """.trimIndent() - } -} +// +//import capital.yuri.yuriplayer.core.library.Track +//import capital.yuri.yuriplayer.core.log.yuriLog +//import capital.yuri.yuriplayer.core.os.OsMediaControls +//import org.freedesktop.dbus.DBusPath +//import org.freedesktop.dbus.annotations.DBusInterfaceName +//import org.freedesktop.dbus.connections.impl.DBusConnection +//import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder +//import org.freedesktop.dbus.exceptions.DBusExecutionException +//import org.freedesktop.dbus.interfaces.DBusInterface +//import org.freedesktop.dbus.interfaces.Introspectable +//import org.freedesktop.dbus.interfaces.Properties +//import org.freedesktop.dbus.messages.DBusSignal +//import org.freedesktop.dbus.types.UInt32 +//import org.freedesktop.dbus.types.Variant +//import java.net.URLEncoder +//import java.nio.charset.StandardCharsets +//import java.util.concurrent.atomic.AtomicReference +//import kotlin.math.abs +// +///** +// * org.mpris.MediaPlayer2.yuriplayer on the session bus. +// * GNOME/KDE media keys and `playerctl -p yuriplayer` talk to this. +// */ +//class MprisMediaControls : OsMediaControls { +// private val log = yuriLog("Mpris") +// +// private val callbacks = AtomicReference(null) +// private val state = AtomicReference(NowPlaying()) +// private var connection: DBusConnection? = null +// private var exported: MprisObject? = null +// private var gnomeKeys: GnomeMediaKeys? = null +// +// override fun attach(callbacks: OsMediaControls.Callbacks) { +// this.callbacks.set(callbacks) +// try { +// val conn = DBusConnectionBuilder.forSessionBus() +// .withShared(false) +// .build() +// val obj = MprisObject(callbacks, state) +// conn.exportObject(OBJECT_PATH, obj) +// conn.requestBusName(BUS_NAME) +// connection = conn +// exported = obj +// log.w { "MPRIS: claimed $BUS_NAME" } +// announce(conn, obj) +// grabGnomeKeys(conn, callbacks) +// } catch (e: Exception) { +// log.w(e) { "MPRIS unavailable: ${e.javaClass.simpleName}: ${e.message}" } +// } +// } +// +// private fun announce(conn: DBusConnection, obj: MprisObject) { +// val now = state.get() +// runCatching { +// conn.sendMessage( +// Properties.PropertiesChanged( +// OBJECT_PATH, +// ROOT_IFACE, +// mapOf( +// "Identity" to Variant("Yuri Player"), +// "DesktopEntry" to Variant(APP_ID), +// "CanRaise" to Variant(true) +// ), +// emptyList() +// ) +// ) +// conn.sendMessage( +// Properties.PropertiesChanged( +// OBJECT_PATH, +// PLAYER_IFACE, +// mapOf( +// "PlaybackStatus" to Variant(obj.playbackStatus(now)), +// "Metadata" to Variant(obj.metadata(now)), +// "CanControl" to Variant(true), +// "CanPlay" to Variant(true), +// "CanPause" to Variant(true), +// "CanGoNext" to Variant(true), +// "CanGoPrevious" to Variant(true) +// ), +// emptyList() +// ) +// ) +// } +// } +// +// private fun grabGnomeKeys(conn: DBusConnection, cb: OsMediaControls.Callbacks) { +// runCatching { +// conn.addSigHandler(GnomeMediaKeys.MediaPlayerKeyPressed::class.java) { sig -> +// if (sig.application != APP_ID) return@addSigHandler +// when (sig.key) { +// "Play", "PlayPause" -> cb.onPlayPause() +// "Pause" -> cb.onPause() +// "Stop" -> cb.onStop() +// "Next" -> cb.onNext() +// "Previous" -> cb.onPrevious() +// } +// } +// val keys = conn.getRemoteObject( +// "org.gnome.SettingsDaemon.MediaKeys", +// "/org/gnome/SettingsDaemon/MediaKeys", +// GnomeMediaKeys::class.java +// ) +// keys.GrabMediaPlayerKeys(APP_ID, UInt32(0)) +// gnomeKeys = keys +// log.w { "MPRIS: grabbed GNOME media keys" } +// }.onFailure { +// log.w { "MPRIS: GNOME media keys skipped (${it.message})" } +// } +// } +// +// override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { +// val next = NowPlaying(track, playing, positionMs, durationMs, volume.coerceIn(0f, 1f)) +// val prev = state.getAndSet(next) +// val conn = connection ?: return +// val obj = exported ?: return +// val changed = LinkedHashMap>() +// if (prev.playing != next.playing || (prev.track == null) != (next.track == null)) { +// changed["PlaybackStatus"] = Variant(obj.playbackStatus(next)) +// } +// if (prev.track?.id != next.track?.id) { +// changed["Metadata"] = Variant(obj.metadata(next)) +// } +// if (abs(prev.volume - next.volume) > 0.01f) { +// changed["Volume"] = Variant(next.volume.toDouble()) +// } +// if (changed.isNotEmpty()) { +// runCatching { +// conn.sendMessage( +// Properties.PropertiesChanged( +// OBJECT_PATH, +// PLAYER_IFACE, +// changed, +// emptyList() +// ) +// ) +// } +// } +// val jump = abs(next.positionMs - prev.positionMs) +// if (jump > 2_000 && prev.track?.id == next.track?.id && next.track != null) { +// runCatching { +// conn.sendMessage(MprisPlayer.Seeked(OBJECT_PATH, next.positionMs * 1000)) +// } +// } +// } +// +// override fun release() { +// runCatching { gnomeKeys?.ReleaseMediaPlayerKeys(APP_ID) } +// runCatching { connection?.unExportObject(OBJECT_PATH) } +// runCatching { connection?.releaseBusName(BUS_NAME) } +// runCatching { connection?.disconnect() } +// gnomeKeys = null +// connection = null +// exported = null +// } +// +// companion object { +// const val APP_ID = "yuriplayer" +// const val BUS_NAME = "org.mpris.MediaPlayer2.yuriplayer" +// const val OBJECT_PATH = "/org/mpris/MediaPlayer2" +// const val ROOT_IFACE = "org.mpris.MediaPlayer2" +// const val PLAYER_IFACE = "org.mpris.MediaPlayer2.Player" +// } +//} +// +//internal data class NowPlaying( +// val track: Track? = null, +// val playing: Boolean = false, +// val positionMs: Long = 0L, +// val durationMs: Long = 0L, +// val volume: Float = 1f +//) +// +//@DBusInterfaceName("org.mpris.MediaPlayer2") +//interface MediaPlayer2 : DBusInterface { +// fun Raise() +// fun Quit() +//} +// +//@DBusInterfaceName("org.mpris.MediaPlayer2.Player") +//interface MprisPlayer : DBusInterface { +// fun Next() +// fun Previous() +// fun Pause() +// fun PlayPause() +// fun Stop() +// fun Play() +// fun Seek(Offset: Long) +// fun SetPosition(TrackId: DBusPath, Position: Long) +// fun OpenUri(Uri: String) +// +// class Seeked(path: String, val Position: Long) : DBusSignal(path, Position) +//} +// +//@DBusInterfaceName("org.gnome.SettingsDaemon.MediaKeys") +//interface GnomeMediaKeys : DBusInterface { +// fun GrabMediaPlayerKeys(application: String, time: UInt32) +// fun ReleaseMediaPlayerKeys(application: String) +// +// class MediaPlayerKeyPressed( +// path: String, +// val application: String, +// val key: String +// ) : DBusSignal(path, application, key) +//} +// +//internal class MprisObject( +// private val callbacks: OsMediaControls.Callbacks, +// private val state: AtomicReference +//) : MediaPlayer2, MprisPlayer, Properties, Introspectable { +// +// override fun isRemote(): Boolean = false +// override fun getObjectPath(): String = MprisMediaControls.OBJECT_PATH +// +// override fun Raise() = callbacks.onRaise() +// override fun Quit() = callbacks.onQuit() +// override fun Next() = callbacks.onNext() +// override fun Previous() = callbacks.onPrevious() +// override fun Pause() = callbacks.onPause() +// override fun PlayPause() = callbacks.onPlayPause() +// override fun Stop() = callbacks.onStop() +// override fun Play() = callbacks.onPlay() +// override fun Seek(Offset: Long) { +// val now = state.get() +// callbacks.onSeek((now.positionMs + Offset / 1000).coerceAtLeast(0)) +// } +// +// override fun SetPosition(TrackId: DBusPath, Position: Long) { +// callbacks.onSeek((Position / 1000).coerceAtLeast(0)) +// } +// +// override fun OpenUri(Uri: String) {} +// +// override fun Get(interface_name: String, property_name: String): A { +// val variant = propertiesFor(interface_name, state.get())[property_name] +// ?: throw DBusExecutionException("Unknown property $property_name") +// @Suppress("UNCHECKED_CAST") +// return variant as A +// } +// +// override fun GetAll(interface_name: String): Map> = +// propertiesFor(interface_name, state.get()) +// +// override fun Set(interface_name: String, property_name: String, value: A) { +// if (property_name != "Volume") return +// val raw = when (value) { +// is Variant<*> -> value.value +// else -> value +// } +// val v = (raw as? Number)?.toDouble() ?: return +// callbacks.onVolume(v.coerceIn(0.0, 1.0).toFloat()) +// } +// +// override fun Introspect(): String = INTROSPECT_XML +// +// fun playbackStatus(now: NowPlaying): String = when { +// now.playing -> "Playing" +// now.track != null -> "Paused" +// else -> "Stopped" +// } +// +// fun metadata(now: NowPlaying): Map> { +// val track = now.track ?: return mapOf( +// "mpris:trackid" to Variant(DBusPath("/org/mpris/MediaPlayer2/TrackList/NoTrack")) +// ) +// val safe = URLEncoder.encode(track.id.takeLast(48), StandardCharsets.UTF_8) +// .replace("%", "_") +// .replace("+", "_") +// .replace(".", "_") +// val id = "/org/mpris/MediaPlayer2/Track/$safe" +// val map = linkedMapOf>( +// "mpris:trackid" to Variant(DBusPath(id)), +// "xesam:title" to Variant(track.displayTitle), +// "xesam:artist" to Variant(track.displayArtist), +// "xesam:album" to Variant(track.displayAlbum) +// ) +// if (now.durationMs > 0) { +// map["mpris:length"] = Variant(now.durationMs * 1000L) +// } +// track.artworkUri?.takeIf { it.startsWith("file:") || it.startsWith("http") }?.let { +// map["mpris:artUrl"] = Variant(it.toString()) +// } +// return map +// } +// +// private fun propertiesFor(iface: String, now: NowPlaying): Map> = +// when (iface) { +// MprisMediaControls.ROOT_IFACE -> mapOf( +// "CanQuit" to Variant(true), +// "CanRaise" to Variant(true), +// "HasTrackList" to Variant(false), +// "Identity" to Variant("Yuri Player"), +// "DesktopEntry" to Variant("yuriplayer"), +// "SupportedUriSchemes" to Variant(listOf("file", "http", "https")), +// "SupportedMimeTypes" to Variant( +// listOf("audio/mpeg", "audio/flac", "audio/ogg", "audio/mp4") +// ) +// ) +// MprisMediaControls.PLAYER_IFACE -> playerProps(now) +// else -> emptyMap() +// } +// +// private fun playerProps(now: NowPlaying): Map> = mapOf( +// "PlaybackStatus" to Variant(playbackStatus(now)), +// "LoopStatus" to Variant("None"), +// "Rate" to Variant(1.0), +// "Shuffle" to Variant(false), +// "Metadata" to Variant(metadata(now)), +// "Volume" to Variant(now.volume.toDouble()), +// "Position" to Variant(now.positionMs * 1000L), +// "MinimumRate" to Variant(1.0), +// "MaximumRate" to Variant(1.0), +// "CanGoNext" to Variant(true), +// "CanGoPrevious" to Variant(true), +// "CanPlay" to Variant(true), +// "CanPause" to Variant(true), +// "CanSeek" to Variant(true), +// "CanControl" to Variant(true) +// ) +// +// companion object { +// private val INTROSPECT_XML = """ +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// """.trimIndent() +// } +//} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt index b5e70227..8aff0592 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/mac/MacNowPlayingControls.kt @@ -1,8 +1,8 @@ package capital.yuri.yuriplayer.desktop.os.mac -import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.log.yuriLog import capital.yuri.yuriplayer.core.os.OsMediaControls +import capital.yuri.yuriplayer.data.Song import com.sun.jna.Library import com.sun.jna.Native import java.util.concurrent.atomic.AtomicReference @@ -27,7 +27,7 @@ class MacNowPlayingControls : OsMediaControls { .onFailure { log.w { "macOS media keys: ${it.message}" } } } - override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { + override fun update(track: Song?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { MacNowPlayingStore.track = track MacNowPlayingStore.playing = playing MacNowPlayingStore.positionMs = positionMs @@ -44,7 +44,7 @@ class MacNowPlayingControls : OsMediaControls { } internal object MacNowPlayingStore { - @Volatile var track: Track? = null + @Volatile var track: Song? = null @Volatile var playing: Boolean = false @Volatile var positionMs: Long = 0 @Volatile var durationMs: Long = 0 diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt index 023ffc0a..4926ddcb 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/os/win/WindowsSmtcControls.kt @@ -1,7 +1,7 @@ package capital.yuri.yuriplayer.desktop.os.win -import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.os.OsMediaControls +import capital.yuri.yuriplayer.data.Song import com.sun.jna.platform.win32.User32 import com.sun.jna.platform.win32.WinDef.HWND import com.sun.jna.platform.win32.WinUser @@ -29,7 +29,7 @@ class WindowsSmtcControls : OsMediaControls { } } - override fun update(track: Track?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { + override fun update(track: Song?, playing: Boolean, positionMs: Long, durationMs: Long, volume: Float) { WindowsNowPlaying.track = track WindowsNowPlaying.playing = playing WindowsNowPlaying.positionMs = positionMs @@ -85,7 +85,7 @@ class WindowsSmtcControls : OsMediaControls { } internal object WindowsNowPlaying { - @Volatile var track: Track? = null + @Volatile var track: Song? = null @Volatile var playing: Boolean = false @Volatile var positionMs: Long = 0 @Volatile var durationMs: Long = 0 diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/DesktopPlayerHost.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/DesktopPlayerHost.kt new file mode 100644 index 00000000..b005de2c --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/DesktopPlayerHost.kt @@ -0,0 +1,257 @@ +package capital.yuri.yuriplayer.desktop.player + +import capital.yuri.yuriplayer.core.log.yuriLog +import capital.yuri.yuriplayer.core.player.ColdSource +import capital.yuri.yuriplayer.core.player.ColdSourceType +import capital.yuri.yuriplayer.core.player.PlaybackEngine +import capital.yuri.yuriplayer.core.player.PlaybackSnapshot +import capital.yuri.yuriplayer.core.player.QueueLane +import capital.yuri.yuriplayer.core.player.RepeatMode +import capital.yuri.yuriplayer.core.player.toPlaybackMedia +import capital.yuri.yuriplayer.data.Song +import capital.yuri.yuriplayer.player.QueueManager +import capital.yuri.yuriplayer.player.QueueSnapshot +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * Desktop playback host: the shared core [QueueManager] (Song-based, same one + * Android drives) + a platform [PlaybackEngine], exposed directly on [Song]. + */ +class DesktopPlayerHost( + private val queue: QueueManager, + private val engine: PlaybackEngine +) { + private val log = yuriLog("Player") + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + + /** Refresh network stream URLs (Jellyfin/Subsonic tokens) before playback. */ + var mediaFor: (Song) -> Song = { it } + + private val _current = MutableStateFlow(null) + val current: StateFlow = _current + + private val _history = MutableStateFlow>(emptyList()) + val history: StateFlow> = _history + + private val _volume = MutableStateFlow(1f) + val volume: StateFlow = _volume + + val isPlaying: StateFlow = engine.isPlaying + + val hotQueue: StateFlow> = + queue.snapshot.map { it.hotQueue } + .stateIn(scope, SharingStarted.Eagerly, emptyList()) + + val coldQueue: StateFlow> = + queue.snapshot.map { it.coldQueue } + .stateIn(scope, SharingStarted.Eagerly, emptyList()) + + val coldSource: StateFlow = + queue.snapshot.map { it.coldSource } + .stateIn(scope, SharingStarted.Eagerly, null) + + val shuffle: StateFlow = + queue.snapshot.map { it.shuffleEnabled } + .stateIn(scope, SharingStarted.Eagerly, false) + + val repeat: StateFlow = + queue.snapshot.map { it.repeatMode } + .stateIn(scope, SharingStarted.Eagerly, RepeatMode.OFF) + + private val listener = object : PlaybackEngine.Listener { + override fun onEnded() { + advanceAndApply(fromUser = false) + } + + override fun onAutoAdvanced() { + advanceAndApply(fromUser = false) + } + + override fun onError(message: String, recoverable: Boolean) { + log.e { "engine error: $message recoverable=$recoverable" } + } + } + + init { + engine.addListener(listener) + } + + // ── transport ────────────────────────────────────────────────────────── + + fun play(tracks: List, startIndex: Int = 0, source: ColdSource? = null) { + if (tracks.isEmpty()) return + queue.playSource(tracks, startIndex, source) + rebuffer(autoPlay = true) + } + + fun togglePlay() { + if (_current.value == null) { + rebuffer(autoPlay = true) + } else if (engine.isPlaying.value) { + engine.pause() + } else { + engine.play() + } + } + + fun pause() = engine.pause() + fun stop() = engine.stop() + fun next() = advanceAndApply(fromUser = true) + fun seekTo(positionMs: Long) = engine.seekTo(positionMs.coerceAtLeast(0L)) + + fun previous() { + applyResult(queue.skipPrevious(engine.getPositionMs(), forceTrackChange = false)) + } + + fun setVolume(value: Float) { + val v = value.coerceIn(0f, 1f) + _volume.value = v + engine.setVolume((v * 100).toInt()) + } + + fun positionMs(): Long = engine.getPositionMs() + fun durationMs(): Long = engine.getDurationMs() + + // ── queue mutations ───────────────────────────────────────────────────── + + fun toggleShuffle() = queue.setShuffle(!queue.getSnapshot().shuffleEnabled) + fun setShuffle(enabled: Boolean) = queue.setShuffle(enabled) + fun cycleRepeat() = queue.cycleRepeatMode() + fun setRepeat(mode: RepeatMode) = queue.setRepeatMode(mode) + + fun enqueue(track: Song) = queue.addToQueue(track) + fun enqueueAll(tracks: List) = queue.addToQueue(tracks) + fun clearHot() = queue.clearHotQueue() + fun clearHistory() { + _history.value = emptyList() + } + fun moveHot(from: Int, to: Int) = queue.moveInQueue(from, to) + fun moveCold(from: Int, to: Int) = queue.moveInContext(from, to) + + fun playTrack(track: Song, context: List = emptyList()) { + val snap = queue.getSnapshot() + val hotIdx = snap.hotQueue.indexOfFirst { it.songKey == track.songKey } + if (hotIdx >= 0) { + queue.playItem(QueueLane.HOT, hotIdx) + rebuffer(autoPlay = true) + return + } + val coldIdx = snap.coldQueue.indexOfFirst { it.songKey == track.songKey } + if (coldIdx >= 0) { + queue.playItem(QueueLane.COLD, coldIdx) + rebuffer(autoPlay = true) + return + } + play(if (context.isNotEmpty()) context else listOf(track), + context.indexOfFirst { it.songKey == track.songKey }.coerceAtLeast(0)) + } + + // ── source checks ─────────────────────────────────────────────────────── + + fun isPlayingFromAlbum(albumKey: String): Boolean = + queue.getSnapshot().isPlayingFromAlbum(albumKey) + + fun isPlayingFromPlaylist(playlistId: String): Boolean = + queue.getSnapshot().isPlayingFromPlaylist(playlistId) + + fun isPlayingFromArtist(artistName: String): Boolean = + queue.coldSource()?.matches(ColdSourceType.ARTIST, artistName) == true + + // ── persistence ───────────────────────────────────────────────────────── + + fun snapshot(): PlaybackSnapshot { + val s = queue.getSnapshot() + return PlaybackSnapshot( + queue = s.flatQueue, + linear = s.coldOriginal, + index = 0, + history = _history.value, + shuffle = s.shuffleEnabled, + repeat = s.repeatMode, + volume = _volume.value, + positionMs = engine.getPositionMs(), + hotQueue = s.hotQueue, + coldQueue = s.coldQueue, + coldOriginal = s.coldOriginal, + coldSource = s.coldSource, + lane = s.lane, + indexInLane = s.indexInLane + ) + } + + fun restore(snap: PlaybackSnapshot, play: Boolean = false) { + _volume.value = snap.volume.coerceIn(0f, 1f) + engine.setVolume((_volume.value * 100).toInt()) + _history.value = snap.history + val qs = QueueSnapshot( + hotQueue = snap.hotQueue, + coldQueue = snap.coldQueue, + coldOriginal = snap.coldOriginal, + coldSource = snap.coldSource, + lane = snap.lane, + indexInLane = snap.indexInLane, + shuffleEnabled = snap.shuffle, + repeatMode = snap.repeat, + playedStack = snap.history.reversed() + ) + queue.restore(qs) + rebuffer(snap.positionMs.coerceAtLeast(0L), play) + } + + fun release() { + engine.removeListener(listener) + engine.release() + scope.cancel() + } + + // ── internals ─────────────────────────────────────────────────────────── + + private fun rebuffer(startMs: Long = 0L, autoPlay: Boolean = false) { + val song = queue.currentSong() + if (song == null) { + engine.stop() + _current.value = null + return + } + val track = mediaFor(song) + recordHistory(track) + _current.value = track + val next = queue.peekNext()?.let { mediaFor(it) } + log.i { "load '${track.songKey}' play=$autoPlay startMs=$startMs" } + engine.load(track.toPlaybackMedia(), next?.toPlaybackMedia(), startMs) + if (autoPlay) engine.play() else engine.pause() + } + + private fun advanceAndApply(fromUser: Boolean) { + applyResult(queue.advance(fromUser)) + } + + private fun applyResult(result: QueueManager.AdvanceResult) { + when { + result.finished -> { + engine.pause() + _current.value = null + } + result.seekToStart -> engine.seekTo(0L) + result.reload -> rebuffer(autoPlay = true) + result.song != null -> rebuffer(autoPlay = true) + } + } + + private fun recordHistory(track: Song) { + val next = listOf(track) + _history.value.filterNot { it.songKey == track.songKey } + _history.value = next.take(HISTORY_CAP) + } + + companion object { + private const val HISTORY_CAP = 50 + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index fc48ad14..fd68f336 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -22,9 +22,6 @@ import java.util.concurrent.atomic.AtomicReference /** * LibVLC via vlcj. Same job as Android's VlcPlaybackEngine: decode + output. - * - * HTTPS streams (Navidrome/Jellyfin) must use HTTP/1.1 — VLC's h2 stack - * dies with "peer stream 1 error: Protocol error" and poisons later plays. */ class VlcjPlaybackEngine : PlaybackEngine { private val log = yuriLog("Vlcj") @@ -45,6 +42,7 @@ class VlcjPlaybackEngine : PlaybackEngine { private val openedMrl = AtomicReference(null) private val ignoreFinishedUntil = AtomicLong(0) private val recreating = AtomicBoolean(false) + private val released = AtomicBoolean(false) val nativeError: String? override val isPlaying: StateFlow = _isPlaying.asStateFlow() @@ -52,29 +50,42 @@ class VlcjPlaybackEngine : PlaybackEngine { init { val discovered = NativeDiscovery().discover() - val created = runCatching { - MediaPlayerFactory(*FACTORY_ARGS) + var created: Result = Result.failure(IllegalStateException("not attempted")) + repeat(FACTORY_RETRIES) { attempt -> + created = runCatching { MediaPlayerFactory(*FACTORY_ARGS) } + if (created.isSuccess) return@repeat + if (attempt < FACTORY_RETRIES - 1) { + Thread.sleep(FACTORY_RETRY_DELAY_MS) + } } factory = created.getOrNull() player = factory?.mediaPlayers()?.newMediaPlayer() nativeError = when { - factory == null -> - created.exceptionOrNull()?.message - ?: if (!discovered) "LibVLC not found. Install VLC (libvlc) and restart." - else "Could not start LibVLC." + factory == null -> { + val cause = created.exceptionOrNull() + val detail = cause?.message ?: cause?.cause?.message + when { + detail != null -> "LibVLC failed to start: $detail" + !discovered -> "LibVLC not found. Install VLC (libvlc) and restart." + else -> "Could not start LibVLC." + } + } player == null -> "LibVLC opened but could not create a player." else -> null } if (nativeError != null) { log.e { nativeError } } else { - log.i { "libvlc ready http1.1" } + log.i { "libvlc ready" } } bindEvents(player) onVlc { player?.audio()?.setVolume(volumePercent.get()) player?.audio()?.setMute(false) } + Runtime.getRuntime().addShutdownHook(Thread { + runCatching { release() } + }) } override fun load(current: PlaybackMedia, successor: PlaybackMedia?, startPositionMs: Long) { @@ -84,10 +95,20 @@ class VlcjPlaybackEngine : PlaybackEngine { positionMs.set(startPositionMs.coerceAtLeast(0L)) _currentUri.value = current.uri _isPlaying.value = false + openedMrl.set(null) } override fun play() { - val media = loadedMedia.get() + val opened = openedMrl.get() + val loaded = loadedMedia.get() + if (opened != null && loaded != null && toMrl(loaded.uri) == opened) { + onVlc { + player?.audio()?.setMute(false) + player?.controls()?.play() + } + return + } + val media = loaded if (media == null) { onVlc { player?.audio()?.setMute(false) @@ -99,6 +120,7 @@ class VlcjPlaybackEngine : PlaybackEngine { } override fun pause() { + if (openedMrl.get() == null) return onVlc { player?.controls()?.setPause(true) } } @@ -113,6 +135,7 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun seekTo(positionMs: Long) { val ms = positionMs.coerceAtLeast(0L) + log.i { "seek $ms (dur=${durationMs.get()})" } this.positionMs.set(ms) pendingStartMs.set(-1L) onVlc { player?.controls()?.setTime(ms) } @@ -121,6 +144,16 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun getPositionMs(): Long { val pending = pendingStartMs.get() if (pending > 0L && !_isPlaying.value) return pending + // Poll VLC's actual time rather than trusting only the `timeChanged` + // event (which is flaky on some distro VLC builds). Called by the ticker + // every ~250ms, so this keeps position authoritative and seekable. + val p = player + if (p != null && openedMrl.get() != null) { + runCatching { p.status().time() } + .getOrNull() + ?.takeIf { it > 0L } + ?.let { positionMs.set(it) } + } return positionMs.get() } @@ -142,12 +175,14 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun playPreparedNext(): Boolean = false override fun release() { + if (!released.compareAndSet(false, true)) return onVlc { - runCatching { player?.release() } + runCatching { player?.controls()?.stop() } runCatching { factory?.release() } } vlc.shutdown() listeners.clear() + player = null } override fun addListener(listener: PlaybackEngine.Listener) { @@ -165,23 +200,32 @@ class VlcjPlaybackEngine : PlaybackEngine { return@onVlc } val mrl = toMrl(media.uri) + val isHttp = mrl.startsWith("http") log.i { "play ${redactSecrets(mrl)}" } ignoreFinishedUntil.set(System.currentTimeMillis() + 1_200) silentStop() p.audio().setMute(false) p.audio().setVolume(volumePercent.get()) + val pending = if (isHttp) { + pendingStartMs.set(-1L) + -1L + } else { + pendingStartMs.get() + } val extra = buildList { addAll(mediaOptions(media)) - pendingStartMs.get().takeIf { it > 0 }?.let { - add(":start-time=${it / 1000.0}") + if (pending > 0L) { + add(":start-time=${pending / 1000.0}") } - if (mrl.startsWith("http")) { + if (isHttp) { add(":http-reconnect") add(":http-user-agent=YuriPlayer/1.0") } }.toTypedArray() val ok = p.media().play(mrl, *extra) + log.i { "play result ok=$ok ${redactSecrets(mrl)}" } if (!ok) { + log.e { "play failed (ok=false): ${redactSecrets(mrl)}" } listeners.forEach { it.onError("Could not open ${media.title}", recoverable = true) } return@onVlc } @@ -229,8 +273,13 @@ class VlcjPlaybackEngine : PlaybackEngine { if (pending > 0L) { mediaPlayer.controls().setTime(pending) positionMs.set(pending) - } else { - positionMs.set(mediaPlayer.status().time().coerceAtLeast(0L)) + } + // Seed duration from status if lengthChanged hasn't fired yet. + if (durationMs.get() <= 0L) { + runCatching { mediaPlayer.status().length() } + .getOrNull() + ?.takeIf { it > 0L } + ?.let { durationMs.set(it) } } _isPlaying.value = true log.d { "playing pos=${positionMs.get()} dur=${durationMs.get()}" } @@ -239,11 +288,13 @@ class VlcjPlaybackEngine : PlaybackEngine { override fun paused(mediaPlayer: MediaPlayer) { _isPlaying.value = false + log.d { "paused" } listeners.forEach { it.onIsPlayingChanged(false) } } override fun stopped(mediaPlayer: MediaPlayer) { _isPlaying.value = false + log.d { "stopped" } listeners.forEach { it.onIsPlayingChanged(false) } } @@ -277,7 +328,6 @@ class VlcjPlaybackEngine : PlaybackEngine { try { val old = player runCatching { old?.controls()?.stop() } - runCatching { old?.release() } val next = factory?.mediaPlayers()?.newMediaPlayer() player = next bindEvents(next) @@ -303,9 +353,10 @@ class VlcjPlaybackEngine : PlaybackEngine { "--network-caching=4000", "--file-caching=300", "--http-reconnect", - "--no-http-h2", "--http-user-agent=YuriPlayer/1.0", "--aout=any" ) + private const val FACTORY_RETRIES = 3 + private const val FACTORY_RETRY_DELAY_MS = 300L } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt index dd91164d..ad12e102 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/AddToPlaylistDialog.kt @@ -34,20 +34,20 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.dialog.InWindowPanel -import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.core.library.playlistKeys +import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.desktop.DesktopPlaylistStore @Composable fun AddToPlaylistDialog( - tracks: List, + tracks: List, store: DesktopPlaylistStore, - library: List, + library: List, onDismiss: () -> Unit, - onEnsureTracks: (List) -> Unit = {} + onEnsureTracks: (List) -> Unit = {} ) { val playlists by store.playlists.collectAsState() var initiallyIn by remember { mutableStateOf(emptySet()) } @@ -56,7 +56,7 @@ fun AddToPlaylistDialog( var creating by remember { mutableStateOf(false) } var newName by remember { mutableStateOf("") } - LaunchedEffect(tracks.map { it.id }) { + LaunchedEffect(tracks.map { it.songKey }) { val containing = if (tracks.size == 1) { store.playlistsContaining(tracks.first()) } else { @@ -151,44 +151,43 @@ fun AddToPlaylistDialog( .clickable { selected = if (checked) selected - pl.id else selected + pl.id } - .padding(vertical = 6.dp), + .padding(vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { - Checkbox( - checked = checked, - onCheckedChange = { - selected = if (it) selected + pl.id else selected - pl.id - } - ) - CoverArt( - model = pl.artworkUri(library), - size = 40.dp, - corner = 6.dp - ) + CoverArt(model = pl.artworkUri(library), size = 40.dp, corner = 4.dp) Spacer(Modifier.width(12.dp)) Column(Modifier.weight(1f)) { - Text(pl.name, maxLines = 1, overflow = TextOverflow.Ellipsis) + Text(pl.name, fontWeight = FontWeight.Medium, maxLines = 1) Text( - "${pl.tracks(library).size.coerceAtLeast(pl.trackIds.size)} songs", + "${pl.trackIds.size} songs", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) ) } + Checkbox(checked = checked, onCheckedChange = { + selected = if (checked) selected - pl.id else selected + pl.id + }) } } } } - Button( - onClick = { - val toRemove = initiallyIn - selected - onEnsureTracks(tracks) - selected.forEach { store.addTracks(it, tracks) } - toRemove.forEach { store.removeTracks(it, tracks) } - onDismiss() - }, - modifier = Modifier.fillMaxWidth().padding(top = 12.dp) - ) { - Text("Done") + Row(Modifier.fillMaxWidth().padding(top = 8.dp), horizontalArrangement = Arrangement.End) { + TextButton(onClick = onDismiss) { Text("Cancel") } + Button( + enabled = selected != initiallyIn, + onClick = { + val added = selected - initiallyIn + val removed = initiallyIn - selected + for (pl in playlists) { + when (pl.id) { + in added -> store.addTracks(pl.id, tracks) + in removed -> store.removeTracks(pl.id, tracks) + } + } + if (added.isNotEmpty()) onEnsureTracks(tracks) + onDismiss() + } + ) { Text("Save") } } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt index 42112113..15eeffd0 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopExplore.kt @@ -44,9 +44,9 @@ import capital.yuri.yuriplayer.components.model.AlbumPageModel import capital.yuri.yuriplayer.components.model.albums import capital.yuri.yuriplayer.components.model.toRow import capital.yuri.yuriplayer.core.library.LocalLibraryScanner -import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.library.matchesQuery import capital.yuri.yuriplayer.core.library.matchesSearch +import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.desktop.DesktopPlaylist import capital.yuri.yuriplayer.desktop.DesktopSession import kotlinx.coroutines.Dispatchers @@ -62,16 +62,16 @@ private const val ARTIST_HIT_LIMIT = 12 fun DesktopExplore( session: DesktopSession, query: String, - tracks: List, + tracks: List, albums: List, playlists: List, onOpenAlbum: (AlbumPageModel) -> Unit, onOpenPlaylist: (DesktopPlaylist) -> Unit, onOpenArtist: (String) -> Unit, - onPlaySongs: (List, Int) -> Unit, + onPlaySongs: (List, Int) -> Unit, likedIds: Set = emptySet(), onToggleLike: (String) -> Unit = {}, - songMenu: (Track) -> List = { emptyList() } + songMenu: (Song) -> List = { emptyList() } ) { val remotes by session.sources.remotes.collectAsState() val enabled = remember(remotes) { remotes.filter { it.enabled } } @@ -84,12 +84,16 @@ fun DesktopExplore( var selectedKeys by remember { mutableStateOf(emptySet()) } val effective = if (selectedKeys.isEmpty()) allKeys else selectedKeys - var songHits by remember { mutableStateOf>(emptyList()) } + var songHits by remember { mutableStateOf>(emptyList()) } var albumHits by remember { mutableStateOf>(emptyList()) } var artistHits by remember { mutableStateOf>(emptyList()) } var playlistHits by remember { mutableStateOf>(emptyList()) } var busy by remember { mutableStateOf(false) } + // Cache the artist→tracks grouping across keystrokes (it only changes when + // the library changes, not when the query changes). + val tracksByArtist = remember(tracks) { tracks.groupBy { it.displayArtist.lowercase() } } + LaunchedEffect(query, selectedKeys, tracks, albums, playlists, enabled) { val q = query.trim() if (q.isEmpty()) { @@ -111,36 +115,45 @@ fun DesktopExplore( val sid = track.sourceId ?: LocalLibraryScanner.SOURCE_LOCAL sid in effective } - val localMatches = withContext(Dispatchers.Default) { - indexed.filter { it.matchesQuery(q) }.take(SONG_HIT_LIMIT) + + // Compute the full matching set once. The previous code re-ran + // matchesQuery() inside the album loop (O(albums × tracks)), which was + // the source of the lag; now it runs once per track. + val matching = withContext(Dispatchers.Default) { + indexed.filter { it.matchesQuery(q) } } if (query.trim() != q) return@LaunchedEffect - fun applyHits(songs: List) { - val unique = songs.distinctBy { it.id }.take(SONG_HIT_LIMIT) + val matchingAlbumTitles = matching.mapTo(HashSet()) { it.displayAlbum.lowercase() } + + fun applyHits(songs: List) { + val unique = songs.distinctBy { it.songKey }.take(SONG_HIT_LIMIT) songHits = unique + val uniqueIds = unique.mapTo(HashSet()) { it.songKey } + albumHits = albums.filter { album -> album.title.matchesSearch(q) || album.artist.matchesSearch(q) || - album.tracks.any { row -> unique.any { it.id == row.id } } || - indexed.any { - it.displayAlbum.equals(album.title, true) && it.matchesQuery(q) - } + album.tracks.any { row -> row.id in uniqueIds } || + album.title.lowercase() in matchingAlbumTitles }.take(ALBUM_HIT_LIMIT).ifEmpty { unique.albums().take(ALBUM_HIT_LIMIT) } + artistHits = indexed .map { it.displayArtist } .distinct() .filter { it.matchesSearch(q) } .take(ARTIST_HIT_LIMIT) - .map { name -> - val ofArtist = tracks.filter { it.displayArtist.equals(name, true) } - ArtistHit( + .mapNotNull { name -> + val ofArtist = tracksByArtist[name.lowercase()].orEmpty() + if (ofArtist.isEmpty()) null + else ArtistHit( name = name, - artworkUri = ofArtist.firstNotNullOfOrNull { it.artworkUri }, + artworkUri = ofArtist.firstNotNullOfOrNull { it.albumArtUri }, trackCount = ofArtist.size, albumCount = ofArtist.map { it.displayAlbum }.distinct().size ) } + playlistHits = if (includeLocal) { playlists.filter { it.name.matchesSearch(q) }.take(12) } else { @@ -149,7 +162,7 @@ fun DesktopExplore( busy = false } - applyHits(localMatches) + applyHits(matching) val remoteMatches = if (remoteIds == null || remoteIds.isNotEmpty()) { runCatching { @@ -159,7 +172,7 @@ fun DesktopExplore( emptyList() } if (query.trim() != q) return@LaunchedEffect - applyHits(localMatches + remoteMatches) + applyHits(matching + remoteMatches) } Column(Modifier.fillMaxSize()) { @@ -348,12 +361,12 @@ fun DesktopExplore( } if (songHits.isNotEmpty()) { item { ExploreSection("Songs") } - itemsIndexed(songHits, key = { _, t -> t.id }) { index, track -> + itemsIndexed(songHits, key = { _, t -> t.songKey }) { index, track -> TrackRow( track = track.toRow(), onClick = { onPlaySongs(songHits, index) }, - liked = track.id in likedIds, - onToggleLike = { onToggleLike(track.id) }, + liked = track.songKey in likedIds, + onToggleLike = { onToggleLike(track.songKey) }, contextItems = songMenu(track) ) } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt index 69f15b78..dd0ef1af 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/MetadataEditDialog.kt @@ -23,21 +23,21 @@ import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.art.CoverArt import capital.yuri.yuriplayer.components.dialog.InWindowPanel import capital.yuri.yuriplayer.components.model.AlbumPageModel -import capital.yuri.yuriplayer.core.library.Track +import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.desktop.TagWriter import java.io.File @Composable fun EditSongDialog( - track: Track, + track: Song, onDismiss: () -> Unit, - onSaved: (Track) -> Unit + onSaved: (Song) -> Unit ) { val file = track.path?.let { File(it) } val writable = file != null && file.isFile && file.canWrite() - var title by remember(track.id) { mutableStateOf(track.title.orEmpty()) } - var artist by remember(track.id) { mutableStateOf(track.artist.orEmpty()) } - var genre by remember(track.id) { mutableStateOf(track.genre.orEmpty()) } + var title by remember(track.songKey) { mutableStateOf(track.title.orEmpty()) } + var artist by remember(track.songKey) { mutableStateOf(track.artist.orEmpty()) } + var genre by remember(track.songKey) { mutableStateOf(track.genre.orEmpty()) } var coverFile by remember { mutableStateOf(null) } var cropSource by remember { mutableStateOf(null) } var error by remember { mutableStateOf(null) } @@ -67,7 +67,7 @@ fun EditSongDialog( ) } CoverArt( - model = coverFile?.toURI()?.toString() ?: track.artworkUri, + model = coverFile?.toURI()?.toString() ?: track.albumArtUri, size = 96.dp, corner = 8.dp, modifier = Modifier.padding(vertical = 12.dp) @@ -108,7 +108,7 @@ fun EditSongDialog( title = title.ifBlank { track.title }, artist = artist.ifBlank { track.artist }, genre = genre.ifBlank { track.genre }, - artworkUri = coverFile?.toURI()?.toString() ?: track.artworkUri + albumArtUri = coverFile?.toURI()?.toString() ?: track.albumArtUri ) ) onDismiss() @@ -126,9 +126,9 @@ fun EditSongDialog( @Composable fun EditAlbumDialog( album: AlbumPageModel, - tracks: List, + tracks: List, onDismiss: () -> Unit, - onSaved: (List) -> Unit + onSaved: (List) -> Unit ) { val writable = tracks.mapNotNull { it.path?.let { p -> File(p) } }.any { it.isFile && it.canWrite() } var title by remember(album.id) { mutableStateOf(album.title) } @@ -183,7 +183,7 @@ fun EditAlbumDialog( onClick = { val coverBytes = coverFile?.readBytes() val yearInt = year.toIntOrNull() - val updated = ArrayList() + val updated = ArrayList() var failed = 0 tracks.forEach { t -> val f = t.path?.let { File(it) } @@ -208,7 +208,7 @@ fun EditAlbumDialog( albumArtist = artist.ifBlank { t.albumArtist }, year = yearInt ?: t.year, genre = genre.ifBlank { t.genre }, - artworkUri = coverFile?.toURI()?.toString() ?: t.artworkUri + albumArtUri = coverFile?.toURI()?.toString() ?: t.albumArtUri ) } if (failed == tracks.size) { diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt index 00888e79..490d47fd 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/PlaylistPage.kt @@ -23,6 +23,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.Checkbox import androidx.compose.material3.FilledIconButton @@ -52,8 +53,9 @@ import capital.yuri.yuriplayer.components.list.LikeHeart import capital.yuri.yuriplayer.components.list.TrackRow import capital.yuri.yuriplayer.components.menu.MenuEntry import capital.yuri.yuriplayer.components.model.toRow -import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.library.matchesQuery +import capital.yuri.yuriplayer.core.library.playlistKeys +import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.desktop.DesktopCover import capital.yuri.yuriplayer.desktop.DesktopPlaylist import capital.yuri.yuriplayer.desktop.DesktopPlaylistStore @@ -62,18 +64,19 @@ import java.io.File @Composable fun PlaylistPage( playlist: DesktopPlaylist, - tracks: List, - library: List, + tracks: List, + library: List, currentId: String?, + playing: Boolean = false, store: DesktopPlaylistStore, onBack: () -> Unit, - onPlay: (List, Int) -> Unit, - onEditTrack: (Track) -> Unit, + onPlay: (List, Int) -> Unit, + onEditTrack: (Song) -> Unit, likedIds: Set = emptySet(), onToggleLike: (String) -> Unit = {}, playlistLiked: Boolean = false, onTogglePlaylistLike: () -> Unit = {}, - songMenu: (Track) -> List = { emptyList() } + songMenu: (Song) -> List = { emptyList() } ) { var name by remember(playlist.id, playlist.updatedAtMs) { mutableStateOf(playlist.name) } var description by remember(playlist.id, playlist.updatedAtMs) { @@ -167,7 +170,10 @@ fun PlaylistPage( FilledIconButton( onClick = { if (tracks.isNotEmpty()) onPlay(tracks, 0) } ) { - Icon(Icons.Default.PlayArrow, contentDescription = "Play") + Icon( + if (playing) Icons.Default.Pause else Icons.Default.PlayArrow, + contentDescription = if (playing) "Pause" else "Play" + ) } LikeHeart(liked = playlistLiked, onToggle = onTogglePlaylistLike) TextButton(onClick = { showAdd = true }) { Text("Add songs") } @@ -178,15 +184,15 @@ fun PlaylistPage( val density = LocalDensity.current val rowPx = with(density) { 64.dp.toPx() } LazyColumn(Modifier.weight(1f)) { - itemsIndexed(tracks, key = { i, t -> "${t.id}#$i" }) { index, track -> + itemsIndexed(tracks, key = { i, t -> "${t.songKey}#$i" }) { index, track -> TrackRow( - track = track.toRow(highlighted = track.id == currentId), + track = track.toRow(highlighted = track.songKey == currentId), onClick = { onPlay(tracks, index) }, onLongClick = { onEditTrack(track) }, showCover = true, showAlbum = true, - liked = track.id in likedIds, - onToggleLike = { onToggleLike(track.id) }, + liked = track.songKey in likedIds, + onToggleLike = { onToggleLike(track.songKey) }, contextItems = songMenu(track), modifier = Modifier.pointerInput(tracks, playlist.id) { var from: Int? = null @@ -246,10 +252,10 @@ fun PlaylistPage( @Composable private fun AddSongsDialog( - library: List, + library: List, existing: Set, onDismiss: () -> Unit, - onAdd: (List) -> Unit + onAdd: (List) -> Unit ) { var query by remember { mutableStateOf("") } val hits = remember(query, library) { @@ -268,7 +274,7 @@ private fun AddSongsDialog( singleLine = true ) LazyColumn(Modifier.weight(1f).padding(top = 8.dp)) { - itemsIndexed(hits, key = { _, t -> t.id }) { _, track -> + itemsIndexed(hits, key = { _, t -> t.songKey }) { _, track -> val inPl = track.playlistKeys().any { it in existing } TrackRow( track = track.toRow(), diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt index 5bcfdcc9..7151d899 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/SourcesPickerDialog.kt @@ -28,13 +28,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import capital.yuri.yuriplayer.components.dialog.InWindowPanel -import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.source.RemoteAccount import capital.yuri.yuriplayer.core.source.SourceKind import capital.yuri.yuriplayer.core.library.LocalLibraryScanner +import capital.yuri.yuriplayer.data.Song data class SourceChoice( - val track: Track, + val track: Song, val label: String, val typeLabel: String, val writable: Boolean, @@ -42,21 +42,21 @@ data class SourceChoice( ) fun sourceChoices( - sources: List, + sources: List, remotes: List, preferredId: String? ): List { val byId = remotes.associateBy { it.id } return sources.map { t -> val remote = t.sourceId?.let { byId[it] } - val local = t.sourceId == LocalLibraryScanner.SOURCE_LOCAL || t.uri.startsWith("file:") + val local = t.sourceId == LocalLibraryScanner.SOURCE_LOCAL || t.contentUri.startsWith("file:") val writable = local val typeLabel = when { local -> "Local" remote?.kind == SourceKind.JELLYFIN -> "Jellyfin" remote?.kind == SourceKind.SUBSONIC -> "Navidrome" - t.id.startsWith("jellyfin:") -> "Jellyfin" - t.id.startsWith("subsonic:") -> "Subsonic" + t.songKey.startsWith("jellyfin:") -> "Jellyfin" + t.songKey.startsWith("subsonic:") || t.songKey.startsWith("navidrome:") -> "Subsonic" else -> "Source" } val label = when { @@ -69,7 +69,7 @@ fun sourceChoices( label = label, typeLabel = typeLabel, writable = writable, - preferred = preferredId != null && (t.id == preferredId || t.sourceId == preferredId) + preferred = preferredId != null && (t.songKey == preferredId || t.sourceId == preferredId) ) } } @@ -79,7 +79,7 @@ fun SourcesPickerDialog( title: String, choices: List, onDismiss: () -> Unit, - onPick: (Track) -> Unit + onPick: (Song) -> Unit ) { InWindowPanel(onDismiss = onDismiss, modifier = Modifier.width(420.dp).height(480.dp)) { Column(Modifier.fillMaxSize().padding(20.dp)) { @@ -98,7 +98,7 @@ fun SourcesPickerDialog( ) HorizontalDivider() LazyColumn(Modifier.weight(1f).padding(top = 8.dp)) { - items(choices, key = { it.track.id }) { c -> + items(choices, key = { it.track.songKey }) { c -> Row( Modifier .fillMaxWidth() diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt index e572d51e..f7faea24 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/YuriDesktopApp.kt @@ -66,10 +66,13 @@ import capital.yuri.yuriplayer.components.theme.PlayerChromeTheme import capital.yuri.yuriplayer.components.theme.YuriTheme import capital.yuri.yuriplayer.components.theme.playerColorsFromPixels import capital.yuri.yuriplayer.core.artist.ArtistProfile -import capital.yuri.yuriplayer.core.library.Track import capital.yuri.yuriplayer.core.library.albumPageIdentity +import capital.yuri.yuriplayer.core.library.catalogKey import capital.yuri.yuriplayer.core.library.pickPreferred import capital.yuri.yuriplayer.core.library.sourceRank +import capital.yuri.yuriplayer.core.player.ColdSource +import capital.yuri.yuriplayer.core.player.ColdSourceType +import capital.yuri.yuriplayer.data.Song import capital.yuri.yuriplayer.desktop.DesktopCollection import capital.yuri.yuriplayer.desktop.DesktopSession import kotlinx.coroutines.launch @@ -80,6 +83,7 @@ private sealed class Route { data class Album(val album: AlbumPageModel) : Route() data class Playlist(val id: String) : Route() data class Artist(val name: String) : Route() + data object MyStuff : Route() } @Composable @@ -98,6 +102,7 @@ fun YuriDesktopApp(session: DesktopSession) { val position by session.positionMs.collectAsState() val duration by session.durationMs.collectAsState() val engineMessage by session.engineMessage.collectAsState() + val online by session.network.isOnline.collectAsState() val shuffle by session.player.shuffle.collectAsState() val repeat by session.player.repeat.collectAsState() val volume by session.player.volume.collectAsState() @@ -106,6 +111,7 @@ fun YuriDesktopApp(session: DesktopSession) { val saved by session.collection.saved.collectAsState() val hotQueue by session.player.hotQueue.collectAsState() val coldQueue by session.player.coldQueue.collectAsState() + val coldSource by session.player.coldSource.collectAsState() val playlists by session.playlists.playlists.collectAsState() val remotes by session.sources.remotes.collectAsState() val leftFrac by session.layout.leftFraction.collectAsState() @@ -119,10 +125,10 @@ fun YuriDesktopApp(session: DesktopSession) { var showSettings by remember { mutableStateOf(false) } var showNewPlaylist by remember { mutableStateOf(false) } var showSidebar by remember { mutableStateOf(true) } - var editSong by remember { mutableStateOf(null) } + var editSong by remember { mutableStateOf(null) } var editAlbum by remember { mutableStateOf(null) } - var addToPlaylist by remember { mutableStateOf?>(null) } - var sourcesFor by remember { mutableStateOf?>(null) } + var addToPlaylist by remember { mutableStateOf?>(null) } + var sourcesFor by remember { mutableStateOf?>(null) } val route = stack.last() val nav = if (route is Route.Search) DesktopNav.Search else DesktopNav.Home val scope = rememberCoroutineScope() @@ -148,7 +154,7 @@ fun YuriDesktopApp(session: DesktopSession) { fun openAlbum(album: AlbumPageModel) = push(Route.Album(album)) fun openArtist(name: String) = push(Route.Artist(name)) - fun playlistAddAlternate(songs: List): ContextMenuScope.() -> Unit = { + fun playlistAddAlternate(songs: List): ContextMenuScope.() -> Unit = { val candidates = playlists .filter { pl -> songs.any { t -> pl.orderedEntries().none { e -> e.matches(t) } } @@ -167,7 +173,7 @@ fun YuriDesktopApp(session: DesktopSession) { } fun songMenu( - track: Track, + track: Song, onAlbumPage: Boolean = false, playlistId: String? = null ): List = buildContextMenu { @@ -177,16 +183,16 @@ fun YuriDesktopApp(session: DesktopSession) { item("Add to queue") { session.player.enqueue(track) } item("Sources") { val ids = HashSet() - ids += track.id + ids += track.songKey ids += track.catalogKey() val row = albums.asSequence() - .mapNotNull { a -> a.tracks.firstOrNull { track.id in it.sourceIds || it.id == track.id } } + .mapNotNull { a -> a.tracks.firstOrNull { track.songKey in it.sourceIds || it.id == track.songKey } } .firstOrNull() if (row != null) ids += row.sourceIds val want = ids sourcesFor = tracks.asSequence() - .filter { it.id in want || it.catalogKey() in want } - .distinctBy { it.id } + .filter { it.songKey in want || it.catalogKey() in want } + .distinctBy { it.songKey } .take(16) .toList() .ifEmpty { listOf(track) } @@ -195,7 +201,7 @@ fun YuriDesktopApp(session: DesktopSession) { if (!onAlbumPage) { item("Album") { albums.firstOrNull { a -> - a.tracks.any { track.id in it.sourceIds || it.id == track.id } + a.tracks.any { track.songKey in it.sourceIds || it.id == track.songKey } }?.let(::openAlbum) } } @@ -209,21 +215,21 @@ fun YuriDesktopApp(session: DesktopSession) { } } - fun menuForQueueRow(row: TrackRowModel, pool: List): List { - val track = pool.firstOrNull { it.id == row.id } ?: Track( - id = row.id, - uri = "", + fun menuForQueueRow(row: TrackRowModel, pool: List): List { + val track = pool.firstOrNull { it.songKey == row.id } ?: Song( + id = 0L, + contentUri = "", title = row.title, artist = row.artist, album = row.album, durationMs = row.durationMs, - artworkUri = row.artworkUri + albumArtUri = row.artworkUri ) return songMenu(track) } - fun pinTracks(item: LibraryRailItem): List = when { - item.id == "liked" -> tracks.filter { it.id in liked } + fun pinTracks(item: LibraryRailItem): List = when { + item.id == "liked" -> tracks.filter { it.songKey in liked } item.id.startsWith("playlist:") -> { val id = item.id.removePrefix("playlist:") playlists.firstOrNull { it.id == id }?.tracks(tracks).orEmpty() @@ -235,22 +241,22 @@ fun YuriDesktopApp(session: DesktopSession) { else -> { val album = albums.firstOrNull { it.id == item.id } album?.tracks?.mapNotNull { row -> - val group = tracks.filter { it.id in row.sourceIds } + val group = tracks.filter { it.songKey in row.sourceIds } pickPreferred(group, session.sourcePrefs.get(albumPageIdentity(group.firstOrNull() ?: return@mapNotNull null))) }.orEmpty() - .ifEmpty { tracks.filter { it.id == item.id }.takeIf { it.isNotEmpty() } ?: emptyList() } + .ifEmpty { tracks.filter { it.songKey == item.id }.takeIf { it.isNotEmpty() } ?: emptyList() } } } val recency = remember(history) { - history.mapIndexed { i, t -> t.id to i }.toMap() + history.mapIndexed { i, t -> t.songKey to i }.toMap() } fun albumRecency(album: AlbumPageModel): Int = album.tracks.minOfOrNull { recency[it.id] ?: Int.MAX_VALUE } ?: Int.MAX_VALUE val libraryItems = remember(albums, tracks, libraryFilter, pins, saved, history, liked, playlists) { val likedAlbums = albums.filter { album -> album.tracks.any { it.id in liked } } - val likedArtists = tracks.filter { it.id in liked }.map { it.displayArtist }.distinct() + val likedArtists = tracks.filter { it.songKey in liked }.map { it.displayArtist }.distinct() val pinItems = pins.map { pin -> when (pin.kind) { DesktopCollection.Kind.ALBUM -> { @@ -263,14 +269,14 @@ fun YuriDesktopApp(session: DesktopSession) { } DesktopCollection.Kind.ARTIST -> LibraryRailItem( pin.id, pin.title, pin.subtitle, - tracks.firstOrNull { it.displayArtist.equals(pin.title, true) }?.artworkUri, + tracks.firstOrNull { it.displayArtist.equals(pin.title, true) }?.albumArtUri, circular = true, pinned = true ) DesktopCollection.Kind.SONG -> { - val t = tracks.firstOrNull { it.id == pin.id } + val t = tracks.firstOrNull { it.songKey == pin.id } LibraryRailItem( pin.id, pin.title, pin.subtitle, - t?.artworkUri, pinned = true + t?.albumArtUri, pinned = true ) } DesktopCollection.Kind.PLAYLIST -> { @@ -285,10 +291,11 @@ fun YuriDesktopApp(session: DesktopSession) { val likedRow = if (liked.isNotEmpty()) { listOf( LibraryRailItem( - "liked", - "Liked Songs", - "${liked.size} songs", - tracks.firstOrNull { it.id in liked }?.artworkUri + id = "liked", + title = "My Stuff", + subtitle = "${liked.size} songs", + artworkUri = null, + heart = true ) ) } else emptyList() @@ -304,7 +311,7 @@ fun YuriDesktopApp(session: DesktopSession) { "artist:$name", name, "Artist", - tracks.firstOrNull { it.displayArtist.equals(name, true) }?.artworkUri, + tracks.firstOrNull { it.displayArtist.equals(name, true) }?.albumArtUri, circular = true ) } @@ -336,10 +343,9 @@ fun YuriDesktopApp(session: DesktopSession) { pins.filter { it.kind == DesktopCollection.Kind.ALBUM }.map { it.id } + likedAlbums.map { it.id } ).toSet() - val stuffPlaylistIds = playlists.map { it.id }.toSet() buildList { history.forEach { t -> - val album = albums.firstOrNull { a -> a.tracks.any { it.id == t.id } } + val album = albums.firstOrNull { a -> a.tracks.any { it.id == t.songKey } } if (album != null && (album.id in stuffAlbumIds || album.tracks.any { it.id in liked })) { add(LibraryRailItem(album.id, album.title, "Album · ${album.artist}", album.artworkUri)) } @@ -373,10 +379,7 @@ fun YuriDesktopApp(session: DesktopSession) { onLibraryItem = { item -> when { item.id == "new-playlist" -> showNewPlaylist = true - item.id == "liked" -> { - val likedTracks = tracks.filter { it.id in liked } - if (likedTracks.isNotEmpty()) session.player.play(likedTracks, 0) - } + item.id == "liked" -> push(Route.MyStuff) item.id.startsWith("playlist:") -> { push(Route.Playlist(item.id.removePrefix("playlist:"))) } @@ -426,46 +429,50 @@ fun YuriDesktopApp(session: DesktopSession) { showSidebar = showSidebar, leftFraction = leftFrac, rightFraction = rightFrac, + offline = !online, onLeftFraction = { f, persist -> session.layout.setLeft(f, persist) }, onRightFraction = { f, persist -> session.layout.setRight(f, persist) }, bottomBar = { - BottomPlayerBar( - track = current?.toCover(), - playing = playing, - positionMs = position, - durationMs = duration, - onPrev = session.player::previous, - onToggle = session.player::togglePlay, - onNext = session.player::next, - onSeek = { ms -> - session.player.seekTo(ms) - session.persistPlayback() - }, - accent = playerColors?.accent ?: MaterialTheme.colorScheme.primary, - shuffle = shuffle, - repeat = repeat, - onToggleShuffle = session.player::toggleShuffle, - onCycleRepeat = session.player::cycleRepeat, - volume = volume, - onVolume = session.player::setVolume, - liked = current?.id in liked, - onToggleLike = { - val t = current ?: return@BottomPlayerBar - session.collection.toggleLike(t.id) - }, - queueVisible = showSidebar, - onToggleQueue = { showSidebar = !showSidebar }, - songMenu = current?.let { songMenu(it) }.orEmpty() - ) + PlayerChromeTheme(playerColors, useArtBackground = true) { + BottomPlayerBar( + track = current?.toCover(), + playing = playing, + positionMs = position, + durationMs = duration, + onPrev = session.player::previous, + onToggle = session.player::togglePlay, + onNext = session.player::next, + onSeek = { ms -> + session.player.seekTo(ms) + session.persistPlayback() + }, + accent = playerColors?.accent ?: MaterialTheme.colorScheme.primary, + shuffle = shuffle, + repeat = repeat, + onToggleShuffle = session.player::toggleShuffle, + onCycleRepeat = session.player::cycleRepeat, + volume = volume, + onVolume = session.player::setVolume, + liked = current?.songKey in liked, + onToggleLike = { + val t = current ?: return@BottomPlayerBar + session.collection.toggleLike(t.songKey) + }, + queueVisible = showSidebar, + onToggleQueue = { showSidebar = !showSidebar }, + songMenu = current?.let { songMenu(it) }.orEmpty() + ) + } }, sidebar = { PlayerChromeTheme(playerColors, useArtBackground = true) { - val curId = current?.id - val hotIdx = hotQueue.indexOfFirst { it.id == curId } - val coldIdx = coldQueue.indexOfFirst { it.id == curId } + val curId = current?.songKey + val hotIdx = hotQueue.indexOfFirst { it.songKey == curId } + val coldIdx = coldQueue.indexOfFirst { it.songKey == curId } val hotUp = if (hotIdx >= 0) hotQueue.drop(hotIdx + 1) else hotQueue val coldUp = if (coldIdx >= 0) coldQueue.drop(coldIdx + 1) else coldQueue - val coldLabel = coldUp.firstOrNull()?.displayAlbum?.takeIf { it.isNotBlank() && it != "Unknown Album" } + val coldLabel = coldSource?.title?.takeIf { it.isNotBlank() } + ?: coldUp.firstOrNull()?.displayAlbum?.takeIf { it.isNotBlank() && it != "Unknown Album" } ?: "Up next" NowPlayingSidebar( track = current?.toCover(), @@ -476,7 +483,7 @@ fun YuriDesktopApp(session: DesktopSession) { onPlayHot = { i -> hotUp.getOrNull(i)?.let { session.player.playTrack(it) } }, onPlayCold = { i -> coldUp.getOrNull(i)?.let { session.player.playTrack(it) } }, onHistoryTrack = { row -> - val t = history.firstOrNull { it.id == row.id } ?: return@NowPlayingSidebar + val t = history.firstOrNull { it.songKey == row.id } ?: return@NowPlayingSidebar session.player.playTrack(t, tracks.ifEmpty { listOf(t) }) }, onClearHot = session.player::clearHot, @@ -531,11 +538,22 @@ fun YuriDesktopApp(session: DesktopSession) { onToggleLike = session.collection::toggleLike, songMenu = { songMenu(it) } ) + Route.MyStuff -> MyStuffPage( + tracks = tracks, + albums = albums, + playlists = playlists, + pins = pins, + liked = liked, + onOpenAlbum = ::openAlbum, + onOpenPlaylist = { push(Route.Playlist(it.id)) }, + onOpenArtist = ::openArtist, + onPlayTracks = { list, i -> session.player.play(list, i) } + ) is Route.Album -> { val live = albums.firstOrNull { it.id == r.album.id } ?: r.album - fun resolveRow(row: capital.yuri.yuriplayer.components.model.TrackRowModel): Track? { - val group = tracks.filter { it.id in row.sourceIds.ifEmpty { listOf(row.id) } } - if (group.isEmpty()) return tracks.firstOrNull { it.id == row.id } + fun resolveRow(row: TrackRowModel): Song? { + val group = tracks.filter { it.songKey in row.sourceIds.ifEmpty { listOf(row.id) } } + if (group.isEmpty()) return tracks.firstOrNull { it.songKey == row.id } val identity = albumPageIdentity(group.first()) return pickPreferred(group, session.sourcePrefs.get(identity)) } @@ -543,16 +561,16 @@ fun YuriDesktopApp(session: DesktopSession) { AlbumPage( album = live.copy( tracks = live.tracks.map { - it.copy(highlighted = current?.id == it.id || current?.id in it.sourceIds) + it.copy(highlighted = current?.songKey == it.id || current?.songKey in it.sourceIds) } ), - playing = playing && albumTracks.any { it.id == current?.id }, + playing = playing && session.player.isPlayingFromAlbum(live.id), onBack = ::goBack, onPlay = { - if (albumTracks.isNotEmpty()) session.player.play(albumTracks, 0) + if (albumTracks.isNotEmpty()) session.player.play(albumTracks, 0, ColdSource(ColdSourceType.ALBUM, live.id, live.title)) }, onTrack = { i -> - if (i in albumTracks.indices) session.player.play(albumTracks, i) + if (i in albumTracks.indices) session.player.play(albumTracks, i, ColdSource(ColdSourceType.ALBUM, live.id, live.title)) }, onEdit = { editAlbum = live }, onEditTrack = { i -> @@ -573,13 +591,13 @@ fun YuriDesktopApp(session: DesktopSession) { likedTrackIds = liked, onToggleTrackLike = session.collection::toggleLike, songMenu = { row -> - tracks.firstOrNull { it.id == row.id }?.let { songMenu(it, onAlbumPage = true) }.orEmpty() + tracks.firstOrNull { it.songKey == row.id }?.let { songMenu(it, onAlbumPage = true) }.orEmpty() }, onSources = { row -> val ids = row.sourceIds.ifEmpty { listOf(row.id) }.toHashSet() - sourcesFor = tracks.filter { it.id in ids } + sourcesFor = tracks.filter { it.songKey in ids } .ifEmpty { tracks.filter { it.catalogKey() == row.id } } - .ifEmpty { listOfNotNull(tracks.firstOrNull { it.id == row.id }) } + .ifEmpty { listOfNotNull(tracks.firstOrNull { it.songKey == row.id }) } } ) } @@ -611,21 +629,21 @@ fun YuriDesktopApp(session: DesktopSession) { } ArtistPage( artist = model, - playing = playing && artistTracks.any { it.id == current?.id }, + playing = playing && session.player.isPlayingFromArtist(r.name), onPlay = { - if (artistTracks.isNotEmpty()) session.player.play(artistTracks, 0) + if (artistTracks.isNotEmpty()) session.player.play(artistTracks, 0, ColdSource(ColdSourceType.ARTIST, r.name, r.name)) }, onShuffle = { if (artistTracks.isNotEmpty()) { if (!shuffle) session.player.toggleShuffle() - session.player.play(artistTracks, 0) + session.player.play(artistTracks, 0, ColdSource(ColdSourceType.ARTIST, r.name, r.name)) } }, onTrack = { i -> val popularIds = model.popular.map { it.id } - val list = popularIds.mapNotNull { id -> artistTracks.firstOrNull { it.id == id } } + val list = popularIds.mapNotNull { id -> artistTracks.firstOrNull { it.songKey == id } } .ifEmpty { artistTracks } - if (i in list.indices) session.player.play(list, i) + if (i in list.indices) session.player.play(list, i, ColdSource(ColdSourceType.ARTIST, r.name, r.name)) }, onOpenAlbum = ::openAlbum, onChangeHeader = { @@ -649,7 +667,7 @@ fun YuriDesktopApp(session: DesktopSession) { likedTrackIds = liked, onToggleTrackLike = session.collection::toggleLike, songMenu = { row -> - tracks.firstOrNull { it.id == row.id }?.let { songMenu(it) }.orEmpty() + tracks.firstOrNull { it.songKey == row.id }?.let { songMenu(it) }.orEmpty() } ) if (pickBanner) { @@ -657,18 +675,20 @@ fun YuriDesktopApp(session: DesktopSession) { artistName = r.name, client = session.artists, onDismiss = { pickBanner = false }, - onPicked = { c -> - pickBanner = false + onPicked = { candidate -> scope.launch { - cropBanner = DesktopFiles.downloadImage(c.url) + runCatching { + profile = session.artists.applyBannerUrl(r.name, candidate.url) + } + pickBanner = false } } ) } - cropBanner?.let { src -> + if (cropBanner != null) { ImageCropDialog( - source = src, - title = "Crop header", + source = cropBanner!!, + title = "Crop banner", aspect = 3f, onCancel = { cropBanner = null }, onCropped = { cropped -> @@ -685,17 +705,18 @@ fun YuriDesktopApp(session: DesktopSession) { if (pl == null) { Text("Playlist gone.", modifier = Modifier.padding(24.dp)) } else { - LaunchedEffect(pl.id, pl.snapshots.map { it.id }) { + LaunchedEffect(pl.id, pl.snapshots.map { it.songKey }) { session.ensureTracks(pl.snapshots) } PlaylistPage( playlist = pl, tracks = pl.tracks(tracks), library = tracks, - currentId = current?.id, + currentId = current?.songKey, + playing = playing && session.player.isPlayingFromPlaylist(pl.id), store = session.playlists, onBack = ::goBack, - onPlay = { list, i -> session.player.play(list, i) }, + onPlay = { list, i -> session.player.play(list, i, ColdSource(ColdSourceType.PLAYLIST, pl.id, pl.name)) }, onEditTrack = { editSong = it }, likedIds = liked, onToggleLike = session.collection::toggleLike, @@ -740,7 +761,7 @@ fun YuriDesktopApp(session: DesktopSession) { ) } editAlbum?.let { album -> - val albumTracks = tracks.filter { t -> album.tracks.any { it.id == t.id } } + val albumTracks = tracks.filter { t -> album.tracks.any { it.id == t.songKey } } EditAlbumDialog( album = album, tracks = albumTracks, @@ -759,13 +780,13 @@ fun YuriDesktopApp(session: DesktopSession) { } sourcesFor?.let { group -> val preferredId = group.firstOrNull()?.let { session.sourcePrefs.get(albumPageIdentity(it)) } - ?: group.minByOrNull { it.sourceRank() }?.id + ?: group.minByOrNull { it.sourceRank() }?.songKey SourcesPickerDialog( title = group.first().displayTitle, choices = sourceChoices(group, remotes, preferredId), onDismiss = { sourcesFor = null }, onPick = { picked -> - session.sourcePrefs.set(albumPageIdentity(picked), picked.id) + session.sourcePrefs.set(albumPageIdentity(picked), picked.songKey) prefRev++ sourcesFor = null } @@ -774,30 +795,178 @@ fun YuriDesktopApp(session: DesktopSession) { } } +@Composable +private fun MyStuffPage( + tracks: List, + albums: List, + playlists: List, + pins: List, + liked: Set, + onOpenAlbum: (AlbumPageModel) -> Unit, + onOpenPlaylist: (capital.yuri.yuriplayer.desktop.DesktopPlaylist) -> Unit, + onOpenArtist: (String) -> Unit, + onPlayTracks: (List, Int) -> Unit +) { + val likedTracks = remember(liked, tracks) { tracks.filter { it.songKey in liked } } + val pinnedAlbumIds = remember(pins) { + pins.filter { it.kind == DesktopCollection.Kind.ALBUM }.map { it.id }.toSet() + } + val pinnedAlbumTitles = remember(pins) { + pins.filter { it.kind == DesktopCollection.Kind.ALBUM }.map { it.title.lowercase() }.toSet() + } + val pinnedAlbums = remember(albums, pinnedAlbumIds, pinnedAlbumTitles) { + albums.filter { it.id in pinnedAlbumIds || it.title.lowercase() in pinnedAlbumTitles } + } + val pinnedArtists = remember(pins, tracks) { + pins.filter { it.kind == DesktopCollection.Kind.ARTIST }.map { p -> + p.title to tracks.firstOrNull { + it.displayArtist.equals(p.title, true) || it.albumArtist.equals(p.title, true) + }?.albumArtUri + } + } + val pinnedPlaylists = remember(pins, playlists) { + val ids = pins.filter { it.kind == DesktopCollection.Kind.PLAYLIST }.map { it.id }.toSet() + playlists.filter { it.id in ids } + } + + Column( + Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(horizontal = 24.dp, vertical = 16.dp) + ) { + Text( + "My Stuff", + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onBackground + ) + Spacer(Modifier.height(16.dp)) + + if (likedTracks.isNotEmpty()) { + SectionHeader("Liked songs") + Column { + likedTracks.forEach { track -> + Row( + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(10.dp)) + .clickable { onPlayTracks(likedTracks, likedTracks.indexOf(track)) } + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CoverArt(model = track.albumArtUri, size = 44.dp, corner = 6.dp) + Spacer(Modifier.width(12.dp)) + Column(Modifier.weight(1f)) { + Text( + track.displayTitle, + fontWeight = FontWeight.Medium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + track.displayArtist, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } + } + } + + if (pinnedAlbums.isNotEmpty()) { + Spacer(Modifier.height(20.dp)) + SectionHeader("Albums") + LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + items(pinnedAlbums, key = { it.id }) { album -> + AlbumCard(album = album, onClick = { onOpenAlbum(album) }) + } + } + } + + if (pinnedArtists.isNotEmpty()) { + Spacer(Modifier.height(20.dp)) + SectionHeader("Artists") + LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + items(pinnedArtists, key = { it.first }) { (name, cover) -> + Column( + Modifier.width(140.dp).clickable { onOpenArtist(name) }, + horizontalAlignment = Alignment.CenterHorizontally + ) { + CoverArt( + model = cover, + size = 128.dp, + corner = 64.dp, + contentScale = androidx.compose.ui.layout.ContentScale.Crop + ) + Text( + name, + modifier = Modifier.padding(top = 8.dp), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Medium + ) + } + } + } + } + + if (pinnedPlaylists.isNotEmpty()) { + Spacer(Modifier.height(20.dp)) + SectionHeader("Playlists") + LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + items(pinnedPlaylists, key = { it.id }) { pl -> + AlbumCard( + album = AlbumPageModel( + id = pl.id, + title = pl.name, + artist = pl.description ?: "Playlist", + artworkUri = pl.artworkUri(tracks), + tracks = emptyList() + ), + onClick = { onOpenPlaylist(pl) } + ) + } + } + } + + if (likedTracks.isEmpty() && pinnedAlbums.isEmpty() && pinnedArtists.isEmpty() && pinnedPlaylists.isEmpty()) { + Spacer(Modifier.height(12.dp)) + Text( + "Nothing here yet — like songs or pin albums, artists, and playlists.", + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f) + ) + } + } +} + @Composable private fun HomeFeed( albums: List, playlists: List, - tracks: List, - recents: List, + tracks: List, + recents: List, pins: List, liked: Set, status: String, onOpenAlbum: (AlbumPageModel) -> Unit, onOpenPlaylist: (capital.yuri.yuriplayer.desktop.DesktopPlaylist) -> Unit, onOpenArtist: (String) -> Unit, - onPlayTracks: (List, Int) -> Unit, - onEnqueue: (List) -> Unit = {}, + onPlayTracks: (List, Int) -> Unit, + onEnqueue: (List) -> Unit = {}, onUnpin: (DesktopCollection.Kind, String) -> Unit = { _, _ -> }, - onAddToPlaylist: (List) -> Unit = {}, - playlistQuickAdd: (List) -> List = { emptyList() } + onAddToPlaylist: (List) -> Unit = {}, + playlistQuickAdd: (List) -> List = { emptyList() } ) { val recentAlbums = remember(recents, albums) { recents.mapNotNull { t -> - albums.firstOrNull { a -> a.tracks.any { it.id == t.id } } + albums.firstOrNull { a -> a.tracks.any { it.id == t.songKey } } }.distinctBy { it.id } } - val likedTracks = remember(liked, tracks) { tracks.filter { it.id in liked } } + val likedTracks = remember(liked, tracks) { tracks.filter { it.songKey in liked } } val shortcuts = remember(pins, liked, likedTracks, albums, playlists, tracks) { buildHomeShortcuts( pins, liked, likedTracks, albums, playlists, tracks, @@ -826,7 +995,7 @@ private fun HomeFeed( val stuffArtists = remember(pins, liked, albums, tracks) { val pinned = pins.filter { it.kind == DesktopCollection.Kind.ARTIST }.map { it.title } val fromAlbums = stuffAlbums.map { it.artist } - val fromLiked = tracks.filter { it.id in liked }.map { it.displayArtist } + val fromLiked = tracks.filter { it.songKey in liked }.map { it.displayArtist } (pinned + fromAlbums + fromLiked).map { it.trim() }.filter { it.isNotEmpty() }.distinct() } @@ -865,13 +1034,13 @@ private fun HomeFeed( if (recents.isNotEmpty()) { SectionHeader("Jump back in") LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) { - items(recents.take(12), key = { it.id }) { track -> + items(recents.take(12), key = { it.songKey }) { track -> AlbumCard( album = AlbumPageModel( - id = track.id, + id = track.songKey, title = track.displayTitle, artist = track.displayArtist, - artworkUri = track.artworkUri, + artworkUri = track.albumArtUri, tracks = emptyList() ), onClick = { onPlayTracks(listOf(track) + tracks, 0) } @@ -972,18 +1141,18 @@ private data class SpotifyPin( private fun buildHomeShortcuts( pins: List, liked: Set, - likedTracks: List, + likedTracks: List, albums: List, playlists: List, - tracks: List, + tracks: List, onOpenAlbum: (AlbumPageModel) -> Unit, onOpenPlaylist: (capital.yuri.yuriplayer.desktop.DesktopPlaylist) -> Unit, onOpenArtist: (String) -> Unit, - onPlayTracks: (List, Int) -> Unit, - onEnqueue: (List) -> Unit, + onPlayTracks: (List, Int) -> Unit, + onEnqueue: (List) -> Unit, onUnpin: (DesktopCollection.Kind, String) -> Unit, - onAddToPlaylist: (List) -> Unit, - playlistQuickAdd: (List) -> List + onAddToPlaylist: (List) -> Unit, + playlistQuickAdd: (List) -> List ): List { val out = ArrayList(8) fun add(pin: SpotifyPin) { @@ -994,7 +1163,7 @@ private fun buildHomeShortcuts( fun menu( kind: DesktopCollection.Kind?, id: String, - playable: List, + playable: List, play: () -> Unit ) = buildList { if (playable.isNotEmpty() || kind == null) { @@ -1020,8 +1189,8 @@ private fun buildHomeShortcuts( add( SpotifyPin( id = "liked", - title = "Liked Songs", - artworkUri = likedTracks.firstOrNull()?.artworkUri, + title = "My Stuff", + artworkUri = likedTracks.firstOrNull()?.albumArtUri, onClick = { onPlayTracks(likedTracks, 0) }, menu = menu(null, "liked", likedTracks) { onPlayTracks(likedTracks, 0) } ) @@ -1032,7 +1201,7 @@ private fun buildHomeShortcuts( DesktopCollection.Kind.ALBUM -> { val album = albums.firstOrNull { it.id == pin.id } ?: albums.firstOrNull { it.title.equals(pin.title, true) } - val playable = album?.tracks?.mapNotNull { row -> tracks.firstOrNull { it.id == row.id } }.orEmpty() + val playable = album?.tracks?.mapNotNull { row -> tracks.firstOrNull { it.songKey == row.id } }.orEmpty() add( SpotifyPin( pin.id, pin.title, album?.artworkUri, @@ -1052,7 +1221,7 @@ private fun buildHomeShortcuts( SpotifyPin( pin.id, pin.title, - tracks.firstOrNull { it.displayArtist.equals(pin.title, true) }?.artworkUri, + tracks.firstOrNull { it.displayArtist.equals(pin.title, true) }?.albumArtUri, circular = true, onClick = { onOpenArtist(pin.title) }, menu = menu(DesktopCollection.Kind.ARTIST, pin.id, playable) { onOpenArtist(pin.title) } @@ -1060,11 +1229,11 @@ private fun buildHomeShortcuts( ) } DesktopCollection.Kind.SONG -> { - val t = tracks.firstOrNull { it.id == pin.id } + val t = tracks.firstOrNull { it.songKey == pin.id } val playable = listOfNotNull(t) add( SpotifyPin( - pin.id, pin.title, t?.artworkUri, + pin.id, pin.title, t?.albumArtUri, onClick = { t?.let { onPlayTracks(listOf(it), 0) } }, menu = menu(DesktopCollection.Kind.SONG, pin.id, playable) { t?.let { onPlayTracks(listOf(it), 0) } @@ -1100,31 +1269,31 @@ private fun SpotifyPinCard(pin: SpotifyPin, modifier: Modifier = Modifier) { .height(72.dp) .clip(RoundedCornerShape(6.dp)) .clickable(onClick = pin.onClick), - color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.55f), - shape = RoundedCornerShape(6.dp), - tonalElevation = 2.dp - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - CoverArt( - model = pin.artworkUri, - size = 72.dp, - corner = if (pin.circular) 36.dp else 0.dp, - contentScale = if (pin.circular) { - androidx.compose.ui.layout.ContentScale.Crop - } else { - androidx.compose.ui.layout.ContentScale.Fit - } - ) - Text( - pin.title, - modifier = Modifier.padding(horizontal = 12.dp), - maxLines = 2, - overflow = TextOverflow.Ellipsis, - fontWeight = FontWeight.SemiBold, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface - ) - } + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.55f), + shape = RoundedCornerShape(6.dp), + tonalElevation = 2.dp + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + CoverArt( + model = pin.artworkUri, + size = 72.dp, + corner = if (pin.circular) 36.dp else 0.dp, + contentScale = if (pin.circular) { + androidx.compose.ui.layout.ContentScale.Crop + } else { + androidx.compose.ui.layout.ContentScale.Fit + } + ) + Text( + pin.title, + modifier = Modifier.padding(horizontal = 12.dp), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.SemiBold, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface + ) + } } } } @@ -1162,8 +1331,10 @@ private fun LibraryGrid( } } else { LazyVerticalGrid( - columns = GridCells.Adaptive(minSize = 168.dp), - contentPadding = PaddingValues(bottom = 16.dp) + columns = GridCells.Adaptive(180.dp), + contentPadding = PaddingValues(bottom = 24.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) ) { items(albums, key = { it.id }) { album -> AlbumCard(album = album, onClick = { onOpen(album) }) diff --git a/gradle.properties b/gradle.properties index 962a7968..d2248db6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. For more details, visit # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects -# org.gradle.parallel=true +org.gradle.parallel=true # When enabled, the Configuration Cache allows Gradle to skip the configuration # phase entirely if nothing that affects the build configuration (such as build scripts) # has changed. Additionally, Gradle applies performance optimizations to task execution. @@ -21,3 +21,6 @@ kotlin.code.style=official # AGP 9 built-in Kotlin + KSP: Room generates into kotlin source sets. # See https://developer.android.com/r/tools/built-in-kotlin android.disallowKotlinSourceSets=false + +# Enabled parallel sync for Gradle 9.4+ +org.gradle.tooling.parallel=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6d77ce71..21747597 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "9.3.1" +agp = "9.3.2" coreKtx = "1.19.0" junit = "4.13.2" junitVersion = "1.3.0" @@ -28,12 +28,14 @@ aboutLibraries = "14.2.1" libvlc = "3.6.2" robolectric = "4.16" composeMultiplatform = "1.8.2" +composeIconsExtended = "1.7.3" vlcj = "4.10.1" dbusJava = "5.1.1" junixsocket = "2.10.1" jna = "5.17.0" slf4j = "2.0.17" logback = "1.5.18" +lyricist = "1.6.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -66,9 +68,24 @@ androidx-media3-common = { group = "androidx.media3", name = "media3-common", ve libvlc-all = { group = "org.videolan.android", name = "libvlc-all", version.ref = "libvlc" } +koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" } koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" } koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin" } +aboutlibraries-core = { group = "com.mikepenz", name = "aboutlibraries-core", version.ref = "aboutLibraries" } + +jellyfin-core = { group = "org.jellyfin.sdk", name = "jellyfin-core", version.ref = "jellyfinSdk" } + +coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } +coil-network-ktor3 = { group = "io.coil-kt.coil3", name = "coil-network-ktor3", version.ref = "coil" } +coil-gif = { group = "io.coil-kt.coil3", name = "coil-gif", version.ref = "coil" } + +jetbrains-compose-runtime = { group = "org.jetbrains.compose.runtime", name = "runtime", version.ref = "composeMultiplatform" } +jetbrains-compose-foundation = { group = "org.jetbrains.compose.foundation", name = "foundation", version.ref = "composeMultiplatform" } +jetbrains-compose-ui = { group = "org.jetbrains.compose.ui", name = "ui", version.ref = "composeMultiplatform" } +jetbrains-compose-material3 = { group = "org.jetbrains.compose.material3", name = "material3", version.ref = "composeMultiplatform" } +jetbrains-compose-material-icons-extended = { group = "org.jetbrains.compose.material", name = "material-icons-extended", version.ref = "composeIconsExtended" } + kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "coroutines" } @@ -96,20 +113,6 @@ ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-conte ktor-client-logging = { group = "io.ktor", name = "ktor-client-logging", version.ref = "ktor" } ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } -coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } -coil-network-ktor3 = { group = "io.coil-kt.coil3", name = "coil-network-ktor3", version.ref = "coil" } -coil-gif = { group = "io.coil-kt.coil3", name = "coil-gif", version.ref = "coil" } - -jellyfin-core = { group = "org.jellyfin.sdk", name = "jellyfin-core", version.ref = "jellyfinSdk" } - -aboutlibraries-core = { group = "com.mikepenz", name = "aboutlibraries-core", version.ref = "aboutLibraries" } - -jetbrains-compose-runtime = { group = "org.jetbrains.compose.runtime", name = "runtime", version.ref = "composeMultiplatform" } -jetbrains-compose-foundation = { group = "org.jetbrains.compose.foundation", name = "foundation", version.ref = "composeMultiplatform" } -jetbrains-compose-ui = { group = "org.jetbrains.compose.ui", name = "ui", version.ref = "composeMultiplatform" } -jetbrains-compose-material3 = { group = "org.jetbrains.compose.material3", name = "material3", version.ref = "composeMultiplatform" } -jetbrains-compose-material-icons-extended = { group = "org.jetbrains.compose.material", name = "material-icons-extended", version = "1.7.3" } - [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-kmp-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } diff --git a/settings.gradle.kts b/settings.gradle.kts index c918df79..a9419b78 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,3 +33,4 @@ include(":core") include(":components") include(":app") include(":desktop") +include(":mediasession") From d6931699ae11683e0e6e871ebe41e1f25686118f Mon Sep 17 00:00:00 2001 From: Mey Date: Thu, 27 Aug 2026 08:58:21 -0500 Subject: [PATCH 62/69] vendor: add dev.toastbits:mediasession (0.1.1) fork with local fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fork the upstream dev.toastbits:mediasession library into :mediasession so we can ship MPRIS/SMTC fixes without waiting on upstream releases. This is not a dependency swap — it is intentionally vendored. Local changes over upstream 0.1.1: - Convert MPRIS Seek/SetPosition microseconds to milliseconds before invoking onSeek/onSetPosition (PlayerInterface + SessionInterface). - Add the onSetVolume callback (upstream omitted it) so volume changes made from a media key / playerctl round-trip to the app. - Normalize Volume/Rate as Float in the commonMediaSession API while marshalling to Double on the DBus wire. - Keep the commonMain/jvmMain split so the API surface is platform-agnostic (future Darwin target) and the dbus-java + JNA/SMTC bindings stay JVM-only. Upstream: dev.toastbits:mediasession 0.1.1 --- mediasession/build.gradle.kts | 25 ++ .../toastbits/mediasession/MediaSession.kt | 31 +++ .../mediasession/MediaSessionLoopMode.kt | 5 + .../mediasession/MediaSessionMetadata.kt | 28 ++ .../MediaSessionPlaybackStatus.kt | 5 + .../mediasession/MediaSessionProperties.kt | 29 +++ .../mediasession/mpris/DBusVariant.kt | 9 + .../mediasession/mpris/MprisConstants.kt | 14 + .../mediasession/mpris/MprisMediaSession.kt | 73 ++++++ .../mpris/MprisMediaSessionLoopMode.kt | 18 ++ .../mpris/MprisMediaSessionMetadata.kt | 102 ++++++++ .../mpris/MprisMediaSessionPlaybackStatus.kt | 18 ++ .../mediasession/mpris/MprisProperties.kt | 67 +++++ .../mediasession/mpris/MprisProperty.kt | 39 +++ .../mediasession/smtc/SMTCAdapter.kt | 84 ++++++ .../mediasession/smtc/SMTCMediaSession.kt | 246 ++++++++++++++++++ .../mediasession/MediaSession.jvm.kt | 23 ++ .../mediasession/linux/LinuxMediaSession.kt | 61 +++++ .../mediasession/linux/MediaInterface.kt | 10 + .../mediasession/linux/MediaSessionLinux.kt | 61 +++++ .../mediasession/linux/PlayerInterface.kt | 36 +++ .../mediasession/linux/SessionInterface.kt | 101 +++++++ .../mediasession/mpris/DBusVariant.jvm.kt | 19 ++ .../mediasession/smtc/JniSMTCAdapter.kt | 218 ++++++++++++++++ .../resources/win32-x86-64/libSMTCAdapter.dll | Bin 0 -> 3110810 bytes .../resources/win32-x86/libSMTCAdapter.dll | Bin 0 -> 3110810 bytes 26 files changed, 1322 insertions(+) create mode 100644 mediasession/build.gradle.kts create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSession.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionLoopMode.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionMetadata.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionPlaybackStatus.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionProperties.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/DBusVariant.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisConstants.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSession.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionLoopMode.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionMetadata.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionPlaybackStatus.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisProperties.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisProperty.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/smtc/SMTCAdapter.kt create mode 100644 mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/smtc/SMTCMediaSession.kt create mode 100644 mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/MediaSession.jvm.kt create mode 100644 mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/LinuxMediaSession.kt create mode 100644 mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/MediaInterface.kt create mode 100644 mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/MediaSessionLinux.kt create mode 100644 mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/PlayerInterface.kt create mode 100644 mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/SessionInterface.kt create mode 100644 mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/mpris/DBusVariant.jvm.kt create mode 100644 mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/smtc/JniSMTCAdapter.kt create mode 100755 mediasession/src/jvmMain/resources/win32-x86-64/libSMTCAdapter.dll create mode 100755 mediasession/src/jvmMain/resources/win32-x86/libSMTCAdapter.dll diff --git a/mediasession/build.gradle.kts b/mediasession/build.gradle.kts new file mode 100644 index 00000000..70ba2174 --- /dev/null +++ b/mediasession/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) +} + +// Vendored fork of dev.toastbits:mediasession (0.1.1). Kept as a local +// subproject so we can fix upstream bugs (Float/Double volume marshalling, +// microsecond seek offsets, missing onSetVolume callback) without waiting +// on upstream releases. +kotlin { + jvm { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + } + } + + sourceSets { + jvmMain.dependencies { + implementation(libs.dbus.java.core) + implementation(libs.dbus.java.transport.native.unix) + implementation(libs.junixsocket.core) + implementation(libs.jna) + implementation(libs.jna.platform) + } + } +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSession.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSession.kt new file mode 100644 index 00000000..5a1682e7 --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSession.kt @@ -0,0 +1,31 @@ +package dev.toastbits.mediasession + +interface MediaSession: MediaSessionProperties { + val enabled: Boolean + fun setEnabled(enabled: Boolean) + + var onRaise: (() -> Unit)? + var onQuit: (() -> Unit)? + var onNext: (() -> Unit)? + var onPrevious: (() -> Unit)? + var onPause: (() -> Unit)? + var onPlayPause: (() -> Unit)? + var onStop: (() -> Unit)? + var onPlay: (() -> Unit)? + var onSeek: ((by_ms: Long) -> Unit)? + var onSetPosition: ((to_ms: Long) -> Unit)? + var onOpenUri: ((uri: String) -> Unit)? + var onSetRate: ((rate: Float) -> Unit)? + var onSetLoop: ((loop_mode: MediaSessionLoopMode) -> Unit)? + var onSetShuffle: ((shuffle_mode: Boolean) -> Unit)? + var onSetVolume: ((volume: Float) -> Unit)? + + fun getPositionMs(): Long = 0 + fun onPositionChanged() + + companion object { + fun create(getPositionMs: (() -> Long)? = null): MediaSession? = createMediaSession(getPositionMs) + } +} + +expect fun createMediaSession(getPositionMs: (() -> Long)? = null): MediaSession? diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionLoopMode.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionLoopMode.kt new file mode 100644 index 00000000..ab6b90ab --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionLoopMode.kt @@ -0,0 +1,5 @@ +package dev.toastbits.mediasession + +enum class MediaSessionLoopMode { + NONE, ONE, ALL +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionMetadata.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionMetadata.kt new file mode 100644 index 00000000..65c19eb2 --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionMetadata.kt @@ -0,0 +1,28 @@ +package dev.toastbits.mediasession + +data class MediaSessionMetadata( + val track_id: String? = null, + val length_ms: Long? = null, + val art_url: String? = null, + val album: String? = null, + val album_artists: List? = null, + val artist: String? = null, + val lyrics: String? = null, + val audio_bpm: Int? = null, + val auto_rating: Float? = null, + val comment: List? = null, + val composer: List? = null, + val content_created: String? = null, + val disc_number: Int? = null, + val first_used: String? = null, + val genres: List? = null, + val last_used: String? = null, + val lyricist: List? = null, + val title: String? = null, + val track_number: Int? = null, + val url: String? = null, + val use_count: Int? = null, + val user_rating: Float? = null, + + val custom_metadata: Map = emptyMap() +) diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionPlaybackStatus.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionPlaybackStatus.kt new file mode 100644 index 00000000..8667bf2b --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionPlaybackStatus.kt @@ -0,0 +1,5 @@ +package dev.toastbits.mediasession + +enum class MediaSessionPlaybackStatus { + PLAYING, PAUSED, STOPPED +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionProperties.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionProperties.kt new file mode 100644 index 00000000..6b811cb3 --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/MediaSessionProperties.kt @@ -0,0 +1,29 @@ +package dev.toastbits.mediasession + +interface MediaSessionProperties { + val identity: String + val desktop_entry: String? + val supported_uri_schemes: List + val supported_mime_types: List + val loop_mode: MediaSessionLoopMode + val shuffle: Boolean + val volume: Float + val rate: Float + val playback_status: MediaSessionPlaybackStatus + val maximum_rate: Float + val minimum_rate: Float + val metadata: MediaSessionMetadata + + fun setIdentity(identity: String) + fun setDesktopEntry(desktop_entry: String?) + fun setSupportedUriSchemes(supported_uri_schemes: List) + fun setSupportedMimeTypes(supported_mime_types: List) + fun setLoopMode(loop_mode: MediaSessionLoopMode) + fun setShuffle(shuffle: Boolean) + fun setVolume(volume: Float) + fun setRate(rate: Float) + fun setPlaybackStatus(status: MediaSessionPlaybackStatus) + fun setMaximumRate(maximum_rate: Float) + fun setMinimumRate(minimum_rate: Float) + fun setMetadata(metadata: MediaSessionMetadata) +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/DBusVariant.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/DBusVariant.kt new file mode 100644 index 00000000..006dca8d --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/DBusVariant.kt @@ -0,0 +1,9 @@ +package dev.toastbits.mediasession.mpris + +expect class DBusVariant + +expect val DBusVariant.value: T + +expect fun createDBusVariant(value: T, signature: String): DBusVariant +expect inline fun createDBusVariant(value: T): DBusVariant +expect inline fun createDBusVariant(value: Array): DBusVariant>> diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisConstants.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisConstants.kt new file mode 100644 index 00000000..361cb09d --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisConstants.kt @@ -0,0 +1,14 @@ +package dev.toastbits.mediasession.mpris + +object MprisConstants { + val OBJECT_PATH: String = "/org/mpris/MediaPlayer2" + + enum class Interface(val iface: String) { + GENERAL("org.mpris.MediaPlayer2"), + PLAYER("org.mpris.MediaPlayer2.Player"), + DBUS_PROPERTIES("org.freedesktop.DBus.Properties") + } + + fun getBusName(identity: String): String = + "org.mpris.MediaPlayer2.$identity" +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSession.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSession.kt new file mode 100644 index 00000000..119cb541 --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSession.kt @@ -0,0 +1,73 @@ +@file:Suppress("UNCHECKED_CAST") +package dev.toastbits.mediasession.mpris + +import dev.toastbits.mediasession.MediaSessionProperties +import dev.toastbits.mediasession.MediaSessionLoopMode +import dev.toastbits.mediasession.MediaSessionMetadata +import dev.toastbits.mediasession.MediaSessionPlaybackStatus + +abstract class MprisMediaSession: MediaSessionProperties { + abstract val properties: MprisProperties + + override val identity: String + get() = (properties.getProperty(MprisProperty.Identity) as DBusVariant).value + override val desktop_entry: String? + get() = (properties.getProperty(MprisProperty.DesktopEntry) as DBusVariant).value + override val supported_uri_schemes: List + get() = (properties.getProperty(MprisProperty.SupportedUriSchemes) as DBusVariant>).value + override val supported_mime_types: List + get() = (properties.getProperty(MprisProperty.SupportedMimeTypes) as DBusVariant>).value + override val loop_mode: MediaSessionLoopMode + get() = (properties.getProperty(MprisProperty.LoopStatus) as DBusVariant).value?.fromMprisLoopMode() ?: MediaSessionLoopMode.NONE + override val shuffle: Boolean + get() = (properties.getProperty(MprisProperty.Shuffle) as DBusVariant).value ?: false + override val volume: Float + get() = (properties.getProperty(MprisProperty.Volume) as DBusVariant).value.toFloat() + override val rate: Float + get() = (properties.getProperty(MprisProperty.Rate) as DBusVariant).value + override val metadata: MediaSessionMetadata + get() = (properties.getProperty(MprisProperty.Metadata) as DBusVariant>>?)?.value?.fromMprisPlayerMetadata(identity) ?: MediaSessionMetadata() + override val playback_status: MediaSessionPlaybackStatus + get() = (properties.getProperty(MprisProperty.PlaybackStatus) as DBusVariant).value.fromMprisPlaybackStatus() + override val maximum_rate: Float + get() = (properties.getProperty(MprisProperty.MaximumRate) as DBusVariant).value + override val minimum_rate: Float + get() = (properties.getProperty(MprisProperty.MinimumRate) as DBusVariant).value + + override fun setIdentity(identity: String) = properties.setProperty(MprisProperty.Identity, createDBusVariant(identity)) + override fun setDesktopEntry(desktop_entry: String?) = properties.setProperty(MprisProperty.DesktopEntry, createDBusVariant(desktop_entry)) + override fun setSupportedUriSchemes(supported_uri_schemes: List) = properties.setProperty(MprisProperty.SupportedUriSchemes, createDBusVariant(supported_uri_schemes.toTypedArray())) + override fun setSupportedMimeTypes(supported_mime_types: List) = properties.setProperty(MprisProperty.SupportedMimeTypes, createDBusVariant(supported_mime_types.toTypedArray())) + override fun setLoopMode(loop_mode: MediaSessionLoopMode) = properties.setProperty(MprisProperty.LoopStatus, createDBusVariant(loop_mode.toMprisLoopMode())) + override fun setShuffle(shuffle: Boolean) = properties.setProperty(MprisProperty.Shuffle, createDBusVariant(shuffle)) + override fun setVolume(volume: Float) = properties.setProperty(MprisProperty.Volume, createDBusVariant(volume.toDouble())) + override fun setRate(rate: Float) = properties.setProperty(MprisProperty.Rate, createDBusVariant(rate)) + override fun setPlaybackStatus(status: MediaSessionPlaybackStatus) = properties.setProperty(MprisProperty.PlaybackStatus, createDBusVariant(status.toMprisPlaybackStatus())) + override fun setMaximumRate(maximum_rate: Float) = properties.setProperty(MprisProperty.MaximumRate, createDBusVariant(maximum_rate)) + override fun setMinimumRate(minimum_rate: Float) = properties.setProperty(MprisProperty.MinimumRate, createDBusVariant(minimum_rate)) + + override fun setMetadata(metadata: MediaSessionMetadata) = + properties.setProperty( + MprisProperty.Metadata, + createDBusVariant( + metadata + .toMprisPlayerMetadata(identity) + .entries + .associate { + val value = it.value + + try { + if (value is List<*>) { + return@associate it.key to createDBusVariant((value as List).toTypedArray()) + } + + return@associate it.key to createDBusVariant(value) + } + catch (e: Throwable) { + throw RuntimeException("Could not convert metadata key '${it.key}' with value '$value'", e) + } + }, + "a{sv}" + ) + ) +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionLoopMode.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionLoopMode.kt new file mode 100644 index 00000000..216191e7 --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionLoopMode.kt @@ -0,0 +1,18 @@ +package dev.toastbits.mediasession.mpris + +import dev.toastbits.mediasession.MediaSessionLoopMode + +fun MediaSessionLoopMode.toMprisLoopMode(): String = + when (this) { + MediaSessionLoopMode.NONE -> "None" + MediaSessionLoopMode.ONE -> "Track" + MediaSessionLoopMode.ALL -> "Playlist" + } + +fun String.fromMprisLoopMode(): MediaSessionLoopMode = + when (this) { + "None" -> MediaSessionLoopMode.NONE + "Track" -> MediaSessionLoopMode.ONE + "Playlist" -> MediaSessionLoopMode.ALL + else -> throw NotImplementedError("Unknown loop status '$this'") + } diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionMetadata.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionMetadata.kt new file mode 100644 index 00000000..798b7015 --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionMetadata.kt @@ -0,0 +1,102 @@ +package dev.toastbits.mediasession.mpris + +import dev.toastbits.mediasession.MediaSessionMetadata +import kotlin.text.Regex + +@Suppress("UNCHECKED_CAST") +fun MediaSessionMetadata.toMprisPlayerMetadata(identity: String): Map { + val track_id_error: String? = track_id?.let { getTrackIdError(it) } + check(track_id_error == null) { + "Track ID '$track_id' is not a valid DBus object path. $track_id_error. (See https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path)" + } + + return mutableMapOf( + "mpris:trackid" to track_id, + "mpris:length" to length_ms?.times(1000L), + "mpris:artUrl" to art_url, + "xesam:album" to album, + "xesam:albumArtist" to album_artists, + "xesam:artist" to artist, + "xesam:asText" to lyrics, + "xesam:audioBPM" to audio_bpm, + "xesam:autoRating" to auto_rating, + "xesam:comment" to comment, + "xesam:composer" to composer, + "xesam:contentCreated" to content_created, + "xesam:discNumber" to disc_number, + "xesam:firstUsed" to first_used, + "xesam:genre" to genres, + "xesam:lastUsed" to last_used, + "xesam:lyricist" to lyricist, + "xesam:title" to title, + "xesam:trackNumber" to track_number, + "xesam:url" to url, + "xesam:useCount" to use_count, + "xesam:userRating" to user_rating + ) + .also { map -> + for ((key, value) in custom_metadata) { + map["$identity:$key"] = value + } + } + .filter { it.value != null } as Map +} + +@Suppress("UNCHECKED_CAST") +fun Map>.fromMprisPlayerMetadata(identity: String): MediaSessionMetadata { + val default: MediaSessionMetadata = MediaSessionMetadata() + val custom_prefix: String = identity + ":" + + return MediaSessionMetadata( + track_id = (get("mpris:trackid")?.value ?: default.track_id) as String?, + length_ms = (get("mpris:length")?.value as Long?)?.div(1000L) ?: default.length_ms, + art_url = (get("mpris:artUrl")?.value ?: default.art_url) as String?, + album = (get("xesam:album")?.value ?: default.album) as String?, + album_artists = (get("xesam:albumArtist")?.value as Array>?)?.toStringList() ?: default.album_artists, + artist = (get("xesam:artist")?.value ?: default.artist) as String?, + lyrics = (get("xesam:asText")?.value ?: default.lyrics) as String?, + audio_bpm = (get("xesam:audioBPM")?.value ?: default.audio_bpm) as Int?, + auto_rating = (get("xesam:autoRating")?.value ?: default.auto_rating) as Float?, + comment = (get("xesam:comment")?.value as Array>?)?.toStringList() ?: default.comment, + composer = (get("xesam:composer")?.value as Array>?)?.toStringList() ?: default.composer, + content_created = (get("xesam:contentCreated")?.value ?: default.content_created) as String?, + disc_number = (get("xesam:discNumber")?.value ?: default.disc_number) as Int?, + first_used = (get("xesam:firstUsed")?.value ?: default.first_used) as String?, + genres = (get("xesam:genre")?.value as Array>?)?.toStringList() ?: default.genres, + last_used = (get("xesam:lastUsed")?.value ?: default.last_used) as String?, + lyricist = (get("xesam:lyricist")?.value as Array>?)?.toStringList() ?: default.lyricist, + title = (get("xesam:title")?.value ?: default.title) as String?, + track_number = (get("xesam:trackNumber")?.value ?: default.track_number) as Int?, + url = (get("xesam:url")?.value ?: default.url) as String?, + use_count = (get("xesam:useCount")?.value ?: default.use_count) as Int?, + user_rating = (get("xesam:userRating")?.value ?: default.user_rating) as Float?, + custom_metadata = mapNotNull { entry -> + if (!entry.key.startsWith(custom_prefix)) { + return@mapNotNull null + } + + val value: String = (entry.value.value as String?) ?: return@mapNotNull null + return@mapNotNull entry.key.drop(custom_prefix.length) to value + }.toMap() + ) +} + +fun Array>.toStringList(): List = + map { it.value as String } + +fun getTrackIdError(id: String): String? { + if (!id.startsWith("/")) { + return "ID doesn't begin with '/'" + } + + val parts: List = id.split('/').filter { it.isNotEmpty() } + val part_regex: Regex = Regex("^[A-Za-z0-9_]+\$") + + for ((index, part) in parts.withIndex()) { + if (!part_regex.matches(part)) { + return "Part ${index + 1} '$part' contains illegal character(s)" + } + } + + return null +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionPlaybackStatus.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionPlaybackStatus.kt new file mode 100644 index 00000000..75b468e0 --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisMediaSessionPlaybackStatus.kt @@ -0,0 +1,18 @@ +package dev.toastbits.mediasession.mpris + +import dev.toastbits.mediasession.MediaSessionPlaybackStatus + +fun MediaSessionPlaybackStatus.toMprisPlaybackStatus(): String = + when (this) { + MediaSessionPlaybackStatus.PLAYING -> "Playing" + MediaSessionPlaybackStatus.PAUSED -> "Paused" + MediaSessionPlaybackStatus.STOPPED -> "Stopped" + } + +fun String.fromMprisPlaybackStatus(): MediaSessionPlaybackStatus = + when (this) { + "Playing" -> MediaSessionPlaybackStatus.PLAYING + "Paused" -> MediaSessionPlaybackStatus.PAUSED + "Stopped" -> MediaSessionPlaybackStatus.STOPPED + else -> throw NotImplementedError("Unknown playback status '$this'") + } diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisProperties.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisProperties.kt new file mode 100644 index 00000000..f94def4b --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisProperties.kt @@ -0,0 +1,67 @@ +package dev.toastbits.mediasession.mpris + +import dev.toastbits.mediasession.MediaSession +import dev.toastbits.mediasession.MediaSessionMetadata + +abstract class MprisProperties { + protected abstract val session: MediaSession + protected abstract fun emitPropertyChange(property: MprisProperty, value: DBusVariant<*>?) + + var metadata: MediaSessionMetadata = MediaSessionMetadata() + private set + + private val properties: MutableMap?> = + mutableMapOf( + MprisProperty.HasTrackList to createDBusVariant(false), + MprisProperty.Identity to createDBusVariant("mediasessionkt"), + MprisProperty.DesktopEntry to null, + MprisProperty.SupportedUriSchemes to createDBusVariant(emptyArray()), + MprisProperty.SupportedMimeTypes to createDBusVariant(emptyArray()), + MprisProperty.PlaybackStatus to createDBusVariant("Stopped"), + MprisProperty.LoopStatus to createDBusVariant("None"), + MprisProperty.Rate to createDBusVariant(1.0), + MprisProperty.Shuffle to createDBusVariant(false), + MprisProperty.Metadata to createDBusVariant(emptyMap>(), "a{sv}"), + MprisProperty.Volume to createDBusVariant(1.0), + MprisProperty.MaximumRate to createDBusVariant(1.0), + MprisProperty.MinimumRate to createDBusVariant(1.0), + ) + + fun getProperty(property: MprisProperty): DBusVariant<*>? = + when (property) { + MprisProperty.Position -> createDBusVariant(session.getPositionMs() * 1000) + MprisProperty.CanRaise -> createDBusVariant(session.onRaise != null) + MprisProperty.CanQuit -> createDBusVariant(session.onQuit != null) + MprisProperty.CanGoNext -> createDBusVariant(session.onNext != null) + MprisProperty.CanGoPrevious -> createDBusVariant(session.onPrevious != null) + MprisProperty.CanPlay -> createDBusVariant(session.onPlay != null) + MprisProperty.CanPause -> createDBusVariant(session.onPause != null) + MprisProperty.CanSeek -> createDBusVariant(session.onSeek != null) + MprisProperty.CanControl -> createDBusVariant(true) + else -> { + if (!properties.contains(property)) { + throw NotImplementedError(property.toString()) + } + + properties[property] + } + } + + inline fun forEachProperty(action: (MprisProperty, DBusVariant<*>) -> Unit) { + for (property in MprisProperty.entries) { + val value: DBusVariant<*> = getProperty(property) ?: continue + action(property, value) + } + } + + fun setProperty(property: MprisProperty, value: DBusVariant<*>?) { + onPropertySet(property, value) + emitPropertyChange(property, value) + } + + protected fun onPropertySet(property: MprisProperty, value: DBusVariant<*>?) { + if (properties.contains(property)) { + properties[property] = value + } + } +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisProperty.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisProperty.kt new file mode 100644 index 00000000..c278911b --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/mpris/MprisProperty.kt @@ -0,0 +1,39 @@ +package dev.toastbits.mediasession.mpris + +enum class MprisProperty { + CanRaise, + CanQuit, + CanGoNext, + CanGoPrevious, + CanPlay, + CanPause, + CanSeek, + CanControl, + + HasTrackList, + Identity, + DesktopEntry, + SupportedUriSchemes, + SupportedMimeTypes, + PlaybackStatus, + LoopStatus, + Rate, + Shuffle, + Metadata, + Volume, + Position, + MaximumRate, + MinimumRate; + + fun getInterface(): MprisConstants.Interface = + when (this) { + CanQuit, + CanRaise, + HasTrackList, + Identity, + DesktopEntry, + SupportedUriSchemes, + SupportedMimeTypes -> MprisConstants.Interface.GENERAL + else -> MprisConstants.Interface.PLAYER + } +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/smtc/SMTCAdapter.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/smtc/SMTCAdapter.kt new file mode 100644 index 00000000..f0cccc56 --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/smtc/SMTCAdapter.kt @@ -0,0 +1,84 @@ +package dev.toastbits.mediasession.smtc + +import dev.toastbits.mediasession.MediaSessionLoopMode + +interface SMTCAdapter { + fun init(): Int + + var onPause: (() -> Unit)? + var onStop: (() -> Unit)? + var onPlay: (() -> Unit)? + var onNext: (() -> Unit)? + var onPrevious: (() -> Unit)? + var onSetPosition: ((to_ms: Long) -> Unit)? + var onSetRate: ((rate: Float) -> Unit)? + var onSetLoop: ((loop_mode: Int) -> Unit)? + var onSetShuffle: ((shuffle_mode: Boolean) -> Unit)? + + fun revokeCallbacks() + + fun getEnabled(): Boolean + fun setEnabled(enabled: Boolean) + + fun getNextEnabled(): Boolean + fun setNextEnabled(enabled: Boolean) + + fun getPreviousEnabled(): Boolean + fun setPreviousEnabled(enabled: Boolean) + + fun getPlayEnabled(): Boolean + fun setPlayEnabled(enabled: Boolean) + + fun getPauseEnabled(): Boolean + fun setPauseEnabled(enabled: Boolean) + + fun getStopEnabled(): Boolean + fun setStopEnabled(enabled: Boolean) + + fun getRate(): Double + fun setRate(rate: Double) + + fun getShuffle(): Boolean + fun setShuffle(shuffle: Boolean) + + fun getLoop(): Int + fun setLoop(loop: Int) + + fun getPlaybackState(): Int + fun setPlaybackState(state: Int) + + fun setTimelineProperties(start: Long, end: Long, seek_start: Long, seek_end: Long) + fun setPosition(position: Long) + + fun update() + fun reset() + + fun getMediaType(): Int + fun setMediaType(type: Int) + + fun thumbnailLoaded(): Boolean + fun setThumbnail(path: String, update_when_loaded: Boolean) + + fun getMusicTitle(): String + fun setMusicTitle(title: String) + + fun getMusicArtist(): String + fun setMusicArtist(artist: String) + + fun getMusicAlbumTitle(): String + fun setMusicAlbumTitle(title: String) + + fun getMusicAlbumArtist(): String + fun setMusicAlbumArtist(artist: String) + + fun getMusicGenresSize(): Int + fun getMusicGenreAt(index: Int): String + fun addMusicGenre(genre: String) + fun clearMusicGenres() + + fun getMusicAlbumTrackCount(): Int + fun setMusicAlbumTrackCount(count: Int) + + fun getMusicTrack(): Int + fun setMusicTrack(track: Int) +} diff --git a/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/smtc/SMTCMediaSession.kt b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/smtc/SMTCMediaSession.kt new file mode 100644 index 00000000..4f7acc13 --- /dev/null +++ b/mediasession/src/commonMain/kotlin/dev/toastbits/mediasession/smtc/SMTCMediaSession.kt @@ -0,0 +1,246 @@ +package dev.toastbits.mediasession.smtc + +import dev.toastbits.mediasession.MediaSessionProperties +import dev.toastbits.mediasession.MediaSession +import dev.toastbits.mediasession.MediaSessionLoopMode +import dev.toastbits.mediasession.MediaSessionMetadata +import dev.toastbits.mediasession.MediaSessionPlaybackStatus + +open class SMTCMediaSession(private val smtc: SMTCAdapter): MediaSessionProperties, MediaSession { + private var session_enabled: Boolean = false + private var _art_url: String? = null + + override val enabled: Boolean + get() = session_enabled + + init { + val result: Int = + try { + smtc.init() + } + catch (e: Throwable) { + throw RuntimeException("Failed to initialise smtc", e) + } + + if (result != 0) { + throw RuntimeException("Failed to initialise smtc ($result), is Windows.Media.Playback.MediaPlayer available?") + } + } + + override fun setEnabled(enabled: Boolean) { + if (enabled == session_enabled) { + return + } + + smtc.setEnabled(enabled) + session_enabled = enabled + + if (enabled) { + update() + } + } + + override var onRaise: (() -> Unit)? = null + override var onQuit: (() -> Unit)? = null + + override var onNext: (() -> Unit)? = null + set(value) { + field = value + smtc.onNext = value + smtc.setNextEnabled(value != null) + } + override var onPrevious: (() -> Unit)? = null + set(value) { + field = value + smtc.onPrevious = value + smtc.setPreviousEnabled(value != null) + } + + override var onPause: (() -> Unit)? = null + set(value) { + field = value + smtc.onPause = value + } + override var onStop: (() -> Unit)? = null + set(value) { + field = value + smtc.onStop = value + } + override var onPlay: (() -> Unit)? = null + set(value) { + field = value + smtc.onPlay = value + } + override var onSetPosition: ((to_ms: Long) -> Unit)? = null + set(value) { + field = value + smtc.onSetPosition = value + } + override var onSetRate: ((rate: Float) -> Unit)? = null + set(value) { + field = value + smtc.onSetRate = value + } + override var onSetLoop: ((loop_mode: MediaSessionLoopMode) -> Unit)? = null + set(value) { + field = value + + if (value == null) { + smtc.onSetLoop = null + } + else { + smtc.onSetLoop = { loop_mode -> + value.invoke(loop_mode.toLoopMode()) + } + } + } + override var onSetShuffle: ((shuffle_mode: Boolean) -> Unit)? = null + set(value) { + field = value + smtc.onSetShuffle = value + } + + override var onPlayPause: (() -> Unit)? = null + override var onSeek: ((by_ms: Long) -> Unit)? = null + override var onOpenUri: ((uri: String) -> Unit)? = null + override var onSetVolume: ((volume: Float) -> Unit)? = null + + override fun onPositionChanged() { + smtc.setPosition(getPositionMs()) + } + + override val identity: String + get() = "" + override val desktop_entry: String? + get() = null + override val supported_uri_schemes: List + get() = emptyList() + override val supported_mime_types: List + get() = emptyList() + override val loop_mode: MediaSessionLoopMode + get() = smtc.getLoop().toLoopMode() + override val shuffle: Boolean + get() = smtc.getShuffle() + override val volume: Float + get() = 0f + override val rate: Float + get() = smtc.getRate().toFloat() + override val metadata: MediaSessionMetadata + get() = MediaSessionMetadata( + track_id = null, + length_ms = null, + art_url = _art_url, + album = smtc.getMusicAlbumTitle(), + album_artists = listOfNotNull(smtc.getMusicAlbumArtist()), + artist = smtc.getMusicArtist(), + lyrics = null, + audio_bpm = null, + auto_rating = null, + comment = null, + composer = null, + content_created = null, + disc_number = null, + first_used = null, + genres = (0 until smtc.getMusicGenresSize()).mapNotNull { i -> smtc.getMusicGenreAt(i) }, + last_used = null, + lyricist = null, + title = smtc.getMusicTitle(), + track_number = smtc.getMusicTrack().toInt(), + url = null, + use_count = null, + user_rating = null + ) + override val playback_status: MediaSessionPlaybackStatus + get() = + when (smtc.getPlaybackState()) { + 0 -> MediaSessionPlaybackStatus.STOPPED + 1 -> MediaSessionPlaybackStatus.PAUSED + 2 -> MediaSessionPlaybackStatus.STOPPED + 3 -> MediaSessionPlaybackStatus.PLAYING + 4 -> MediaSessionPlaybackStatus.PAUSED + else -> MediaSessionPlaybackStatus.STOPPED + } + override val maximum_rate: Float + get() = 1f + override val minimum_rate: Float + get() = 0f + + override fun setIdentity(identity: String) {} + + override fun setDesktopEntry(desktop_entry: String?) {} + + override fun setSupportedUriSchemes(supported_uri_schemes: List) {} + + override fun setSupportedMimeTypes(supported_mime_types: List) {} + + override fun setLoopMode(loop_mode: MediaSessionLoopMode) { + smtc.setLoop(loop_mode.toInt()) + update() + } + override fun setShuffle(shuffle: Boolean) { + smtc.setShuffle(shuffle) + update() + } + override fun setVolume(volume: Float) { + // TODO + } + override fun setRate(rate: Float) { + smtc.setRate(rate.toDouble()) + update() + } + override fun setPlaybackStatus(status: MediaSessionPlaybackStatus) { + smtc.setPlaybackState( + when (status) { + MediaSessionPlaybackStatus.PLAYING -> 3 + MediaSessionPlaybackStatus.PAUSED -> 1 + MediaSessionPlaybackStatus.STOPPED -> 2 + } + ) + update() + } + override fun setMaximumRate(maximum_rate: Float) {} + override fun setMinimumRate(minimum_rate: Float) {} + + override fun setMetadata(metadata: MediaSessionMetadata) { + smtc.setMusicTitle(metadata.title ?: "") + smtc.setMusicArtist(metadata.artist ?: "") + smtc.setMusicAlbumTitle(metadata.album ?: "") + smtc.setMusicAlbumArtist(metadata.album_artists?.firstOrNull() ?: "") + smtc.setMusicTrack(metadata.track_number ?: 0) + + smtc.setThumbnail((metadata.art_url ?: "").replace("/", "\\"), true) + _art_url = metadata.art_url + + smtc.clearMusicGenres() + for (genre in metadata.genres ?: emptyList()) { + smtc.addMusicGenre(genre) + } + + val length: Long = metadata.length_ms ?: 0 + smtc.setTimelineProperties(0, length, 0, length) + + update() + } + + private fun update() { + if (enabled) { + smtc.update() + } + } +} + +private fun Int.toLoopMode(): MediaSessionLoopMode = + when (this) { + 0 -> MediaSessionLoopMode.NONE + 1 -> MediaSessionLoopMode.ONE + 2 -> MediaSessionLoopMode.ALL + else -> throw NotImplementedError(this.toString()) + } + + +private fun MediaSessionLoopMode.toInt(): Int = + when (this) { + MediaSessionLoopMode.NONE -> 0 + MediaSessionLoopMode.ONE -> 1 + MediaSessionLoopMode.ALL -> 2 + } diff --git a/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/MediaSession.jvm.kt b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/MediaSession.jvm.kt new file mode 100644 index 00000000..25044d6e --- /dev/null +++ b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/MediaSession.jvm.kt @@ -0,0 +1,23 @@ +package dev.toastbits.mediasession + +import dev.toastbits.mediasession.linux.LinuxMediaSession +import dev.toastbits.mediasession.smtc.SMTCMediaSession +import dev.toastbits.mediasession.smtc.JniSMTCAdapter + +actual fun createMediaSession(getPositionMs: (() -> Long)?): MediaSession? { + val os: String = System.getProperty("os.name").lowercase() + + if (os.startsWith("windows")) { + return object : SMTCMediaSession(JniSMTCAdapter()) { + override fun getPositionMs(): Long = getPositionMs?.invoke() ?: super.getPositionMs() + } + } + + if (os.contains("linux")) { + return object : LinuxMediaSession() { + override fun getPositionMs(): Long = getPositionMs?.invoke() ?: super.getPositionMs() + } + } + + return null +} diff --git a/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/LinuxMediaSession.kt b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/LinuxMediaSession.kt new file mode 100644 index 00000000..dddbc338 --- /dev/null +++ b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/LinuxMediaSession.kt @@ -0,0 +1,61 @@ +package dev.toastbits.mediasession.linux + +import dev.toastbits.mediasession.* +import dev.toastbits.mediasession.mpris.MprisMediaSession +import dev.toastbits.mediasession.mpris.MprisProperties +import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder + +@Suppress("UNCHECKED_CAST") +open class LinuxMediaSession: MprisMediaSession(), MediaSession, MediaSessionProperties { + private var session_enabled: Boolean = false + private val connection: DBusConnection = DBusConnectionBuilder.forSessionBus().build() + + private val session_interface: SessionInterface = SessionInterface(this, connection) + override val properties: MprisProperties = session_interface + + override val enabled: Boolean + get() = session_enabled + + override fun setEnabled(enabled: Boolean) { + if (enabled == session_enabled) { + return + } + + if (enabled) { + connection.requestBusName("org.mpris.MediaPlayer2.$identity") + connection.exportObject(session_interface.getObjectPath(), session_interface) + session_enabled = true + } + else { + connection.unExportObject(session_interface.getObjectPath()) + connection.releaseBusName("org.mpris.MediaPlayer2.$identity") + session_enabled = false + } + } + + override var onRaise: (() -> Unit)? = null + override var onQuit: (() -> Unit)? = null + override var onNext: (() -> Unit)? = null + override var onPrevious: (() -> Unit)? = null + override var onPause: (() -> Unit)? = null + override var onPlayPause: (() -> Unit)? = null + override var onStop: (() -> Unit)? = null + override var onPlay: (() -> Unit)? = null + override var onSeek: ((by_ms: Long) -> Unit)? = null + override var onSetPosition: ((to_ms: Long) -> Unit)? = null + override var onOpenUri: ((uri: String) -> Unit)? = null + override var onSetRate: ((rate: Float) -> Unit)? = null + override var onSetLoop: ((loop_mode: MediaSessionLoopMode) -> Unit)? = null + override var onSetShuffle: ((shuffle_mode: Boolean) -> Unit)? = null + override var onSetVolume: ((volume: Float) -> Unit)? = null + + override fun onPositionChanged() { + connection.sendMessage( + PlayerInterface.Seeked( + session_interface.getObjectPath(), + getPositionMs() + ) + ) + } +} diff --git a/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/MediaInterface.kt b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/MediaInterface.kt new file mode 100644 index 00000000..e08a9e7b --- /dev/null +++ b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/MediaInterface.kt @@ -0,0 +1,10 @@ +package dev.toastbits.mediasession + +import org.freedesktop.dbus.annotations.DBusInterfaceName +import org.freedesktop.dbus.interfaces.DBusInterface + +@DBusInterfaceName("org.mpris.MediaPlayer2") +internal interface MediaInterface: DBusInterface { + fun Raise() + fun Quit() +} diff --git a/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/MediaSessionLinux.kt b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/MediaSessionLinux.kt new file mode 100644 index 00000000..6e9c12c3 --- /dev/null +++ b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/MediaSessionLinux.kt @@ -0,0 +1,61 @@ +package dev.toastbits.mediasession.linux + +import dev.toastbits.mediasession.* +import dev.toastbits.mediasession.mpris.MprisMediaSession +import dev.toastbits.mediasession.mpris.MprisProperties +import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder + +@Suppress("UNCHECKED_CAST") +open class MediaSessionLinux: MprisMediaSession(), MediaSession, MediaSessionProperties { + private var session_enabled: Boolean = false + private val connection: DBusConnection = DBusConnectionBuilder.forSessionBus().build() + + private val session_interface: SessionInterface = SessionInterface(this, connection) + override val properties: MprisProperties = session_interface + + override val enabled: Boolean + get() = session_enabled + + override fun setEnabled(enabled: Boolean) { + if (enabled == session_enabled) { + return + } + + if (enabled) { + connection.requestBusName("org.mpris.MediaPlayer2.$identity") + connection.exportObject(session_interface.getObjectPath(), session_interface) + session_enabled = true + } + else { + connection.unExportObject(session_interface.getObjectPath()) + connection.releaseBusName("org.mpris.MediaPlayer2.$identity") + session_enabled = false + } + } + + override var onRaise: (() -> Unit)? = null + override var onQuit: (() -> Unit)? = null + override var onNext: (() -> Unit)? = null + override var onPrevious: (() -> Unit)? = null + override var onPause: (() -> Unit)? = null + override var onPlayPause: (() -> Unit)? = null + override var onStop: (() -> Unit)? = null + override var onPlay: (() -> Unit)? = null + override var onSeek: ((by_ms: Long) -> Unit)? = null + override var onSetPosition: ((to_ms: Long) -> Unit)? = null + override var onOpenUri: ((uri: String) -> Unit)? = null + override var onSetRate: ((rate: Float) -> Unit)? = null + override var onSetLoop: ((loop_mode: MediaSessionLoopMode) -> Unit)? = null + override var onSetShuffle: ((shuffle_mode: Boolean) -> Unit)? = null + override var onSetVolume: ((volume: Float) -> Unit)? = null + + override fun onPositionChanged() { + connection.sendMessage( + PlayerInterface.Seeked( + session_interface.getObjectPath(), + getPositionMs() + ) + ) + } +} diff --git a/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/PlayerInterface.kt b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/PlayerInterface.kt new file mode 100644 index 00000000..de17c803 --- /dev/null +++ b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/PlayerInterface.kt @@ -0,0 +1,36 @@ +package dev.toastbits.mediasession + +import org.freedesktop.dbus.annotations.DBusInterfaceName +import org.freedesktop.dbus.annotations.DBusProperty +import org.freedesktop.dbus.annotations.DBusProperty.Access +import org.freedesktop.dbus.interfaces.DBusInterface +import org.freedesktop.dbus.DBusPath +import org.freedesktop.dbus.messages.DBusSignal + +@DBusInterfaceName("org.mpris.MediaPlayer2.Player") +@DBusProperty(name = "Metadata", type = Map::class, access = Access.READ) +@DBusProperty(name = "PlaybackStatus", type = String::class, access = Access.READ) +@DBusProperty(name = "LoopStatus", type = String::class) +@DBusProperty(name = "Volume", type = Double::class) +@DBusProperty(name = "Shuffle", type = Boolean::class) +@DBusProperty(name = "Position", type = Long::class, access = Access.READ) +@DBusProperty(name = "Rate", type = Double::class) +@DBusProperty(name = "MinimumRate", type = Double::class) +@DBusProperty(name = "MaximumRate", type = Double::class) +@DBusProperty(name = "CanControl", type = Boolean::class, access = Access.READ) +@DBusProperty(name = "CanPlay", type = Boolean::class, access = Access.READ) +@DBusProperty(name = "CanPause", type = Boolean::class, access = Access.READ) +@DBusProperty(name = "CanSeek", type = Boolean::class, access = Access.READ) +internal interface PlayerInterface: DBusInterface { + fun Next() + fun Previous() + fun Pause() + fun PlayPause() + fun Stop() + fun Play() + fun Seek(by_us: Long) + fun SetPosition(arg0: DBusPath, to_us: Long) + fun OpenUri(uri: String) + + class Seeked(path: String, position_ms: Long): DBusSignal(path, position_ms * 1000) +} diff --git a/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/SessionInterface.kt b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/SessionInterface.kt new file mode 100644 index 00000000..d985cfe9 --- /dev/null +++ b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/linux/SessionInterface.kt @@ -0,0 +1,101 @@ +package dev.toastbits.mediasession + +import dev.toastbits.mediasession.mpris.DBusVariant +import dev.toastbits.mediasession.mpris.MprisConstants +import dev.toastbits.mediasession.mpris.MprisProperties +import org.freedesktop.dbus.DBusPath +import org.freedesktop.dbus.connections.impl.DBusConnection +import org.freedesktop.dbus.interfaces.Properties +import org.freedesktop.dbus.types.Variant +import dev.toastbits.mediasession.mpris.MprisProperty +import dev.toastbits.mediasession.mpris.fromMprisLoopMode + +internal class SessionInterface( + override val session: MediaSession, + private val connection: DBusConnection +): MprisProperties(), MediaInterface, PlayerInterface, Properties { + override fun emitPropertyChange(property: MprisProperty, value: DBusVariant<*>?) { + connection.sendMessage( + Properties.PropertiesChanged( + objectPath, + property.getInterface().iface, + mapOf( + property.name to value + ), + emptyList() + ) + ) + } + + @Suppress("UNCHECKED_CAST") + override fun Get(interface_name: String, property_name: String): A? { + val property: MprisProperty = + MprisProperty.entries.firstOrNull { it.name == property_name } ?: return null + return getProperty(property) as A? + } + + override fun Set(interface_name: String, property_name: String, value: A) { + when (property_name) { + "LoopStatus" -> { + session.onSetLoop?.invoke((value as String).fromMprisLoopMode()) + } + "Shuffle" -> { + session.onSetShuffle?.invoke(value as Boolean) + } + "Rate" -> { + session.onSetRate?.invoke((value as Double).toFloat()) + } + "Volume" -> { + session.onSetVolume?.invoke((value as Double).toFloat()) + } + } + + val property: MprisProperty = + MprisProperty.entries.firstOrNull { it.name == property_name } ?: return + onPropertySet(property, Variant(value)) + } + + override fun GetAll(interface_name: String): Map> = + buildMap { + forEachProperty { key, value -> + put(key.name, value) + } + } + + override fun Raise() { + session.onRaise?.invoke() + } + override fun Quit() { + session.onQuit?.invoke() + } + override fun Next() { + session.onNext?.invoke() + } + override fun Previous() { + session.onPrevious?.invoke() + } + override fun Pause() { + session.onPause?.invoke() + } + override fun PlayPause() { + session.onPlayPause?.invoke() + } + override fun Stop() { + session.onStop?.invoke() + } + override fun Play() { + session.onPlay?.invoke() + } + override fun Seek(by_us: Long) { + session.onSeek?.invoke(by_us / 1000) + } + override fun SetPosition(arg0: DBusPath, to_us: Long) { + session.onSetPosition?.invoke(to_us / 1000) + } + override fun OpenUri(uri: String) { + session.onOpenUri?.invoke(uri) + } + + override fun getObjectPath(): String = + MprisConstants.OBJECT_PATH +} diff --git a/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/mpris/DBusVariant.jvm.kt b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/mpris/DBusVariant.jvm.kt new file mode 100644 index 00000000..b3477f7b --- /dev/null +++ b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/mpris/DBusVariant.jvm.kt @@ -0,0 +1,19 @@ +package dev.toastbits.mediasession.mpris + +import org.freedesktop.dbus.types.Variant + +actual typealias DBusVariant = Variant + +actual val DBusVariant.value: T get() = this.value + +actual fun createDBusVariant( + value: T, + signature: String +): DBusVariant = + Variant(value, signature) + +actual inline fun createDBusVariant(value: T): DBusVariant = + Variant(value) + +actual inline fun createDBusVariant(value: Array): DBusVariant>> = + Variant(Array(value.size) { Variant(value[it]) }) diff --git a/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/smtc/JniSMTCAdapter.kt b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/smtc/JniSMTCAdapter.kt new file mode 100644 index 00000000..7266a4a4 --- /dev/null +++ b/mediasession/src/jvmMain/kotlin/dev/toastbits/mediasession/smtc/JniSMTCAdapter.kt @@ -0,0 +1,218 @@ +package dev.toastbits.mediasession.smtc + +import com.sun.jna.Native +import com.sun.jna.Library +import com.sun.jna.win32.W32APIOptions +import com.sun.jna.Pointer +import com.sun.jna.Callback +import dev.toastbits.mediasession.MediaSessionLoopMode + +class JniSMTCAdapter: SMTCAdapter { + private lateinit var libsmtc: SMTCAdapterLibrary + + private var onPauseCallback: Callback? = null + private var onStopCallback: Callback? = null + private var onPlayCallback: Callback? = null + private var onNextCallback: Callback? = null + private var onPreviousCallback: Callback? = null + private var onSetPositionCallback: Callback? = null + private var onSetRateCallback: Callback? = null + private var onSetLoopCallback: Callback? = null + private var onSetShuffleCallback: Callback? = null + + override fun init(): Int { + libsmtc = + Native.loadLibrary( + "libSMTCAdapter.dll", + SMTCAdapterLibrary::class.java, + W32APIOptions.DEFAULT_OPTIONS + ) + + val result: Int = libsmtc.init() + if (result != 0) { + return result + } + + libsmtc.setMediaType(0) + return 0 + } + + override var onPause: (() -> Unit)? = null + set(value) { + field = value + + val callback = value?.let { Callback1(it) } + libsmtc.setOnPause(callback) + onPauseCallback = callback + } + override var onStop: (() -> Unit)? = null + set(value) { + field = value + + val callback = value?.let { Callback1(it) } + libsmtc.setOnStop(callback) + onStopCallback = callback + } + override var onPlay: (() -> Unit)? = null + set(value) { + field = value + + val callback = value?.let { Callback1(it) } + libsmtc.setOnPlay(callback) + onPlayCallback = callback + } + override var onNext: (() -> Unit)? = null + set(value) { + field = value + + val callback = value?.let { Callback1(it) } + libsmtc.setOnNext(callback) + onNextCallback = callback + } + override var onPrevious: (() -> Unit)? = null + set(value) { + field = value + + val callback = value?.let { Callback1(it) } + libsmtc.setOnPrevious(callback) + onPreviousCallback = callback + } + override var onSetPosition: ((to_ms: Long) -> Unit)? = null + set(value) { + field = value + + val callback = value?.let { CallbackSeek(it) } + libsmtc.setOnSeek(callback) + onSetPositionCallback = callback + } + override var onSetRate: ((rate: Float) -> Unit)? = null + set(value) { + field = value + + val callback = value?.let { CallbackRate(it) } + libsmtc.setOnRateChanged(callback) + onSetRateCallback = callback + } + override var onSetLoop: ((loop_mode: Int) -> Unit)? = null + set(value) { + field = value + + val callback = value?.let { CallbackLoop(it) } + libsmtc.setOnLoopChanged(callback) + onSetLoopCallback = callback + } + override var onSetShuffle: ((shuffle_mode: Boolean) -> Unit)? = null + set(value) { + field = value + + val callback = value?.let { CallbackShuffle(it) } + libsmtc.setOnShuffleChanged(callback) + onSetShuffleCallback = callback + } + + override fun revokeCallbacks() = libsmtc.revokeCallbacks() + + override fun getEnabled(): Boolean = libsmtc.getEnabled() + override fun setEnabled(enabled: Boolean) = libsmtc.setEnabled(enabled) + + override fun getNextEnabled(): Boolean = libsmtc.getNextEnabled() + override fun setNextEnabled(enabled: Boolean) { + libsmtc.setNextEnabled(enabled) + } + + override fun getPreviousEnabled(): Boolean = libsmtc.getPreviousEnabled() + override fun setPreviousEnabled(enabled: Boolean) = libsmtc.setPreviousEnabled(enabled) + + override fun getPlayEnabled(): Boolean = libsmtc.getPlayEnabled() + override fun setPlayEnabled(enabled: Boolean) = libsmtc.setPlayEnabled(enabled) + + override fun getPauseEnabled(): Boolean = libsmtc.getPauseEnabled() + override fun setPauseEnabled(enabled: Boolean) = libsmtc.setPauseEnabled(enabled) + + override fun getStopEnabled(): Boolean = libsmtc.getStopEnabled() + override fun setStopEnabled(enabled: Boolean) = libsmtc.setStopEnabled(enabled) + + override fun getRate(): Double = libsmtc.getRate() + override fun setRate(rate: Double) = libsmtc.setRate(rate) + + override fun getShuffle(): Boolean = libsmtc.getShuffle() + override fun setShuffle(shuffle: Boolean) = libsmtc.setShuffle(shuffle) + + override fun getLoop(): Int = libsmtc.getLoop() + override fun setLoop(loop: Int) = libsmtc.setLoop(loop) + + override fun getPlaybackState(): Int = libsmtc.getPlaybackState() + override fun setPlaybackState(state: Int) = libsmtc.setPlaybackState(state) + + override fun setTimelineProperties(start: Long, end: Long, seek_start: Long, seek_end: Long) = libsmtc.setTimelineProperties(start, end, seek_start, seek_end) + override fun setPosition(position: Long) = libsmtc.setPosition(position) + + override fun update() = libsmtc.update() + override fun reset() = libsmtc.reset() + + override fun getMediaType(): Int = libsmtc.getMediaType() + override fun setMediaType(type: Int) = libsmtc.setMediaType(type) + + override fun thumbnailLoaded(): Boolean = libsmtc.thumbnailLoaded() + override fun setThumbnail(path: String, update_when_loaded: Boolean) = libsmtc.setThumbnail(path, update_when_loaded) + + override fun getMusicTitle(): String = libsmtc.getMusicTitle() + override fun setMusicTitle(title: String) = libsmtc.setMusicTitle(title) + + override fun getMusicArtist(): String = libsmtc.getMusicArtist() + override fun setMusicArtist(artist: String) = libsmtc.setMusicArtist(artist) + + override fun getMusicAlbumTitle(): String = libsmtc.getMusicAlbumTitle() + override fun setMusicAlbumTitle(title: String) = libsmtc.setMusicAlbumTitle(title) + + override fun getMusicAlbumArtist(): String = libsmtc.getMusicAlbumArtist() + override fun setMusicAlbumArtist(artist: String) = libsmtc.setMusicAlbumArtist(artist) + + override fun getMusicGenresSize(): Int = libsmtc.getMusicGenresSize() + override fun getMusicGenreAt(index: Int): String = libsmtc.getMusicGenreAt(index) + override fun addMusicGenre(genre: String) = libsmtc.addMusicGenre(genre) + override fun clearMusicGenres() = libsmtc.clearMusicGenres() + + override fun getMusicAlbumTrackCount(): Int = libsmtc.getMusicAlbumTrackCount() + override fun setMusicAlbumTrackCount(count: Int) = libsmtc.setMusicAlbumTrackCount(count) + + override fun getMusicTrack(): Int = libsmtc.getMusicTrack() + override fun setMusicTrack(track: Int) = libsmtc.setMusicTrack(track) +} + +private interface SMTCAdapterLibrary: SMTCAdapter, Library { + fun setOnPause(callback: Callback1?) + fun setOnStop(callback: Callback1?) + fun setOnPlay(callback: Callback1?) + fun setOnNext(callback: Callback1?) + fun setOnPrevious(callback: Callback1?) + fun setOnSeek(callback: CallbackSeek?) + fun setOnRateChanged(callback: CallbackRate?) + fun setOnLoopChanged(callback: CallbackLoop?) + fun setOnShuffleChanged(callback: CallbackShuffle?) +} + +@Suppress("UNUSED_PARAMETER") +private class Callback1(val callback: () -> Unit): Callback { + fun callback(data: Int?) { callback.invoke() } +} + +@Suppress("UNUSED_PARAMETER") +private class CallbackSeek(val callback: (Long) -> Unit): Callback { + fun callback(value: Long, data: Int?) { callback.invoke(value) } +} + +@Suppress("UNUSED_PARAMETER") +private class CallbackRate(val callback: (Float) -> Unit): Callback { + fun callback(value: Float, data: Int?) { callback.invoke(value) } +} + +@Suppress("UNUSED_PARAMETER") +private class CallbackLoop(val callback: (Int) -> Unit): Callback { + fun callback(value: Int, data: Int?) { callback.invoke(value) } +} + +@Suppress("UNUSED_PARAMETER") +private class CallbackShuffle(val callback: (Boolean) -> Unit): Callback { + fun callback(value: Boolean, data: Int?) { callback.invoke(value) } +} diff --git a/mediasession/src/jvmMain/resources/win32-x86-64/libSMTCAdapter.dll b/mediasession/src/jvmMain/resources/win32-x86-64/libSMTCAdapter.dll new file mode 100755 index 0000000000000000000000000000000000000000..6755de607ae702d4d9106dbdd50f916c4ae02c0d GIT binary patch literal 3110810 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
  • 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 &Pe{ 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�ntiB9ZdBO*xW&CVDdS*em?7GHGuAh ze7OR#cz&&QGTQ2#KD1Qd-PCMca4#2(W(y;fG+Pur1iM>k7@FNJga@}n=ZdC-y@lXCW6Xd7Zg^f?K ze=Zz2M^&`F90uEpJ;g?9Z%XCo)JQGKmZ&9Jm{u*!GCajj(sJWd?BAH-^b`x<)kjo@ zNCW=Q9T%KcSvbHO=;?!{Jb5|=+IXf$5$FOQQXFquSQC0$81nlIQXhGWji5ZmY7_XU zSnk~Lu-ZUI9r-CX65PWkKc+@Xd@c?eo0FkYo?@LOvTo@q_Ipy1PR#W&0Z~xKo*xUf zJofxlyOZ-7x~?!x7-b^Jql|)alrc;gWef}oF?}>l<0$je4|Njxnn#(a;x&#kQNe2% zWuoxh*Eq`j(wg89vNih2e)6bcXj#uB@rM&0JnJQCK9+h2cDK+lG`m{}4{mpo=3kTD zEi??x?iRv>+f5zCqs(v3M-Fk6>G&6ouU4bXe~M}vWzG=pILbt7Z%XBHBvMPVC2C0) zwW=0o8Ah24wA?t#beiDSfef^XTLCuq&h+B zBcn_NWt7n-Xfet(kad)agz_j8DNRS2267x_9I0`XnIRRCQAR--WsVeTd6ZeC-D#tY zVZtaAK^|okjH8TU!YE^)jWUL59A(zZEGDbG=20f9c#We>RPY)`nJD~PN14m42@WA! zQ~uGPJZczP)>A?H*9i}v^^!CnOFaa;TWA=X-7SO%w>wGmugUHf8irk0X&9~2=XYSU>s!(6Gj;WZIm%g z<0!LF$GF!%%0v~fag>P)Uc)F8gn%nSa;6 zZIp@B-jvGYNTilzOVpAqYE>=FGK@0Aek3b(ag_PI89vY`{n?T7PK2)B`X`a9V80;l>GB}9c6tyOXeo$0l4uu)f=COrvC)GR%K5*VMHj+@ zF1rm)Uczt1lJ;enV==OX-&Wm9{9DWTi|{@P_&yYu=x@vJ`TQPiA3C~vVVN`>eL9@p z&|@P<&6%Vkiy>0f3bl1H=K|9HTeE&FwnvPeRhMgR6@Pdu&cm4#uZfj&6vufn`OeTm zb^p#DtFY4)4@(koJzY$#)C#BxxSsZ1cxnQ&tX8VR%|rYp3>;v(lur6X=FSzBReGuy zW<5(n<1RT59Js%Hoe0qDOzaF$5DArb*m@8*mwOQRmbU~iq&v$!H33m>t+>P)ipEv2 zo$PKikj%h0U6e{7{p34DI;+e;bj+AjyN5RRB!nXn8b|OuI@*I+FLqgh{S+D=L}+*r zp`qZg5E=?fXn2rI-DBop2UB*HUAmh`!WD>(tFDpQfT59sFE?YW^vX4;$eKH=1dwMa z9h>KI2_*VDMR3F@3Tc#k^!Z>wDEzDNODHHPq2NJ;f(H=_Ex|qt1y4;tz9RHLot+2;1>4E&HUlLT zl&Yb?ZePi}EWOQ!AI;taZ`*U1?CHI$`i?5#ci_!E#v2_(yhMLCn`t3ZjH94W_`o%~^p!*#G&YxwmTEc$lpU=i`X=rG{*9_&0)XiImt7J&zH z+)v;b)}Z!l`nc{{-zET?c7}%FDDsN>vurjp5Qaa+&eqUTL{`a=S+XO*hQLwm5f(Tb zttM&UC_V;`V#n`O1W-`QB?CG=8r@`;z~h@D-R;BJczhz3Gxu*sfeox@(}YGXDH%IK^bBcl(_OB z#=*Ux)k%Fn4T}-P7Z*I(pMu!(m=*g{*$A?11X(tM;d~88NzY&EOC0__K|fr!e-m65 zTX8nMF^(aTZ6bnx;#BzM)Hk{ikMyx1jU|%o$HxG4yp@p;!`ChNHuQYNup8vh5#zBn z3EPtUkI;>_km)s@KInj%#lh`p$&Sgv$@;0gvUUIB+H z;V1Cs(ZwrycP7zFQtPr&6YAGb-k&XA8K&Obq)1!<4&Fk+PFBF;io%I5Odg9)633PW zV~r@ght_RjGCB*E))*mQBeZW z@WaA`X#al^bB71diXdGR8>y;IEzgw^C(dSEU%~#M?!Ed`-BoeG0vvxC-F4k|d}ClB zp3#TRY~Vrnq>2ukF&nkQH)=}C8W?AE&k;sU1$?n(PO9Y3wF&YAm5r|3>pt3)xUc$PV=zR4MR1`-{BaDD!k)xiF)!-Hyy!u{57Cvd2=2Csco7S?a^SFj z9}8alrfkt}Oj34}1Onn#sHNFT@fI~%1K}h^RK_{i&#rq{)qzsu+%d!0>-ON+o_9rq z6Z*RBy1S?$?y9~^8Ul$;70}m$kbM*sr}rRiJzq>48Js?X;cnzy5je4ia`KVf{5-pM zq#^kIn66s~@q4a+zS){8gI{H%LW^!!Y<}Rk9}~ZHxW7}}8-;I7oHi;7CkOLeD>sri z;-fP-%`-+o>an+qocN`J;+J;_RsAyGc0e;S-^>5{&?un;9g)8~cq!(oml6l3-{K(^ z4T}of1;l0TG$tuXeTA;`AZgF9Rq4ubxW(}CJpCAQnOP z+TRzdw1I-s1|JlvwZTi8?JGPgeYznK+2F{A|;6JE-KoB3O zK0}CSs;tZnS0z;|5vD6y3*z*SDmKgZW3w!40pbJI#|ZIQ(BfjZq@^GJ6>LH5-ckL% zD&XG>T7dXK^#CCb1TA5JJMpp70QVbf1FRs%0q&~Xo5i(bM&DiaHmd5|s<(%_lWn+% zJ?dkZ%Akq=s8q8;iL}T_!&w6c^VAX#EB&1*9pR0M5j zx=0y*a?i3UyX~#iOm77X%%a04l|mVYRX5Qnt6&`F}mm|)e~l7gF4a1*Cew5kfw zB;tn3o0A&dQl3N{*0pncJ}}Q=-QBzC6Ltf$kufvW)cnByrzLwh+#3W38;`VBFEHzP zvkdGqP()$}M*ST`q*IJk!l@!BnMFa#EGvX+nPsgxEN7OtNeDob{N#~iDMD!}9S=l4 z*?uE{8{v=r@JE(%Da6U}r?5x{$zuD};J%ufU8Zo%r@*^U3Fi;9?Qa6`CT0!^;oWMf z4)lGBs3fs=>8&pEv=)`zal`UUuf?xxZ-G6xgz1XbHnL$@2;axRjQ3Jv(iv}wWtfIb zp#&OG5Q`o}$6PAr%HSVCp79!}+SKw~DMOm^jvsu+JHC4eGv4kw8h+{Yxgk#H%s+$~ z@3q=v$WBP93RWn0r6AhtdSM=(ANc;KB~%>l&lL9-;lr6OZnInw$baQ2@jb#T!J(i8 z$NfUJ;5evbeHeDvhrXdw@<`$%z(fjjj#pJi&3RD~751p%K?&v`N+5SZCift!|9ufd z^_Tp)RNvudDekAj*N&G`5Q`vZgZ~n$R9Hc&@b83b6@F>I&b9dD^aWZgZrDm5$%_#= zi&ub*uwKyzppSTcR7c*K2;`Z>i;;fC=>SeAUVVW1krjz3p7C{kZe6;@L816c?s!q; z(%0b^AAYuS9S2``MNgwhR!imP9W=VC0GS$H!8$;MtOu!=^Iw^DBVqW+ZZq7IrooPB zL?S!#LS+a;KH)HU*1{)Rl_6Z+la~?0+9O9$(cTd;h?NBFPr$(6Aq|GIwJgdW^i@9p zP`bN3)QzOT3KbKV`OZCP|1;< z5i`7=spnKY9?w}G41%59I<94=@x@5EJqWjMbn!(lfra=STYVun;8<7J-Ia;s9k_5_ zwK8$sb(JG{y{|rUQD~|mON^YjxKRR4F+d>`ryUoY+85MZjCg}}(`KCDntq6q=q0RJ zQtP;OnTRaQ6`nW)nQYA9ts7YW|JJ32t7`BW?w8QI)H*F&7a4xMByIV4SOobMvx2hz z??Jp>m?W|x^C15zf-m|BJ-6fiItK7|0QqJz)8tovo3XSZE4H=QcVk0A`vm+w0XKGJ z)4S?Bs{6C9*%$(Ok5WnHD8wNqQ^>bUKuPqt;z7K{{-6|7$q&5p7d!-dT$uL;o4ekp zHZR1iZQ+ff0JS-fuOMqBYG_Jp-RJN6VT!k@ApZ}!&{KL$JS z^XreJq(s^a5`Xg3%#d-&Cgb=63D{_n9eqp?KV}2JPYM#bD#sX-f7Z0LinFg4<&t@4UFz;UqYIJYK~ka`12o(vLFe<0C_SuYChXf!5Xba)W& zL_G+fQE*|~PI~5o8=(~xnN=2|-4|}<)@~rxc2=?zw_>@fK?Y7kGH6)onu%7!5%A~{ z6KgkY&!)~mdtxt-7KXR~^fFUHa^hhrh_>qyMQu5MCzIRLa8d=&1{?V&@A-Z8Y5uri zPc+YwaMwdNz6ZlxojO`YAW*ClaaqC0sOOcA8E2W^Ml< z259p=Y0byWe>32kGvuv-u;8COa`DG#FNXhL99HkL*CQ^o8`HNDo*ggauu&;HnmA}Nm?$Ps%KC zXMBcSwwF0kR1SFAUiRd}>U?t%;B&7F)nZki)Eh_|;GTpRT_nYQQ~a5z8$|(LF)#;+ zK}e|tauHv#(y`|Tj|*{uIZ`^|`JLE$lbyanIxLUGrKrs58)N@R3g7j#l&h1SzByE@ zo};&v1xd2_zLf8~Y5+R-(*Sg?(?P4#Fnr#a=Sx%O4ZG2Il4C)&r_7UT=N`X-@X|@I zJfNM#BlRlRNs%-Y?KG1}=m{QAN~F`3R49Qk=+P207eOS;k=$2RehK(nv&2sb#$iG* zZXC}A#?6G=&E(gg3-Q;Y<2Yr(ceT(Ai|p8ZC{qK>1(ZNp6*<>~NS9+C3{@YMA87k) zc{+|bH#Ok4?=ka6qL7jE;T`iA`mAHfaG8$^w2S zCXJepPOxG}L&xYbF|lkj3SXB*&%;<{y5M@$Rey5N)0-W&5k+ha59GwCvyOzVKRYTs zh7$+9vDcr3Yg2aA2DHG2@D!e1umix3>+xLK>?gn0OdXA28$G^$J?47jd17;Te$)1R z32taO9Q$~(*>mgHW708~rcA5(Hjc|`H*L=@dwm-=Kwz9kuWc7y`T|Bk3`dj(lu96& z9~A0n$!Q6WLm;wfI(x~CZTRqQB-POw`r%uuavn(?2p0ZaE(c4Y1V~_jO+a|4r?Q@X z4{1Ekfpg7S8G|K{B))f-NajwCI0@xXnvVdPPaNV$u<$X{%-f@Qf5_sVi>SM3M;3KV(u|?{S#pPghfk(fo^I23jY8Z|=6EAKL=Z%(~*gu0-u{|^S3dsVv+Zc5l zJr`MX?weu-sZ*dYa`7x<3*2449-M8%EIYfBL}G7vcFZys!krfqheq`|u24kv_&Ca3 zWE8$%if0+;iEF}+NalIQIZ%AgT5bw9(~~jTI2V?kyEd^ba`06D1g>eel9In465w0_GPwEXM zY4n7IeZ|(=#e7jmt99^91t}hpFg(ajc&wFvB3f@%oB*JXkw&|tMxBV(Uxk@Nwqdh$ zLB7PKPeHCd3->FLQv5gye+N9oPgNgqd9*)ciBZFc%pI8Rk4=%VBP=6`Krm`9Z@U3tu>vc7I zYfr)hIFUH=if@TJ+lZS#4$`q{>pp;4q4 zmi6LN=ssKu&60S}aG?4T+Vk`gNZKPyQqH+ZJ?AnvmXf%fel5oa`$V7@Q|Ml74A_8; z0W61iC@~@bD7n)@jUxm6Hk+Xaaz*}zDi`E=pA6C3>!8r}Al;I~IX+4eIpjk0Ie@lg~ zrv|C`py;OUQwVAWX$puC@*rz^!AzNyA9&|?5D?j^E9Ea@ke@vA)$y>!Q;0gRN7X(*lkFp6qDa~K91srzp6S2GF z#P9%>oqjEVYj?zq7z}qJe7om~u!Tk7Tc=+I;3^WasY(z^N}7fG&02}hS;;$xH{!32 zeXy;MZ`mVh=Vv7=PlT^=Y4EYr&xO#reZcfxi@(@ZePj0ntJj3#_5b>+zOn1Q+CgGk z?s&@fn9HbFH0(f5BD=8&B-0e4F zx@>@nAU*szYETXw|K!RSUcv89UZU+PzeF2L`$Y!k89S}}&AeY^V76&9W;J6&AG&u= z*ynoW5>c0Kl_^`cM`1jfga(O3I;o5&7JD4^6g+B=Vt*wLep(o7 z;m(`06{P7M{p5$8~7BU=Y5-9dM1pC8V>1mh!}4+Id&f-OJM%4kQ3wx7zx?2PjxlVdp* zs?UIa){EMmV8Wb9DEXeXCK`68k*6ZQrXG*c0vAN1tE$BsA<>xujw3~6l?fLM~YldigoJqVJn!|wq;BB{}Ajn9v%w?4+}Z*^Fpn2Ai~ol9dGRG2BKPD z`k6G9vH@sdbfw@FoewL52I5WGFv@eGqmLDh zb;KnSw+As{_aGZ^f*F8BwLNQ}66Rrwp2Dlng@Pe@p@|eRLD~#`s;I0o1wK1vYUpmX zpAyhkkNra0&*2_q9NN!t!PPv7_A^kmkookJ_k2qeSPu%u34fj#EI9+f8RrHo0%rqv zZ{Wt{eaSM+7?z!bk^dYL(RHt=dOAqciDWZ|QpuZwE=ssL0_JVqg+zo)a7+q1`jAI&#nsy>`%!$Ndk9AH5?<_nCYYZoaYtF^s zgadhs){{>l4)uubW`*P_eWh(RR!s+t^HwsUH>jY->v_Qpr6&>m07ACpd?;~$LX;3I34yBt+60No~k}r z1L8rH5)jYSenE&|)SjpZ#1r+eV%uu=)%x>-c)tE0g!m7X${ghqOK={mx(YqJYRlZ+ zmFp{q`JHdOnr-LXpKZBm9v?; zU;j>h$iDF1`pb2A&&%~+kr%pbUGg@7&f#VuWzTwq77HH<9*;c!RP~<-@lSAkXaeHF z+EXFKQ;5H{&|L zNU;NMe}r2>IBI`3@91uraBG5Y!gkYps)MR}PxXN+b{aeVO%(mG`s$#Xv?BO7-)(iN zV_BPDDq_<{UzWGyYOYNTx1S35AF4kOE3d_UB7yY8it-PbiYZv-{PXHxSM?T*NTGxN zOp)aXPgxfzyo~W7(bdT+3I*w#!I->6kk>R7BxVd_3KDYyvgZE351oc(6;c&Jz6*?2 zw%YPG;lW)Q^|kjES$0aj5C6R38vf_K_{JdpLDvAiD0a%7MtIUIVX%aj<%-KHo#FPP zPP*p_L#z@O!*b$uh`vItpjTdSJkOpV_};5D5u^WGXeUxaoQ&~fGG8~e7(EP67Q3@kWK_a}43fe#gZ3qQP^mLlfO@gUgE`y<5u*dfi-fobI6dI)KlY$jnE(?XT z@K{qi9!mRhky%jc@qL7s<#vz9$6mID1)j;GWfY4S5#)om5#)u#2y!q+u#bWGtMSKTDta2no#pb;+&jR^m$7js10G<5|$yC@E0MF^Stutr>4 z6C4hWG{~|?)N{pWm5KG6w(qB#OV(vmr_tY)_KJxOueblMgt%!JzARnlND#!qKlOa6 zbza8BWPJZeI~T(%jgBF|X-D2YWq+38KvqG2Rd|hlP!2Sy<7p@cw@N)ER*cXFM58Ls zH);b8jXEHXI<*Ol$5Gm-6_1GpFCrcRpw*xs!B9&A%|H+jpt1Ozq({49EC_h&&I-B00V1_#Y&)MhAY~aCk%t_$A$a!mK z+4R~`8u-?%pJlV&f%xvIK2#mtw1=u+tadlkSFYz5tIt)3R`5G&kJpBz^6}b-YeQ4{ zziaQT4@u=8)}O2o)@9u3`S;D`I-SR6@2NlB;K803sh?Oh`8zE`o|M0*{{A7);Ke}` z3)aDc2Pi-uAVX-#D}E`J85wea1mWa$qtfY*1jIm5EnXE_x*E@eILEzPPE=HMdRpI! zv>bl&(Kbb0f<}3w`Yh%H*|XIz`3nI&Pak-`NY4;o1P{hldHN@9PFFtk2f)1j5FTFJ zM;zM2>xj{Z^c>gNC{vK+rqJ>mW;IWQ8rzylQe)l>xCAqpC#s*P^^DJhnU`j{W#(8b zPeb$WtA31H`D4{5eJgvOKJa{$TKS{k@vWRkE}r5{3#RQ0Y-U=1q!lx~w1k#PPhKa>q~8&fC)Vzn2M;L>=Cx{+ z{esnL40Mj6Z%$jNj-E^w0T8}@p*=+Jp(Xs7P?X6Sdr+ zPn!KW$ehjK^JRAYwNNTn?ZI694PA9&*^smMW*ph-LY*Mm_1 zw?dVQgxA!9#IYZ7 z{l(S(?BZ>*?If>!Lkpsz^q3V?&sy;>Qan8=z-kX7R(>K>wDu=PhkC-~sA)QigC^Ni z>+rQD`$52OHq=Yt>Q|!lG*ZkeshpFVh=$02wBlF{%k-?2#Q1CEnKz{gE z4Bp=kyS{>B6u2R1*l=9BM1Ro}pE3-~56mA&J>_vK0(3E53?(-Oj|V}Wsnw7h-=!<; zpZukR>P4rZMNYX4op#yfNFtZtiobB}-`Z>s-UO|~o1h11s`da~Q??G*lsyW_w1htr zUA@^UXTX`x*aP3iq+t)Xv-V=51|=Jz1WWC`SZaq7-9y!&Vkw+`>7|ICOX-SDxc}w1 z0Jw$p0oVgkngY>}E0;f&{sv6V-tefOnf=HrIeB>f{pMCTzZooKZ+<(O z`u6I(qp1nUy6on+Qj5HmIJoasUWO+HWq9&nC^4^mQ@eoPB%axaUpKxD^Gh*^0@OGr zqg5uKgoXgydSzo95NuqpG}u)82TjP}vbJ}Gq5nPI;iUmVrT7e^%> z~Ay@)Uf7 z{dn}*@zm5hdGhp)+03OOxhxQUoncxZK{Bd{Am^3{@)JS?dDMy^A3iu!G>^^?jF=?j zoZ(zo=LcLkvj}o@6Soxgxc2yaD|R%T!JWvA?i>EfD}SRw z!-MLblvnPg+Yi^`_QL}N9ia5GHhZ-C8H&%(RKHUL@;kNf*8qLL_G5y6T>A|{zp3p- z-~*N$Tb5Z|R@kR^L{` zxn;bi!I*My?a>-=Uxxwshf7!IY-mW{QborFvfI&+P>mOucjIjp5FZSM1Mz4T6<|O7 zl=Q$KC8_FJ>lZqQGkrOJt+^P#-cfxR-4NPzf~DgV&^V{9mIld>F+5++F$HUkH^@1J z9wFfkAaoQFczR5MneHfN*yWHgHM_;rNmx=j;iXwyFxFa%2Rbpa;?Ur-RAH6Z2)vh4H1OjqE8s-V1%jUZP+L1IP~6qMJo z5hTrMw0{%bS%r=~UwkDOQKC#h8o2mU{JQkYd}l>2O-W>Fn*9+zO~mQmrx z#3`W#hQid*(ET#)-dTs;JFg-SlBp_>wK4R1qi3YGn7?YRcy za>t7fk{1hpqshB`kGY0nT4%m8L_-C$5gZ7AbXPiTXmMXOKgt^M(|7L^Sr9X$zWO2U1Gp&28I` zwVmpzaC=ZkMdgZvG-A6Tl1@E637>Z$lC0q72W=rD9L`r#p#(meTQ?AE0t;r-oDdt? zmBnz_$Zj~K1Z>IoAU3iam>-zPTF7$!$yeCCj`xw!r_#34P6I4GU$zs#&Km-71D>V0 z%TxdrRweSU({(&z?f5Nl!Y!BLuS-cGrbn0U0A>d%1V9Rd+Xrzc-GjB_9{LE|MZ}A# z%?*3$_U64+h-gHJ&?RO*ZDKn0G#a*71|eKW)E8E0pkcjkg@`uit4($hj9j!H#$g?( zKij+|Sh!_7Fx%f4fH&^eAdO-RXpeRF#$C46T9rH1oEP}g5YmcP-NF|qkX8WD8a&-3 zx0_MN!~EkMV+Yx&V_-fOQrwz9@?y4{6rlZ~ zK(bC0qeUj*{e;^oJr?e8?8(9Klg|clHmO1_ARxg@{KdTlZan9NV_bVvvtg&SU?&pJlTOR@=vp@74P5gkHT z!H63;wn^xcNi;72S^Fh_mlZb_#f{~!?9eu$_--X~V78WZ@;7*iLmzJ=eGCd1gH=Bs zv(Q$N(x970wS=QVH;;Q!gXu>4d`xUE@&kV`Pabd$hpJ(%SA!Y3ZSLH_g5p-R$d(DI zPUo}hquSTLa$m8tz%P2atdh`o?|jLh4LE6X_fNj3PQ{h>+@VnL0zC=75EQ_ZSb_Qyn(60OA3EiD*6Z=>s_XFU zx|?yFThEifEKIaGhVJ@L0Wp(EK{Cv!G$#dE{d#CHnIJ@vnH40>2vG%zIRSaLt5h*# zi)fZFyhae^){9P|Qt+d{J&565!OUDG)cRF6g zJZT!Jd0?#~vdSFPapzl5!v_3D!f&MJRPGUSJ6D;NhOM1%(P1l5&tFZ$O@X9KX1LFp zrMToQ+lna%w_)z_8wpQN0%ZFE4ULHYqTm>b(6(nZ(Vg%W76_oIvDIqZ;I6?%;t;0|LipFDQ0c=`u z6wJk(%%jc#a0ZFU09g`Pfaxcf zi~;?q#Q+u)Z+M&jTd@zH!;`jTB1@W7sX~K^pyYMbHIn zAsu9RnRy*;BdZm^lQQ_ce+t3^r49@1^&+<_>pH0R6 z@5(`q(pfQ(I1?CT3`2*Dy)^D5V0co@S6Jt!&%NlHNNPTl)6U5c{PZN9$(3Uv=E97iM`q_Js zPWQZ9W@YcL;);$=z==n@-2Hea#kmFvu@AJk}Hc!N%j8@4!1b1GQq2nbxRB z(&7{tbvTLCDKtEQLgPdD@#UcEH{XU|w?UNiv-x;V^WgLS_k~I$YIQjRvhM4fYFosyPaHoZ$fk-YK z92G6|l)jCGI7ft%B_b4*i0~jHqEGCVh}bG19)QtYMFioCH3KWP>yUXJOx)Gt5~mAy zx%d_+=DYfDj)49I*%lJ2T>0w}LMI_Dn9#c&3 z%Ff1~0z#+wjf9sNj{G!*1fC=6PSGOo$|A_^qo7P76_j^n5#-QSP^OUI5Y26&sUyhc z?h~qnsRz+7_X$jl6R|3QFaX?W6cHV zzzZIdAbp51WBq;P+V6*dHRVU4>Jmi>mDvizhDE32y(gqCFRmW zBO1%2WBG+bC&%76^z>cbekO$TM3pMdc#%S%yCu3FWM-IBgVD z1uL=ZLAam?5xc5FVz-hXXF79Ow34?^(BN~j;4%Lz-V*-~gHJ0WnVBy)jaVwmR<2R& zBc3L~z{?Yc$FCziN75Ohr3gcWg0fK~f}F1uBxa;91!bc~1X=UlqFJ`LM3AZH3snXm z55k?-3RMOl12uvbVG+DV1j>Vt;oaa<&97GOO2HF?f7aOAgt76F)#dLqZQTf=?lU?07atbv@zlGxHBLZsVdNhVBL8?0{;sk%R(;%xXc((1 z#TSIgQ`%bbtd+!@ffdBtkmq};T@YS|b2Gc*YfcS#JP059hEU~G$p)$qDZ+f{+agdN z1q=@#qK{99F2V17g-B!-UcKx_WOwuJJ@e4^{!CJ2q_Z63jzISvF&BT$B}TUrm!EAK z@t!*y&WF>cmK658~y+v%-v`_*)M2Lc(yIc&HKcuIDs%=+wd4m*CeWSJv*q zoV`-a78;d@&h;Ry@nGnWyvLN`7RGE`4}P6UFJI$N!h-WMd~}62a$?Q kEMyNL>? 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
    VYCypxM=h!~Ns|eBS|{aF4AHQHRps&) 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 z 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 z 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≶mZd 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
  • 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 &Pe{ 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�ntiB9ZdBO*xW&CVDdS*em?7GHGuAh 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 kEMyNL>? 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
    VYCypxM=h!~Ns|eBS|{aF4AHQHRps&) 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 z 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 z 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≶mZd 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 Date: Thu, 27 Aug 2026 09:16:07 -0500 Subject: [PATCH 63/69] cleanup: remove jvm crashlog --- .gitignore | 2 + desktop/hs_err_pid1041994.log | 7724 --------------------------------- 2 files changed, 2 insertions(+), 7724 deletions(-) delete mode 100644 desktop/hs_err_pid1041994.log diff --git a/.gitignore b/.gitignore index 4ad57411..ee1b2912 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ keystore.properties *.jks *.keystore .aider* + +hs_err_pid*.log \ No newline at end of file diff --git a/desktop/hs_err_pid1041994.log b/desktop/hs_err_pid1041994.log deleted file mode 100644 index 712f7c18..00000000 --- a/desktop/hs_err_pid1041994.log +++ /dev/null @@ -1,7724 +0,0 @@ -# -# A fatal error has been detected by the Java Runtime Environment: -# -# SIGSEGV (0xb) at pc=0x00007f78dd91f63e, pid=1041994, tid=1042033 -# -# JRE version: OpenJDK Runtime Environment (25.0.2) (build 25.0.2+-15348964-b329.117) -# Java VM: OpenJDK 64-Bit Server VM (25.0.2+-15348964-b329.117, mixed mode, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64) -# Problematic frame: -# C [libc.so.6+0x11f63e] __tfind+0x4e -# -# Core dump will be written. Default location: Core dumps may be processed with "/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h %d %F %I" (or dumping to /home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/desktop/core.1041994) -# -# If you would like to submit a bug report, please visit: -# https://youtrack.jetbrains.com/issues/JBR -# The crash happened outside the Java Virtual Machine in native code. -# See problematic frame for where to report the bug. -# - ---------------- S U M M A R Y ------------ - -Command Line: -Dcompose.application.configure.swing.globals=true -Dcompose.application.resources.dir=/home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/desktop/build/compose/tmp/prepareAppResources -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -Dawt.toolkit.name=XToolkit capital.yuri.yuriplayer.desktop.MainKt - -Host: AMD Ryzen 9 7900X 12-Core Processor, 24 cores, 61G, Arch Linux -Time: Fri Aug 21 11:45:59 2026 CDT elapsed time: 2047.124775 seconds (0d 0h 34m 7s) - ---------------- T H R E A D --------------- - -Current thread (0x00007f78d48e9190): JavaThread "AWT-EventQueue-0" [_thread_in_native, id=1042033, stack(0x00007f784d0ea000,0x00007f784d1ea000) (1024K)] - -Stack: [0x00007f784d0ea000,0x00007f784d1ea000], sp=0x00007f784d1e6900, free space=1010k -Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) -C [libc.so.6+0x11f63e] __tfind+0x4e -Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) -j uk.co.caprica.vlcj.binding.lib.LibVlc.libvlc_media_player_set_equalizer(Luk/co/caprica/vlcj/binding/internal/libvlc_media_player_t;Luk/co/caprica/vlcj/binding/internal/libvlc_equalizer_t;)I+0 -j uk.co.caprica.vlcj.player.base.AudioApi.applyEqualizer()V+74 -j uk.co.caprica.vlcj.player.base.AudioApi.setEqualizer(Luk/co/caprica/vlcj/player/base/Equalizer;)V+55 -j uk.co.caprica.vlcj.player.base.AudioApi.release()V+2 -j uk.co.caprica.vlcj.player.base.MediaPlayer.release()V+15 -j capital.yuri.yuriplayer.desktop.player.VlcjPlaybackEngine.release()V+18 -j capital.yuri.yuriplayer.core.player.PlayerSession.release()V+20 -j capital.yuri.yuriplayer.desktop.DesktopSession.release()V+31 -j capital.yuri.yuriplayer.desktop.ComposableSingletons$MainKt$lambda__744149029$lambda$0$3$0$0$$inlined$onDispose$1.dispose()V+6 -J 8046 c1 androidx.compose.runtime.DisposableEffectImpl.onForgotten()V (23 bytes) @ 0x00007f78c5b2bf34 [0x00007f78c5b2be00+0x0000000000000134] -j androidx.compose.runtime.internal.RememberEventDispatcher.dispatchRememberObservers()V+147 -j androidx.compose.runtime.CompositionImpl.dispose()V+231 -j androidx.compose.ui.scene.BaseComposeScene.close()V+45 -j androidx.compose.ui.scene.CanvasLayersComposeSceneImpl.close()V+196 -j androidx.compose.ui.scene.ComposeSceneMediator.dispose()V+90 -j androidx.compose.ui.scene.ComposeContainer.dispose()V+35 -j androidx.compose.ui.awt.ComposeWindowPanel.dispose()V+16 -j androidx.compose.ui.awt.ComposeWindow.dispose()V+8 -j androidx.compose.ui.awt.SwingWindow_desktopKt.SwingWindow$lambda$24$0(Landroidx/compose/ui/awt/SwingWindow_desktopKt$SwingWindow$listeners$1$1;Landroidx/compose/ui/awt/ComposeWindow;)Lkotlin/Unit;+16 -j androidx.compose.ui.awt.SwingWindow_desktopKt$$Lambda+0x00000000212c4cf8.invoke(Ljava/lang/Object;)Ljava/lang/Object;+8 -j androidx.compose.ui.awt.SwingWindow_desktopKt.SwingWindow$lambda$6$0(Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/awt/ComposeWindow;)Lkotlin/Unit;+2 -j androidx.compose.ui.awt.SwingWindow_desktopKt$$Lambda+0x00000000212cf160.invoke(Ljava/lang/Object;)Ljava/lang/Object;+8 -j androidx.compose.ui.awt.AwtWindow_desktopKt$AwtWindow$lambda$2$0$$inlined$onDispose$1.dispose()V+13 -J 8046 c1 androidx.compose.runtime.DisposableEffectImpl.onForgotten()V (23 bytes) @ 0x00007f78c5b2bf34 [0x00007f78c5b2be00+0x0000000000000134] -j androidx.compose.runtime.internal.RememberEventDispatcher.dispatchRememberObservers()V+147 -j androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Landroidx/compose/runtime/Changes;)V+244 -j androidx.compose.runtime.CompositionImpl.applyChanges()V+41 -J 7325 c1 androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend$lambda$2(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit; (2048 bytes) @ 0x00007f78c5a46afc [0x00007f78c5a42200+0x00000000000048fc] -J 7324 c1 androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$$Lambda+0x000000002159aaf0.invoke(Ljava/lang/Object;)Ljava/lang/Object; (47 bytes) @ 0x00007f78c5a40dec [0x00007f78c5a40c00+0x00000000000001ec] -j androidx.compose.ui.window.YieldFrameClock.withFrameNanos(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;+140 -j androidx.compose.ui.window.YieldFrameClock$withFrameNanos$1.invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;+25 -J 7303 c2 kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(Ljava/lang/Object;)V (124 bytes) @ 0x00007f78cce3f158 [0x00007f78cce3f100+0x0000000000000058] -J 8141 c2 kotlinx.coroutines.DispatchedTask.run()V (400 bytes) @ 0x00007f78ccec49f8 [0x00007f78ccec4600+0x00000000000003f8] -J 7683 c2 java.awt.EventQueue.dispatchEvent(Ljava/awt/AWTEvent;)V java.desktop@25.0.2 (48 bytes) @ 0x00007f78cce77404 [0x00007f78cce76f80+0x0000000000000484] -J 6728 c2 java.awt.EventDispatchThread.pumpOneEventForFilters(I)V java.desktop@25.0.2 (106 bytes) @ 0x00007f78cce0c8bc [0x00007f78cce0c480+0x000000000000043c] -j java.awt.EventDispatchThread.pumpEventsForFilter(ILjava/awt/Conditional;Ljava/awt/EventFilter;)V+35 java.desktop@25.0.2 -j java.awt.EventDispatchThread.pumpEventsForHierarchy(ILjava/awt/Conditional;Ljava/awt/Component;)V+11 java.desktop@25.0.2 -j java.awt.EventDispatchThread.pumpEvents(ILjava/awt/Conditional;)V+4 java.desktop@25.0.2 -j java.awt.EventDispatchThread.pumpEvents(Ljava/awt/Conditional;)V+3 java.desktop@25.0.2 -j java.awt.EventDispatchThread.run()V+18 java.desktop@25.0.2 -v ~StubRoutines::call_stub 0x00007f78cc45ffa6 - -siginfo: si_signo: 11 (SIGSEGV), si_code: 128 (SI_KERNEL), si_addr: 0x0000000000000000 - -Registers: -RAX=0x0000000000000016, RBX=0xde57ad31db6faf84, RCX=0x00007f784fbd56d1, RDX=0x00007f7847893400 -RSP=0x00007f784d1e6900, RBP=0x00007f784d1e6920, RSI=0x00007f7868460548, RDI=0x00007f784d1e6950 -R8 =0x00007f7890e1f150, R9 =0x0000000000000000, R10=0x0000000000000000, R11=0x00007f784fbcc950 -R12=0x00007f784d1e6950, R13=0x00007f7847893400, R14=0x0000000000000002, R15=0x00007f7868460600 -RIP=0x00007f78dd91f63e, EFLAGS=0x0000000000010286, CSGSFS=0x002b000000000033, ERR=0x0000000000000000 - TRAPNO=0x000000000000000d - -XMM[0]=0x0000000000000000 0x000000045e6312b0 -XMM[1]=0x0000000000000000 0x0000000000000064 -XMM[2]=0x0000000000000000 0x0000000000000000 -XMM[3]=0x0000000000000000 0x4000000000000000 -XMM[4]=0x0000000000000000 0x0000000000000000 -XMM[5]=0x0000000000000000 0x0000000000000000 -XMM[6]=0x0000000000000000 0x0000000000000000 -XMM[7]=0x0000000000000000 0x0000000000000000 -XMM[8]=0x0000000000000000 0x00ffffffffffffff -XMM[9]=0x0000000000000000 0x0000ffffffffffff -XMM[10]=0x0000000000000000 0x000000002a8d668d -XMM[11]=0x0000000000000000 0x58d3578f61eae532 -XMM[12]=0x0000000000000000 0x0000000009178da3 -XMM[13]=0x0000000000000000 0x3089705f3089705f -XMM[14]=0x0000000000000000 0x449ec000449ec000 -XMM[15]=0x0000000000000000 0x3f0000003f000000 - MXCSR=0x00001fa3 - - -Register to memory mapping: - -RAX=0x0000000000000016 is an unknown value -RBX=0xde57ad31db6faf84 is an unknown value -RCX=0x00007f784fbd56d1: in /usr/lib64/libvlc.so at 0x00007f784fbb4000 -RDX=0x00007f7847893400: in /usr/lib/libvlccore.so.9 at 0x00007f78477de000 -RSP=0x00007f784d1e6900 is pointing into the stack for thread: 0x00007f78d48e9190 -RBP=0x00007f784d1e6920 is pointing into the stack for thread: 0x00007f78d48e9190 -RSI=0x00007f7868460548 points into unknown readable memory: 0xde57ad31db6faf85 | 85 af 6f db 31 ad 57 de -RDI=0x00007f784d1e6950 is pointing into the stack for thread: 0x00007f78d48e9190 -R8 ={method} {0x00007f7890e1f150} 'removeKnownCompositionLocked' '(Landroidx/compose/runtime/ControlledComposition;)V' in 'androidx/compose/runtime/Recomposer' -R9 =0x0 is null -R10=0x0 is null -R11=0x00007f784fbcc950: libvlc_media_player_set_equalizer+0x0000000000000000 in /usr/lib64/libvlc.so at 0x00007f784fbb4000 -R12=0x00007f784d1e6950 is pointing into the stack for thread: 0x00007f78d48e9190 -R13=0x00007f7847893400: in /usr/lib/libvlccore.so.9 at 0x00007f78477de000 -R14=0x0000000000000002 is an unknown value -R15=0x00007f7868460600 points into unknown readable memory: 0x00007f78478bcc38 | 38 cc 8b 47 78 7f 00 00 - -Top of Stack: (sp=0x00007f784d1e6900) -0x00007f784d1e6900: 00007f7868460600 0000000000000010 ..Fhx........... -0x00007f784d1e6910: 00007f7868460600 00007f784fbd5bf2 ..Fhx....[.Ox... -0x00007f784d1e6920: 00007f7868460550 00007f784789cb61 P.Fhx...a..Gx... -0x00007f784d1e6930: 00007f7890e1f580 0000000000000000 ....x........... -0x00007f784d1e6940: 00007f784d1e6990 00007f784fbd56d1 .i.Mx....V.Ox... -0x00007f784d1e6950: 00007f784fbd5bf2 1e1e4618254b6f00 .[.Ox....oK%.F.. -0x00007f784d1e6960: 00007f784d1e6b48 00007f784d1e7130 Hk.Mx...0q.Mx... -0x00007f784d1e6970: 00007f784fbd5bf2 00007f784fbd56d1 .[.Ox....V.Ox... -0x00007f784d1e6980: 0000000000000008 00007f786842e0d0 ..........Bhx... -0x00007f784d1e6990: 0000000000000008 00007f784fbccc62 ........b..Ox... -0x00007f784d1e69a0: 00007f784d1e6b80 00007f784cc151cc .k.Mx....Q.Lx... -0x00007f784d1e69b0: 00007f7868460600 0000000000000000 ..Fhx........... -0x00007f784d1e69c0: 0000000421020668 0000000421afe868 h..!....h..!.... -0x00007f784d1e69d0: 00007f7890e1f150 0000000000000000 P...x........... -0x00007f784d1e69e0: 00007f784d1e6a60 00007f78cc4679ee `j.Mx....yF.x... -0x00007f784d1e69f0: 00007f78cc4679ee 00000004229a9768 .yF.x...h..".... -0x00007f784d1e6a00: 0000000421afbca0 00007f78cc46764a ...!....JvF.x... -0x00007f784d1e6a10: 0000000000000000 fffffffffffffff5 ................ -0x00007f784d1e6a20: 00007f7890e25201 1e1e4618254b6f00 .R..x....oK%.F.. -0x00007f784d1e6a30: 00007f7890e2cbf8 0000000000000010 ....x........... -0x00007f784d1e6a40: 00007f784d1e6a70 00007f786842e0d0 pj.Mx.....Bhx... -0x00007f784d1e6a50: 0000000000000000 00007f784d1e6ab0 .........j.Mx... -0x00007f784d1e6a60: 00007f784d1e7130 00007f784cc16052 0q.Mx...R`.Lx... -0x00007f784d1e6a70: 0000000000000006 00007f784d1e72c0 .........r.Mx... -0x00007f784d1e6a80: 00007f784d1e6b80 00007f784cc151cc .k.Mx....Q.Lx... -0x00007f784d1e6a90: 0000000000000001 0000000000000000 ................ -0x00007f784d1e6aa0: 0000000000000000 00007f784d1e69b0 .........i.Mx... -0x00007f784d1e6ab0: 00007f784d1e7228 0000000000000000 (r.Mx........... -0x00007f784d1e6ac0: 0000000000000001 00007f784fbcc950 ........P..Ox... -0x00007f784d1e6ad0: 0000000600000003 00007f784d1e69b0 .........i.Mx... -0x00007f784d1e6ae0: 00007f784d1e72c0 0000000021a2b330 .r.Mx...0..!.... -0x00007f784d1e6af0: 00007f786842e030 00007f784d1e69b0 0.Bhx....i.Mx... - -Instructions: (pc=0x00007f78dd91f63e) - 0x00007f78dd91f53e: 00 00 48 89 4d c8 bf 18 00 00 00 ff 15 89 9a 0f - 0x00007f78dd91f54e: 00 48 89 c3 48 85 c0 74 61 48 8b 4d c8 66 0f 6f - 0x00007f78dd91f55e: 05 1d 4b 0a 00 48 8b 01 83 e0 01 48 09 d8 48 89 - 0x00007f78dd91f56e: 01 48 8b 45 b8 0f 11 43 08 48 89 03 49 39 ce 74 - 0x00007f78dd91f57e: 39 48 8b 39 45 89 e0 44 89 f9 4c 89 ea 4c 89 f6 - 0x00007f78dd91f58e: 41 b9 01 00 00 00 e8 17 fd ff ff 48 89 d8 4c 8b - 0x00007f78dd91f59e: 65 e0 4c 8b 6d e8 4c 8b 75 f0 4c 8b 7d f8 48 8b - 0x00007f78dd91f5ae: 5d d8 c9 c3 66 0f 1f 44 00 00 4c 8b 65 e0 4c 8b - 0x00007f78dd91f5be: 6d e8 4c 8b 75 f0 4c 8b 7d f8 48 89 d8 48 8b 5d - 0x00007f78dd91f5ce: d8 c9 c3 0f 1f 80 00 00 00 00 45 89 e7 4c 89 f1 - 0x00007f78dd91f5de: e9 5d ff ff ff 31 db eb e1 66 0f 1f 84 00 00 00 - 0x00007f78dd91f5ee: 00 00 f3 0f 1e fa 55 48 89 e5 48 83 ec 20 48 89 - 0x00007f78dd91f5fe: 5d e8 48 85 f6 74 6e 48 8b 1e 4c 89 65 f0 49 89 - 0x00007f78dd91f60e: fc 4c 89 6d f8 49 89 d5 48 83 fb 01 77 1b eb 42 - 0x00007f78dd91f61e: 66 90 48 8d 53 08 48 83 c3 10 85 c0 48 0f 48 da - 0x00007f78dd91f62e: 48 8b 1b 48 83 fb 01 76 29 48 83 e3 fe 4c 89 e7 -=>0x00007f78dd91f63e: 48 8b 33 41 ff d5 85 c0 75 d8 4c 8b 65 f0 4c 8b - 0x00007f78dd91f64e: 6d f8 48 89 d8 48 8b 5d e8 c9 c3 0f 1f 80 00 00 - 0x00007f78dd91f65e: 00 00 31 db 4c 8b 65 f0 4c 8b 6d f8 48 89 d8 48 - 0x00007f78dd91f66e: 8b 5d e8 c9 c3 31 db eb d9 66 0f 1f 84 00 00 00 - 0x00007f78dd91f67e: 00 00 f3 0f 1e fa 55 48 89 e5 41 57 41 56 41 55 - 0x00007f78dd91f68e: 49 89 fd 41 54 49 89 f4 53 48 83 ec 48 64 4c 8b - 0x00007f78dd91f69e: 34 25 28 00 00 00 4c 89 75 c8 49 89 d6 48 89 e0 - 0x00007f78dd91f6ae: 48 39 c4 74 14 48 81 ec 00 10 00 00 80 8c 24 f8 - 0x00007f78dd91f6be: 0f 00 00 00 48 39 c4 75 ec 48 81 ec 50 01 00 00 - 0x00007f78dd91f6ce: 80 8c 24 48 01 00 00 00 48 8d 5c 24 0f 48 83 e3 - 0x00007f78dd91f6de: f0 48 89 5d b0 4d 85 e4 74 7c 49 8b 04 24 48 83 - 0x00007f78dd91f6ee: f8 01 76 72 48 83 e0 fe c7 45 bc 28 00 00 00 31 - 0x00007f78dd91f6fe: db 49 89 c7 eb 15 0f 1f 40 00 49 8b 47 10 4d 8d - 0x00007f78dd91f70e: 67 10 48 83 c3 01 48 85 c0 74 4b 48 8b 30 4c 89 - 0x00007f78dd91f71e: ef 41 ff d6 41 89 c2 85 c0 0f 84 e3 00 00 00 4c - 0x00007f78dd91f72e: 8d 3c dd 00 00 00 00 39 5d bc 74 56 48 8b 45 b0 - - -Stack slot to memory mapping: - -stack at sp + 0 slots: 0x00007f7868460600 points into unknown readable memory: 0x00007f78478bcc38 | 38 cc 8b 47 78 7f 00 00 -stack at sp + 1 slots: 0x0000000000000010 is an unknown value -stack at sp + 2 slots: 0x00007f7868460600 points into unknown readable memory: 0x00007f78478bcc38 | 38 cc 8b 47 78 7f 00 00 -stack at sp + 3 slots: 0x00007f784fbd5bf2: in /usr/lib64/libvlc.so at 0x00007f784fbb4000 -stack at sp + 4 slots: 0x00007f7868460550 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 -stack at sp + 5 slots: 0x00007f784789cb61: var_SetChecked+0x0000000000000061 in /usr/lib/libvlccore.so.9 at 0x00007f78477de000 -stack at sp + 6 slots: {method} {0x00007f7890e1f580} 'unregisterCompositionLocked' '(Landroidx/compose/runtime/ControlledComposition;)V' in 'androidx/compose/runtime/Recomposer' -stack at sp + 7 slots: 0x0 is null - -Lock stack of current Java thread (top to bottom): -LockStack[0]: androidx.compose.runtime.SynchronizedObject -{0x0000000422a9d690} - klass: 'androidx/compose/runtime/SynchronizedObject' - flags: - - - ---- fields (total size 2 words): - - -Compiled method (c1) 2047129 8046 3 androidx.compose.runtime.DisposableEffectImpl::onForgotten (23 bytes) - total in heap [0x00007f78c5b2bd08,0x00007f78c5b2c038] = 816 - main code [0x00007f78c5b2be00,0x00007f78c5b2bff0] = 496 - stub code [0x00007f78c5b2bff0,0x00007f78c5b2c030] = 64 - oops [0x00007f78c5b2c030,0x00007f78c5b2c038] = 8 - mutable data [0x00007f789802ee70,0x00007f789802eec0] = 80 - relocation [0x00007f789802ee70,0x00007f789802eeb8] = 72 - metadata [0x00007f789802eeb8,0x00007f789802eec0] = 8 - immutable data [0x00007f789802ede0,0x00007f789802ee68] = 136 - dependencies [0x00007f789802ede0,0x00007f789802ede8] = 8 - nul chk table [0x00007f789802ede8,0x00007f789802edf8] = 16 - scopes pcs [0x00007f789802edf8,0x00007f789802ee48] = 80 - scopes data [0x00007f789802ee48,0x00007f789802ee68] = 32 - -[Constant Pool (empty)] - -[MachCode] -[Instructions begin] - 0x00007f78c5b2be00: 660f 1f84 | 0000 0000 | 0066 0f1f | 8400 0000 | 0000 448b | 5608 443b - - 0x00007f78c5b2be18: ; {runtime_call Shared Runtime ic_miss_blob} - 0x00007f78c5b2be18: 5008 0f85 | c017 9806 -[Verified Entry Point] - 0x00007f78c5b2be20: 8984 2400 | c0fe ff55 | 4883 ec30 | 4181 7f20 | 0500 0000 - - 0x00007f78c5b2be34: ; {runtime_call Stub::method_entry_barrier} - 0x00007f78c5b2be34: 7405 e865 - - 0x00007f78c5b2be38: ; {metadata(method data for {method} {0x00007f78916c7340} 'onForgotten' '()V' in 'androidx/compose/runtime/DisposableEffectImpl')} - 0x00007f78c5b2be38: 0597 0648 | bf18 43db | 9278 7f00 | 008b 9f90 | 0000 0083 | c302 899f | 9000 0000 | 81e3 fe07 - 0x00007f78c5b2be58: 0000 85db | 0f84 1301 | 0000 8b7e | 1048 c1e7 | 0348 85ff - - 0x00007f78c5b2be6c: ; {metadata(method data for {method} {0x00007f78916c7340} 'onForgotten' '()V' in 'androidx/compose/runtime/DisposableEffectImpl')} - 0x00007f78c5b2be6c: 48bb 1843 | db92 787f | 0000 b8d8 | 0000 0074 | 05b8 e800 | 0000 488b | 1403 488d | 5201 4889 - 0x00007f78c5b2be8c: 1403 0f84 | bd00 0000 | 4889 7424 | 1848 3b07 - - 0x00007f78c5b2be9c: ; {metadata(method data for {method} {0x00007f78916c7340} 'onForgotten' '()V' in 'androidx/compose/runtime/DisposableEffectImpl')} - 0x00007f78c5b2be9c: 488b df48 | b818 43db | 9278 7f00 | 008b 5b08 | 483b 9800 | 0100 0075 | 0d48 8380 | 0801 0000 - 0x00007f78c5b2bebc: 01e9 6000 | 0000 483b | 9810 0100 | 0075 0d48 | 8380 1801 | 0000 01e9 | 4a00 0000 | 4883 b800 - 0x00007f78c5b2bedc: 0100 0000 | 7517 4889 | 9800 0100 | 0048 c780 | 0801 0000 | 0100 0000 | e929 0000 | 0048 83b8 - 0x00007f78c5b2befc: 1001 0000 | 0075 1748 | 8998 1001 | 0000 48c7 | 8018 0100 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5b2bf1c: 80f8 0000 | 0001 488b | f748 b860 | ef02 9878 - - 0x00007f78c5b2bf2c: ; {virtual_call} - 0x00007f78c5b2bf2c: 7f00 00e8 - - 0x00007f78c5b2bf30: ; ImmutableOopMap {[24]=Oop } - ;*invokeinterface dispose {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.DisposableEffectImpl::onForgotten@8 (line 91) - 0x00007f78c5b2bf30: 2c54 a606 - - 0x00007f78c5b2bf34: ; {post_call_nop} - 0x00007f78c5b2bf34: 0f1f 8400 | 2c02 0000 - - 0x00007f78c5b2bf3c: ; {metadata(method data for {method} {0x00007f78916c7340} 'onForgotten' '()V' in 'androidx/compose/runtime/DisposableEffectImpl')} - 0x00007f78c5b2bf3c: 48be 1843 | db92 787f | 0000 ff86 | 2801 0000 | 488b 7424 | 1841 0fbe | 7f48 85ff | 0f85 3d00 - 0x00007f78c5b2bf5c: 0000 c746 | 1000 0000 | 0048 83c4 - - 0x00007f78c5b2bf68: ; {poll_return} - 0x00007f78c5b2bf68: 305d 493b | 6728 0f87 | 3f00 0000 - - 0x00007f78c5b2bf74: ; {metadata({method} {0x00007f78916c7340} 'onForgotten' '()V' in 'androidx/compose/runtime/DisposableEffectImpl')} - 0x00007f78c5b2bf74: c349 ba40 | 736c 9178 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5b2bf8c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5b2bf8c: e8cf aba5 - - 0x00007f78c5b2bf90: ; ImmutableOopMap {rsi=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.DisposableEffectImpl::onForgotten@-1 (line 91) - 0x00007f78c5b2bf90: 06e9 ccfe - - 0x00007f78c5b2bf94: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5b2bf94: ffff e845 - - 0x00007f78c5b2bf98: ; ImmutableOopMap {rdi=Oop [24]=Oop } - ;*invokeinterface dispose {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.DisposableEffectImpl::onForgotten@8 (line 91) - 0x00007f78c5b2bf98: 4ca5 068b | 7e10 48c1 | e703 4883 | ff00 74b6 | 4889 3c24 - - 0x00007f78c5b2bfac: ; {runtime_call g1_pre_barrier_slow} - 0x00007f78c5b2bfac: e8af b2a5 - - 0x00007f78c5b2bfb0: ; {internal_word} - 0x00007f78c5b2bfb0: 06eb ab49 | ba6a bfb2 | c578 7f00 | 004d 8997 | 3805 0000 - - 0x00007f78c5b2bfc4: ; {runtime_call SafepointBlob} - 0x00007f78c5b2bfc4: e997 4b98 | 0649 8b87 | e005 0000 | 49c7 87e0 | 0500 0000 | 0000 0049 | c787 e805 | 0000 0000 - 0x00007f78c5b2bfe4: 0000 4883 - - 0x00007f78c5b2bfe8: ; {runtime_call C1 Runtime unwind_exception_blob} - 0x00007f78c5b2bfe8: c430 5de9 | f03c a506 -[Stub Code] - 0x00007f78c5b2bff0: ; {other} - 0x00007f78c5b2bff0: 0f1f 4400 - - 0x00007f78c5b2bff4: ; {static_stub} - 0x00007f78c5b2bff4: 0048 bb00 | 0000 0000 - - 0x00007f78c5b2bffc: ; {runtime_call nmethod} - 0x00007f78c5b2bffc: 0000 00e9 | fbff ffff -[Exception Handler] - 0x00007f78c5b2c004: ; {runtime_call C1 Runtime handle_exception_from_callee_blob} - 0x00007f78c5b2c004: e8d7 6da5 - - 0x00007f78c5b2c008: ; {external_word} - 0x00007f78c5b2c008: 0648 bf99 | ef4d dc78 | 7f00 0048 - - 0x00007f78c5b2c014: ; {runtime_call MacroAssembler::debug64(char*, long, long*)} - 0x00007f78c5b2c014: 83e4 f0e8 | f4a5 6017 - - 0x00007f78c5b2c01c: ; {section_word} - 0x00007f78c5b2c01c: f449 ba1d | c0b2 c578 | 7f00 0041 - - 0x00007f78c5b2c028: ; {runtime_call DeoptimizationBlob} - 0x00007f78c5b2c028: 52e9 b232 | 9806 f4f4 -[/MachCode] - - -Compiled method (c1) 2047144 7325 ! 3 androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2 (2048 bytes) - total in heap [0x00007f78c5a42108,0x00007f78c5a4b4b0] = 37800 - main code [0x00007f78c5a42200,0x00007f78c5a4b078] = 36472 - stub code [0x00007f78c5a4b078,0x00007f78c5a4b480] = 1032 - oops [0x00007f78c5a4b480,0x00007f78c5a4b4b0] = 48 - mutable data [0x00007f7869e31530,0x00007f7869e32220] = 3312 - relocation [0x00007f7869e31530,0x00007f7869e32088] = 2904 - metadata [0x00007f7869e32088,0x00007f7869e32220] = 408 - immutable data [0x00007f7869e527b0,0x00007f7869e5a270] = 31424 - dependencies [0x00007f7869e527b0,0x00007f7869e52820] = 112 - nul chk table [0x00007f7869e52820,0x00007f7869e52a20] = 512 - handler table [0x00007f7869e52a20,0x00007f7869e54068] = 5704 - scopes pcs [0x00007f7869e54068,0x00007f7869e55308] = 4768 - scopes data [0x00007f7869e55308,0x00007f7869e5a270] = 20328 - -[Constant Pool (empty)] - -[MachCode] -[Verified Entry Point] - # {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2' - # parm0: rsi:rsi = 'androidx/compose/runtime/Recomposer' - # parm1: rdx:rdx = 'androidx/collection/MutableScatterSet' - # parm2: rcx:rcx = 'androidx/collection/MutableScatterSet' - # parm3: r8:r8 = 'java/util/List' - # parm4: r9:r9 = 'java/util/List' - # parm5: rdi:rdi = 'androidx/collection/MutableScatterSet' - # parm6: [sp+0x280] = 'java/util/List' (sp of caller) - # parm7: [sp+0x288] = 'androidx/collection/MutableScatterSet' - # parm8: [sp+0x290] = 'java/util/Set' - # parm9: [sp+0x298] = long - 0x00007f78c5a42200: 8984 2400 | c0fe ff55 | 4881 ec70 | 0200 0090 | 4181 7f20 | 0500 0000 - - 0x00007f78c5a42218: ; {runtime_call Stub::method_entry_barrier} - 0x00007f78c5a42218: 7405 e881 | a1a5 0648 | 89b4 24d8 | 0000 0048 | 8994 24e8 | 0000 0048 | 898c 24f0 | 0000 004c - 0x00007f78c5a42238: 8984 24e0 | 0000 004c | 898c 24f8 | 0000 0048 | 89bc 2400 - - 0x00007f78c5a4224c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4224c: 0100 0048 | bb48 99d2 | 9278 7f00 | 008b 8390 | 0000 0083 | c002 8983 | 9000 0000 | 25fe 0700 - 0x00007f78c5a4226c: 0085 c00f | 8403 8000 - - 0x00007f78c5a42274: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42274: 0048 bb48 | 99d2 9278 | 7f00 0048 | 8383 d800 - - 0x00007f78c5a42284: ; {metadata(method data for {method} {0x00007f7890e27d88} 'access$getHasBroadcastFrameClockAwaiters' '(Landroidx/compose/runtime/Recomposer;)Z' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a42284: 0000 0148 | bbf0 7ed2 | 9278 7f00 | 008b 8390 | 0000 0083 | c002 8983 | 9000 0000 | 25fe ff1f - 0x00007f78c5a422a4: 0085 c00f | 84ec 7f00 | 0048 3b06 - - 0x00007f78c5a422b0: ; {metadata(method data for {method} {0x00007f7890e27d88} 'access$getHasBroadcastFrameClockAwaiters' '(Landroidx/compose/runtime/Recomposer;)Z' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a422b0: 488b de48 | b8f0 7ed2 | 9278 7f00 | 0048 8380 | d800 0000 | 0148 8bde | 488b f30f - - 0x00007f78c5a422cc: ; {optimized virtual_call} - 0x00007f78c5a422cc: 1f40 00e8 - - 0x00007f78c5a422d0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokespecial getHasBroadcastFrameClockAwaiters {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::access$getHasBroadcastFrameClockAwaiters@1 (line 191) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1 (line 629) - 0x00007f78c5a422d0: cce2 ffff - - 0x00007f78c5a422d4: ; {post_call_nop} - 0x00007f78c5a422d4: 0f1f 8400 | cc01 0000 | 83e0 0185 - - 0x00007f78c5a422e0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a422e0: c048 ba48 | 99d2 9278 | 7f00 00be | e800 0000 | 7405 bef8 | 0000 0048 | 8b3c 3248 | 8d7f 0148 - 0x00007f78c5a42300: 893c 320f | 846f 0600 - - 0x00007f78c5a42308: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a42308: 0048 ba88 | da4f 2104 - - 0x00007f78c5a42310: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42310: 0000 0048 | be48 99d2 | 9278 7f00 | 008b 5208 | 483b 9610 | 0100 0075 | 0d48 8386 | 1801 0000 - 0x00007f78c5a42330: 01e9 6000 | 0000 483b | 9620 0100 | 0075 0d48 | 8386 2801 | 0000 01e9 | 4a00 0000 | 4883 be10 - 0x00007f78c5a42350: 0100 0000 | 7517 4889 | 9610 0100 | 0048 c786 | 1801 0000 | 0100 0000 | e929 0000 | 0048 83be - 0x00007f78c5a42370: 2001 0000 | 0075 1748 | 8996 2001 | 0000 48c7 | 8628 0100 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a42390: 8608 0100 - - 0x00007f78c5a42394: ; {metadata(method data for {method} {0x00007f7891032318} 'beginSection' '(Ljava/lang/String;)Ljava/lang/Object;' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a42394: 0001 48ba | b8dc ae92 | 787f 0000 | 8bb2 9000 | 0000 83c6 | 0289 b290 | 0000 0081 | e6fe ff1f - 0x00007f78c5a423b4: 0085 f60f | 8402 7f00 | 0048 8bb4 | 24d8 0000 - - 0x00007f78c5a423c4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a423c4: 0048 ba48 | 99d2 9278 | 7f00 0048 | 8382 3801 | 0000 018b | 561c 48c1 | e203 483b | 0248 8bda - 0x00007f78c5a423e4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a423e4: 48b8 4899 | d292 787f | 0000 8b5b | 0848 3b98 | 5001 0000 | 750d 4883 | 8058 0100 | 0001 e960 - 0x00007f78c5a42404: 0000 0048 | 3b98 6001 | 0000 750d | 4883 8068 | 0100 0001 | e94a 0000 | 0048 83b8 | 5001 0000 - 0x00007f78c5a42424: 0075 1748 | 8998 5001 | 0000 48c7 | 8058 0100 | 0001 0000 | 00e9 2900 | 0000 4883 | b860 0100 - 0x00007f78c5a42444: 0000 7517 | 4889 9860 | 0100 0048 | c780 6801 | 0000 0100 | 0000 e908 | 0000 0048 | 8380 4801 - 0x00007f78c5a42464: ; {metadata(method data for {method} {0x00007f7890e5a2f0} 'sendFrame' '(J)V' in 'androidx/compose/runtime/BroadcastFrameClock')} - 0x00007f78c5a42464: 0000 0148 | bb50 7dcd | 9278 7f00 | 008b 8390 | 0000 0083 | c002 8983 | 9000 0000 | 25fe ff1f - 0x00007f78c5a42484: 0085 c00f | 8458 7e00 | 008b 5a10 | 48c1 e303 - - 0x00007f78c5a42494: ; {metadata(method data for {method} {0x00007f7890e5a2f0} 'sendFrame' '(J)V' in 'androidx/compose/runtime/BroadcastFrameClock')} - 0x00007f78c5a42494: 48ba 507d | cd92 787f | 0000 4883 | 82d8 0000 - - 0x00007f78c5a424a4: ; {metadata(method data for {method} {0x00007f7890558a48} 'linkToTargetMethod' '(JLjava/lang/Object;)Ljava/lang/Object;' in 'java/lang/invoke/Invokers$Holder')} - 0x00007f78c5a424a4: 0001 48ba | 08da bc92 | 787f 0000 | 8b82 9000 | 0000 83c0 | 0289 8290 | 0000 0025 | feff 1f00 - 0x00007f78c5a424c4: 85c0 0f84 | 3a7e 0000 - - 0x00007f78c5a424cc: ; {metadata(method data for {method} {0x00007f7890558a48} 'linkToTargetMethod' '(JLjava/lang/Object;)Ljava/lang/Object;' in 'java/lang/invoke/Invokers$Holder')} - 0x00007f78c5a424cc: 48ba 08da | bc92 787f | 0000 4883 | 8208 0100 - - 0x00007f78c5a424dc: ; {metadata(method data for {method} {0x00007f7890490820} 'newInvokeSpecial' '(Ljava/lang/Object;J)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle$Holder')} - 0x00007f78c5a424dc: 0001 48ba | c0db bc92 | 787f 0000 | 8b82 9000 | 0000 83c0 | 0289 8290 | 0000 0025 | feff 1f00 - 0x00007f78c5a424fc: 85c0 0f84 | 237e 0000 - - 0x00007f78c5a42504: ; {metadata(method data for {method} {0x00007f7890490820} 'newInvokeSpecial' '(Ljava/lang/Object;J)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle$Holder')} - 0x00007f78c5a42504: 48ba c0db | bc92 787f | 0000 4883 | 82d8 0000 - - 0x00007f78c5a42514: ; {metadata(method data for {method} {0x00007f78900834b0} 'allocateInstance' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle')} - 0x00007f78c5a42514: 0001 48ba | c095 db90 | 787f 0000 | 8b82 9000 | 0000 83c0 | 0289 8290 | 0000 0025 | feff 1f00 - 0x00007f78c5a42534: 85c0 0f84 | 0c7e 0000 - - 0x00007f78c5a4253c: ; {oop(a 'jdk/internal/misc/Unsafe'{0x0000000421000908})} - 0x00007f78c5a4253c: 48b8 0809 | 0021 0400 | 0000 488b - - 0x00007f78c5a42548: ; {metadata(method data for {method} {0x00007f78900834b0} 'allocateInstance' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle')} - 0x00007f78c5a42548: d048 b9c0 | 95db 9078 | 7f00 008b | 5208 483b | 9110 0100 | 0075 0d48 | 8381 1801 | 0000 01e9 - 0x00007f78c5a42568: 6000 0000 | 483b 9120 | 0100 0075 | 0d48 8381 | 2801 0000 | 01e9 4a00 | 0000 4883 | b910 0100 - 0x00007f78c5a42588: 0000 7517 | 4889 9110 | 0100 0048 | c781 1801 | 0000 0100 | 0000 e929 | 0000 0048 | 83b9 2001 - 0x00007f78c5a425a8: 0000 0075 | 1748 8991 | 2001 0000 | 48c7 8128 | 0100 0001 | 0000 00e9 | 0800 0000 | 4883 8108 - 0x00007f78c5a425c8: 0100 0001 - - 0x00007f78c5a425cc: ; {oop(a 'java/lang/Class'{0x0000000422951638} = 'androidx/compose/runtime/BroadcastFrameClock$$Lambda+0x000000002142f288')} - 0x00007f78c5a425cc: 48ba 3816 | 9522 0400 | 0000 488b | f048 899c | 2408 0100 | 0066 0f1f - - 0x00007f78c5a425e4: ; {optimized virtual_call} - 0x00007f78c5a425e4: 4400 00e8 - - 0x00007f78c5a425e8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [264]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual allocateInstance {reexecute=0 rethrow=0 return_oop=0} - ; - java.lang.invoke.DirectMethodHandle::allocateInstance@12 - ; - java.lang.invoke.DirectMethodHandle$Holder::newInvokeSpecial@1 - ; - java.lang.invoke.Invokers$Holder::linkToTargetMethod@5 - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@5 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - 0x00007f78c5a425e8: 247d 1907 - - 0x00007f78c5a425ec: ; {post_call_nop} - 0x00007f78c5a425ec: 0f1f 8400 | e404 0001 - - 0x00007f78c5a425f4: ; {metadata(method data for {method} {0x00007f7890490820} 'newInvokeSpecial' '(Ljava/lang/Object;J)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle$Holder')} - 0x00007f78c5a425f4: 48ba c0db | bc92 787f | 0000 488d | 92f8 0000 | 0048 85c0 | 750c f602 | 0175 23f0 | 4883 0a01 - 0x00007f78c5a42614: eb1c 8b70 | 084c 8bd6 | 4833 3248 | f7c6 fcff | ffff 740a | 40f6 c602 | 7504 4883 - - 0x00007f78c5a42630: ; {metadata(method data for {method} {0x00007f7890490820} 'newInvokeSpecial' '(Ljava/lang/Object;J)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle$Holder')} - 0x00007f78c5a42630: 0a02 48ba | c0db bc92 | 787f 0000 | 4883 8208 | 0100 0001 - - 0x00007f78c5a42644: ; {metadata(method data for {method} {0x00007f78900833f0} 'constructorMethod' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle')} - 0x00007f78c5a42644: 48ba 7894 | db90 787f | 0000 8bb2 | 9000 0000 | 83c6 0289 | b290 0000 | 0081 e6fe | ff1f 0085 - 0x00007f78c5a42664: f60f 84fe | 7c00 0048 | 3b00 488b - - 0x00007f78c5a42670: ; {metadata(method data for {method} {0x00007f7890490820} 'newInvokeSpecial' '(Ljava/lang/Object;J)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle$Holder')} - 0x00007f78c5a42670: d048 bec0 | dbbc 9278 | 7f00 0048 | 8386 6801 - - 0x00007f78c5a42680: ; {metadata(method data for {method} {0x00007f78922256c0} '' '(J)V' in 'androidx/compose/runtime/BroadcastFrameClock$$Lambda+0x000000002142f288')} - 0x00007f78c5a42680: 0000 0148 | bac8 7ecd | 9278 7f00 | 008b b290 | 0000 0083 | c602 89b2 | 9000 0000 | 81e6 feff - 0x00007f78c5a426a0: 1f00 85f6 | 0f84 e57c | 0000 488b - - 0x00007f78c5a426ac: ; {metadata(method data for {method} {0x00007f78922256c0} '' '(J)V' in 'androidx/compose/runtime/BroadcastFrameClock$$Lambda+0x000000002142f288')} - 0x00007f78c5a426ac: d048 bec8 | 7ecd 9278 | 7f00 0048 | 8386 d800 - - 0x00007f78c5a426bc: ; {metadata(method data for {method} {0x00007f78900005d0} '' '()V' in 'java/lang/Object')} - 0x00007f78c5a426bc: 0000 0148 | ba18 7d26 | 9078 7f00 | 008b b290 | 0000 0083 | c602 89b2 | 9000 0000 | 81e6 feff - 0x00007f78c5a426dc: 1f00 85f6 | 0f84 ca7c | 0000 488b | 9424 9802 | 0000 4889 - - 0x00007f78c5a426f0: ; {metadata(method data for {method} {0x00007f7890558a48} 'linkToTargetMethod' '(JLjava/lang/Object;)Ljava/lang/Object;' in 'java/lang/invoke/Invokers$Holder')} - 0x00007f78c5a426f0: 5010 48ba | 08da bc92 | 787f 0000 | 488d 9238 | 0100 0048 | 85c0 750c | f602 0175 | 23f0 4883 - 0x00007f78c5a42710: 0a01 eb1c | 8b70 084c | 8bd6 4833 | 3248 f7c6 | fcff ffff | 740a 40f6 | c602 7504 | 4883 0a02 - 0x00007f78c5a42730: ; {metadata(method data for {method} {0x00007f7890e5a2f0} 'sendFrame' '(J)V' in 'androidx/compose/runtime/BroadcastFrameClock')} - 0x00007f78c5a42730: 48ba 507d | cd92 787f | 0000 488d | 92e8 0000 | 0048 85c0 | 750c f602 | 0175 23f0 | 4883 0a01 - 0x00007f78c5a42750: eb1c 8b70 | 084c 8bd6 | 4833 3248 | f7c6 fcff | ffff 740a | 40f6 c602 | 7504 4883 | 0a02 488b - 0x00007f78c5a42770: 9c24 0801 | 0000 483b | 0348 8bd3 - - 0x00007f78c5a4277c: ; {metadata(method data for {method} {0x00007f7890e5a2f0} 'sendFrame' '(J)V' in 'androidx/compose/runtime/BroadcastFrameClock')} - 0x00007f78c5a4277c: 48be 507d | cd92 787f | 0000 8b52 | 0848 3b96 | 0001 0000 | 750d 4883 | 8608 0100 | 0001 e960 - 0x00007f78c5a4279c: 0000 0048 | 3b96 1001 | 0000 750d | 4883 8618 | 0100 0001 | e94a 0000 | 0048 83be | 0001 0000 - 0x00007f78c5a427bc: 0075 1748 | 8996 0001 | 0000 48c7 | 8608 0100 | 0001 0000 | 00e9 2900 | 0000 4883 | be10 0100 - 0x00007f78c5a427dc: 0000 7517 | 4889 9610 | 0100 0048 | c786 1801 | 0000 0100 | 0000 e908 | 0000 0048 | 8386 f800 - 0x00007f78c5a427fc: 0000 0148 | 8bd0 488b - - 0x00007f78c5a42804: ; {optimized virtual_call} - 0x00007f78c5a42804: f366 90e8 - - 0x00007f78c5a42808: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual flushAndDispatchAwaiters {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@10 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - 0x00007f78c5a42808: 14c8 ecff - - 0x00007f78c5a4280c: ; {post_call_nop} - 0x00007f78c5a4280c: 0f1f 8400 | 0407 0002 - - 0x00007f78c5a42814: ; {oop(a 'androidx/compose/runtime/snapshots/Snapshot$Companion'{0x0000000421145890})} - 0x00007f78c5a42814: 48be 9058 | 1421 0400 | 0000 488b - - 0x00007f78c5a42820: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42820: fe48 bb48 | 99d2 9278 | 7f00 008b | 7f08 483b | bb80 0100 | 0075 0d48 | 8383 8801 | 0000 01e9 - 0x00007f78c5a42840: 6000 0000 | 483b bb90 | 0100 0075 | 0d48 8383 | 9801 0000 | 01e9 4a00 | 0000 4883 | bb80 0100 - 0x00007f78c5a42860: 0000 7517 | 4889 bb80 | 0100 0048 | c783 8801 | 0000 0100 | 0000 e929 | 0000 0048 | 83bb 9001 - 0x00007f78c5a42880: 0000 0075 | 1748 89bb | 9001 0000 | 48c7 8398 | 0100 0001 | 0000 00e9 | 0800 0000 | 4883 8378 - 0x00007f78c5a428a0: 0100 0001 - - 0x00007f78c5a428a4: ; {optimized virtual_call} - 0x00007f78c5a428a4: 6666 90e8 - - 0x00007f78c5a428a8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual sendApplyNotifications {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@41 (line 636) - 0x00007f78c5a428a8: 6430 4107 - - 0x00007f78c5a428ac: ; {post_call_nop} - 0x00007f78c5a428ac: 0f1f 8400 | a407 0003 - - 0x00007f78c5a428b4: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a428b4: 48be 88da | 4f21 0400 - - 0x00007f78c5a428bc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a428bc: 0000 48bf | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb7 b001 | 0000 750d | 4883 87b8 | 0100 0001 - 0x00007f78c5a428dc: e960 0000 | 0048 3bb7 | c001 0000 | 750d 4883 | 87c8 0100 | 0001 e94a | 0000 0048 | 83bf b001 - 0x00007f78c5a428fc: 0000 0075 | 1748 89b7 | b001 0000 | 48c7 87b8 | 0100 0001 | 0000 00e9 | 2900 0000 | 4883 bfc0 - 0x00007f78c5a4291c: 0100 0000 | 7517 4889 | b7c0 0100 | 0048 c787 | c801 0000 | 0100 0000 | e908 0000 | 0048 8387 - 0x00007f78c5a4293c: a801 0000 - - 0x00007f78c5a42940: ; {metadata(method data for {method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a42940: 0148 beb0 | ddae 9278 | 7f00 008b | be90 0000 | 0083 c702 | 89be 9000 | 0000 81e7 | feff 1f00 - 0x00007f78c5a42960: 85ff 0f84 | 6e7a 0000 - - 0x00007f78c5a42968: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42968: 48be 4899 | d292 787f | 0000 ff86 | d801 0000 - - 0x00007f78c5a42978: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a42978: 48be 88da | 4f21 0400 - - 0x00007f78c5a42980: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42980: 0000 48bf | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb7 2802 | 0000 750d | 4883 8730 | 0200 0001 - 0x00007f78c5a429a0: e960 0000 | 0048 3bb7 | 3802 0000 | 750d 4883 | 8740 0200 | 0001 e94a | 0000 0048 | 83bf 2802 - 0x00007f78c5a429c0: 0000 0075 | 1748 89b7 | 2802 0000 | 48c7 8730 | 0200 0001 | 0000 00e9 | 2900 0000 | 4883 bf38 - 0x00007f78c5a429e0: 0200 0000 | 7517 4889 | b738 0200 | 0048 c787 | 4002 0000 | 0100 0000 | e908 0000 | 0048 8387 - 0x00007f78c5a42a00: 2002 0000 - - 0x00007f78c5a42a04: ; {metadata(method data for {method} {0x00007f7891032318} 'beginSection' '(Ljava/lang/String;)Ljava/lang/Object;' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a42a04: 0148 beb8 | dcae 9278 | 7f00 008b | be90 0000 | 0083 c702 | 89be 9000 | 0000 81e7 | feff 1f00 - 0x00007f78c5a42a24: 85ff 0f84 | cb79 0000 | 488b b424 | d800 0000 - - 0x00007f78c5a42a34: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42a34: 48bf 4899 | d292 787f | 0000 4883 | 8750 0200 - - 0x00007f78c5a42a44: ; {metadata(method data for {method} {0x00007f7890e279b8} 'access$recordComposerModifications' '(Landroidx/compose/runtime/Recomposer;)Z' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a42a44: 0001 48bf | 08ba c092 | 787f 0000 | 8b9f 9000 | 0000 83c3 | 0289 9f90 | 0000 0081 | e3fe ff1f - 0x00007f78c5a42a64: 0085 db0f | 84ab 7900 | 0048 8bfe - - 0x00007f78c5a42a70: ; {metadata(method data for {method} {0x00007f7890e279b8} 'access$recordComposerModifications' '(Landroidx/compose/runtime/Recomposer;)Z' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a42a70: 48bb 08ba | c092 787f | 0000 4883 | 83d8 0000 | 0001 488b | fe48 8bf7 | 0f1f 8000 - - 0x00007f78c5a42a8c: ; {optimized virtual_call} - 0x00007f78c5a42a8c: 0000 00e8 - - 0x00007f78c5a42a90: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokespecial recordComposerModifications {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::access$recordComposerModifications@1 (line 191) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@100 (line 643) - 0x00007f78c5a42a90: fc55 4107 - - 0x00007f78c5a42a94: ; {post_call_nop} - 0x00007f78c5a42a94: 0f1f 8400 | 8c09 0004 - - 0x00007f78c5a42a9c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42a9c: 48b8 4899 | d292 787f | 0000 4883 | 8060 0200 | 0001 488b | b424 d800 | 0000 8b7e | 2448 c1e7 - 0x00007f78c5a42abc: 0348 89bc | 2420 0100 | 0048 8d94 | 2450 0200 | 0048 897a | 0848 8b07 | 418b 9fd0 | 0700 0081 - 0x00007f78c5a42adc: fb20 0800 | 000f 8d57 | 7900 0049 | 3b7c 1ff8 | 0f84 2500 | 0000 a802 | 0f85 4479 | 0000 488b - 0x00007f78c5a42afc: d848 83e3 | fe48 83c8 | 01f0 480f | b11f 0f85 | 2e79 0000 | 418b 9fd0 | 0700 0049 | 893c 1f83 - 0x00007f78c5a42b1c: c308 4189 | 9fd0 0700 - - 0x00007f78c5a42b24: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42b24: 0048 ba48 | 99d2 9278 | 7f00 0048 | 8382 7002 | 0000 018b | 563c 48c1 | e203 8b5a | 1048 c1e3 - 0x00007f78c5a42b44: 0348 899c | 2418 0100 | 0048 8bc2 - - 0x00007f78c5a42b50: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42b50: 48b9 4899 | d292 787f | 0000 8b40 | 0848 3b81 | 8802 0000 | 750d 4883 | 8190 0200 | 0001 e960 - 0x00007f78c5a42b70: 0000 0048 | 3b81 9802 | 0000 750d | 4883 81a0 | 0200 0001 | e94a 0000 | 0048 83b9 | 8802 0000 - 0x00007f78c5a42b90: 0075 1748 | 8981 8802 | 0000 48c7 | 8190 0200 | 0001 0000 | 00e9 2900 | 0000 4883 | b998 0200 - 0x00007f78c5a42bb0: 0000 7517 | 4889 8198 | 0200 0048 | c781 a002 | 0000 0100 | 0000 e908 | 0000 0048 | 8381 8002 - 0x00007f78c5a42bd0: 0000 018b | 420c 8984 | 2414 0100 | 00b9 0000 | 0000 e9c3 | 0200 0090 - - 0x00007f78c5a42be8: ; implicit exception: dispatches to 0x00007f78c5a4a456 - 0x00007f78c5a42be8: 3b4b 0c0f | 836f 7800 | 0048 63d1 | 8b54 9310 | 48c1 e203 - - 0x00007f78c5a42bfc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42bfc: 4885 d249 | bd48 99d2 | 9278 7f00 | 0075 0d41 | 808d c902 | 0000 01e9 | b600 0000 | 448b 5a08 - 0x00007f78c5a42c1c: 4d3b 9dd8 | 0200 0075 | 0d49 8385 | e002 0000 | 01e9 6000 | 0000 4d3b | 9de8 0200 | 0075 0d49 - 0x00007f78c5a42c3c: 8385 f002 | 0000 01e9 | 4a00 0000 | 4983 bdd8 | 0200 0000 | 7517 4d89 | 9dd8 0200 | 0049 c785 - 0x00007f78c5a42c5c: e002 0000 | 0100 0000 | e929 0000 | 0049 83bd | e802 0000 | 0075 174d | 899d e802 | 0000 49c7 - 0x00007f78c5a42c7c: 85f0 0200 | 0001 0000 | 00e9 0800 | 0000 4983 | 85d0 0200 - - 0x00007f78c5a42c90: ; {metadata('androidx/compose/runtime/ControlledComposition')} - 0x00007f78c5a42c90: 0001 49bb | b8d6 1821 | 0000 0000 | 448b 6a08 | 4d3b 5d20 | 0f84 2400 | 0000 4d3b | eb0f 841b - 0x00007f78c5a42cb0: 0000 0041 - - 0x00007f78c5a42cb4: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a42cb4: 5541 53e8 | 240d b406 | 415d 415d | 4585 ed0f | 84a5 7700 | 00e9 0000 | 0000 4d85 - - 0x00007f78c5a42cd0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42cd0: c049 be48 | 99d2 9278 | 7f00 0075 | 0d41 808e | f902 0000 | 01e9 b600 | 0000 458b | 6808 4d3b - 0x00007f78c5a42cf0: ae08 0300 | 0075 0d49 | 8386 1003 | 0000 01e9 | 6000 0000 | 4d3b ae18 | 0300 0075 | 0d49 8386 - 0x00007f78c5a42d10: 2003 0000 | 01e9 4a00 | 0000 4983 | be08 0300 | 0000 7517 | 4d89 ae08 | 0300 0049 | c786 1003 - 0x00007f78c5a42d30: 0000 0100 | 0000 e929 | 0000 0049 | 83be 1803 | 0000 0075 | 174d 89ae | 1803 0000 | 49c7 8620 - 0x00007f78c5a42d50: 0300 0001 | 0000 00e9 | 0800 0000 | 4983 8600 | 0300 0001 - - 0x00007f78c5a42d64: ; {metadata('java/util/Collection')} - 0x00007f78c5a42d64: 49bd b05e | 0121 0000 | 0000 458b | 7008 4d3b | 6e20 0f84 | 2400 0000 | 4d3b f50f | 841b 0000 - 0x00007f78c5a42d84: 0041 5641 - - 0x00007f78c5a42d88: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a42d88: 55e8 520c | b406 415e | 415e 4585 | f60f 84dc | 7600 00e9 | 0000 0000 | 4d8b c849 | 3b01 4d8b - 0x00007f78c5a42da8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42da8: d949 bd48 | 99d2 9278 | 7f00 0045 | 8b5b 084d | 3b9d 3803 | 0000 750d | 4983 8540 | 0300 0001 - 0x00007f78c5a42dc8: e960 0000 | 004d 3b9d | 4803 0000 | 750d 4983 | 8550 0300 | 0001 e94a | 0000 0049 | 83bd 3803 - 0x00007f78c5a42de8: 0000 0075 | 174d 899d | 3803 0000 | 49c7 8540 | 0300 0001 | 0000 00e9 | 2900 0000 | 4983 bd48 - 0x00007f78c5a42e08: 0300 0000 | 7517 4d89 | 9d48 0300 | 0049 c785 | 5003 0000 | 0100 0000 | e908 0000 | 0049 8385 - 0x00007f78c5a42e28: 3003 0000 | 0149 8bf1 | 898c 2410 | 0100 0066 | 0f1f 4400 | 0048 b810 | 54de 6978 - - 0x00007f78c5a42e44: ; {virtual_call} - 0x00007f78c5a42e44: 7f00 00e8 - - 0x00007f78c5a42e48: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [280]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@176 (line 645) - 0x00007f78c5a42e48: 36b4 1a07 - - 0x00007f78c5a42e4c: ; {post_call_nop} - 0x00007f78c5a42e4c: 0f1f 8400 | 440d 0005 | 8b8c 2410 | 0100 00ff - - 0x00007f78c5a42e5c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42e5c: c148 be48 | 99d2 9278 | 7f00 008b | be94 0000 | 0083 c702 | 89be 9400 | 0000 81e7 | fe3f 0000 - 0x00007f78c5a42e7c: 85ff 0f84 | 0176 0000 - - 0x00007f78c5a42e84: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [280]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@190 (line 1955) - 0x00007f78c5a42e84: 4d8b 5730 - - 0x00007f78c5a42e88: ; {poll} - 0x00007f78c5a42e88: 4185 0248 | be48 99d2 | 9278 7f00 | 00ff 8660 | 0300 008b | 8424 1401 | 0000 488b | 9c24 1801 - 0x00007f78c5a42ea8: 0000 4c8b | 8424 e000 | 0000 3bc8 - - 0x00007f78c5a42eb4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42eb4: 48ba 4899 | d292 787f | 0000 41b9 | c002 0000 | 7c06 41b9 | b002 0000 | 4e8b 1c0a | 4d8d 5b01 - 0x00007f78c5a42ed4: 4e89 1c0a | 0f8c 0afd | ffff 488b | b424 d800 - - 0x00007f78c5a42ee4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42ee4: 0000 48bb | 4899 d292 | 787f 0000 | 4883 8378 | 0300 0001 | 8b5e 3c48 | c1e3 0348 | 3b03 488b - 0x00007f78c5a42f04: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42f04: c348 ba48 | 99d2 9278 | 7f00 008b | 4008 483b | 8290 0300 | 0075 0d48 | 8382 9803 | 0000 01e9 - 0x00007f78c5a42f24: 6000 0000 | 483b 82a0 | 0300 0075 | 0d48 8382 | a803 0000 | 01e9 4a00 | 0000 4883 | ba90 0300 - 0x00007f78c5a42f44: 0000 7517 | 4889 8290 | 0300 0048 | c782 9803 | 0000 0100 | 0000 e929 | 0000 0048 | 83ba a003 - 0x00007f78c5a42f64: 0000 0075 | 1748 8982 | a003 0000 | 48c7 82a8 | 0300 0001 | 0000 00e9 | 0800 0000 | 4883 8288 - 0x00007f78c5a42f84: 0300 0001 | 488b f30f - - 0x00007f78c5a42f8c: ; {optimized virtual_call} - 0x00007f78c5a42f8c: 1f40 00e8 - - 0x00007f78c5a42f90: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@198 (line 646) - 0x00007f78c5a42f90: 8c92 b2ff - - 0x00007f78c5a42f94: ; {post_call_nop} - 0x00007f78c5a42f94: 0f1f 8400 | 8c0e 0007 | 488d 8424 | 5002 0000 | 488b 7808 | 418b b7d0 | 0700 0049 | 3b7c 37f8 - 0x00007f78c5a42fb4: 0f85 f174 | 0000 4183 | afd0 0700 | 0008 493b | 7c37 f00f | 842a 0000 | 0048 8b07 | a802 0f85 - 0x00007f78c5a42fd4: 1200 0000 | 488b f048 | 83ce 01f0 | 480f b137 | 0f84 0d00 | 0000 4183 | 87d0 0700 | 0008 e9b4 - 0x00007f78c5a42ff4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a42ff4: 7400 0048 | be48 99d2 | 9278 7f00 | 00ff 86b8 | 0300 0048 | 8b94 24e8 | 0000 0048 | 3b02 488b - 0x00007f78c5a43014: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43014: f248 bf48 | 99d2 9278 | 7f00 008b | 7608 483b | b7d8 0300 | 0075 0d48 | 8387 e003 | 0000 01e9 - 0x00007f78c5a43034: 6000 0000 | 483b b7e8 | 0300 0075 | 0d48 8387 | f003 0000 | 01e9 4a00 | 0000 4883 | bfd8 0300 - 0x00007f78c5a43054: 0000 7517 | 4889 b7d8 | 0300 0048 | c787 e003 | 0000 0100 | 0000 e929 | 0000 0048 | 83bf e803 - 0x00007f78c5a43074: 0000 0075 | 1748 89b7 | e803 0000 | 48c7 87f0 | 0300 0001 | 0000 00e9 | 0800 0000 | 4883 87d0 - 0x00007f78c5a43094: 0300 0001 | 488b f20f - - 0x00007f78c5a4309c: ; {optimized virtual_call} - 0x00007f78c5a4309c: 1f40 00e8 - - 0x00007f78c5a430a0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@226 (line 650) - 0x00007f78c5a430a0: fc7f f0ff - - 0x00007f78c5a430a4: ; {post_call_nop} - 0x00007f78c5a430a4: 0f1f 8400 | 9c0f 0008 | 488b 8c24 | f000 0000 - - 0x00007f78c5a430b4: ; implicit exception: dispatches to 0x00007f78c5a4a4c6 - 0x00007f78c5a430b4: 483b 0148 - - 0x00007f78c5a430b8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a430b8: 8bf1 48bf | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb7 0804 | 0000 750d | 4883 8710 | 0400 0001 - 0x00007f78c5a430d8: e960 0000 | 0048 3bb7 | 1804 0000 | 750d 4883 | 8720 0400 | 0001 e94a | 0000 0048 | 83bf 0804 - 0x00007f78c5a430f8: 0000 0075 | 1748 89b7 | 0804 0000 | 48c7 8710 | 0400 0001 | 0000 00e9 | 2900 0000 | 4883 bf18 - 0x00007f78c5a43118: 0400 0000 | 7517 4889 | b718 0400 | 0048 c787 | 2004 0000 | 0100 0000 | e908 0000 | 0048 8387 - 0x00007f78c5a43138: 0004 0000 | 0148 8bf1 | 0f1f 8000 - - 0x00007f78c5a43144: ; {optimized virtual_call} - 0x00007f78c5a43144: 0000 00e8 - - 0x00007f78c5a43148: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@230 (line 651) - 0x00007f78c5a43148: 547f f0ff - - 0x00007f78c5a4314c: ; {post_call_nop} - 0x00007f78c5a4314c: 0f1f 8400 | 4410 0009 | 0f1f 4000 | 4c8b 8424 | e000 0000 - - 0x00007f78c5a43160: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43160: 4d85 c048 | bf48 99d2 | 9278 7f00 | 0075 0c80 | 8f29 0400 | 0001 e9b2 | 0000 0045 | 8b70 084c - 0x00007f78c5a43180: 3bb7 3804 | 0000 750d | 4883 8740 | 0400 0001 | e960 0000 | 004c 3bb7 | 4804 0000 | 750d 4883 - 0x00007f78c5a431a0: 8750 0400 | 0001 e94a | 0000 0048 | 83bf 3804 | 0000 0075 | 174c 89b7 | 3804 0000 | 48c7 8740 - 0x00007f78c5a431c0: 0400 0001 | 0000 00e9 | 2900 0000 | 4883 bf48 | 0400 0000 | 7517 4c89 | b748 0400 | 0048 c787 - 0x00007f78c5a431e0: 5004 0000 | 0100 0000 | e908 0000 | 0048 8387 | 3004 0000 - - 0x00007f78c5a431f4: ; {metadata('java/util/Collection')} - 0x00007f78c5a431f4: 0149 beb0 | 5e01 2100 | 0000 0041 | 8b78 084c | 3b77 200f | 8420 0000 | 0049 3bfe | 0f84 1700 - 0x00007f78c5a43214: 0000 5741 - - 0x00007f78c5a43218: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a43218: 56e8 c207 | b406 5f5f | 85ff 0f84 | a372 0000 | e900 0000 | 004d 8bd8 - - 0x00007f78c5a43230: ; implicit exception: dispatches to 0x00007f78c5a4a4d4 - 0x00007f78c5a43230: 493b 0349 - - 0x00007f78c5a43234: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43234: 8bfb 49bd | 4899 d292 | 787f 0000 | 8b7f 0849 | 3bbd 6804 | 0000 750d | 4983 8570 | 0400 0001 - 0x00007f78c5a43254: e960 0000 | 0049 3bbd | 7804 0000 | 750d 4983 | 8580 0400 | 0001 e94a | 0000 0049 | 83bd 6804 - 0x00007f78c5a43274: 0000 0075 | 1749 89bd | 6804 0000 | 49c7 8570 | 0400 0001 | 0000 00e9 | 2900 0000 | 4983 bd78 - 0x00007f78c5a43294: 0400 0000 | 7517 4989 | bd78 0400 | 0049 c785 | 8004 0000 | 0100 0000 | e908 0000 | 0049 8385 - 0x00007f78c5a432b4: 6004 0000 | 0149 8bf3 | 9048 b838 | 54de 6978 - - 0x00007f78c5a432c4: ; {virtual_call} - 0x00007f78c5a432c4: 7f00 00e8 - - 0x00007f78c5a432c8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface isEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@237 (line 652) - 0x00007f78c5a432c8: 36ed 3307 - - 0x00007f78c5a432cc: ; {post_call_nop} - 0x00007f78c5a432cc: 0f1f 8400 | c411 000a - - 0x00007f78c5a432d4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a432d4: 85c0 48be | 4899 d292 | 787f 0000 | bf90 0400 | 0075 05bf | a004 0000 | 488b 1c3e | 488d 5b01 - 0x00007f78c5a432f4: 4889 1c3e | 0f85 1a00 - - 0x00007f78c5a432fc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a432fc: 0000 48be | 4899 d292 | 787f 0000 | ff86 b004 | 0000 be01 | 0000 00e9 | 0500 0000 | be00 0000 - 0x00007f78c5a4331c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4331c: 0085 f648 | be48 99d2 | 9278 7f00 | 00bf c804 | 0000 7505 | bfd8 0400 | 0048 8b1c | 3e48 8d5b - 0x00007f78c5a4333c: 0148 891c | 3e0f 85f0 | 0100 004c | 8b8c 24f8 | 0000 004d - - 0x00007f78c5a43350: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43350: 85c9 48b8 | 4899 d292 | 787f 0000 | 750c 8088 | e104 0000 | 01e9 b100 | 0000 418b | 5908 483b - 0x00007f78c5a43370: 98f0 0400 | 0075 0d48 | 8380 f804 | 0000 01e9 | 6000 0000 | 483b 9800 | 0500 0075 | 0d48 8380 - 0x00007f78c5a43390: 0805 0000 | 01e9 4a00 | 0000 4883 | b8f0 0400 | 0000 7517 | 4889 98f0 | 0400 0048 | c780 f804 - 0x00007f78c5a433b0: 0000 0100 | 0000 e929 | 0000 0048 | 83b8 0005 | 0000 0075 | 1748 8998 | 0005 0000 | 48c7 8008 - 0x00007f78c5a433d0: 0500 0001 | 0000 00e9 | 0800 0000 | 4883 80e8 | 0400 0001 - - 0x00007f78c5a433e4: ; {metadata('java/util/Collection')} - 0x00007f78c5a433e4: 48bb b05e | 0121 0000 | 0000 418b | 4108 483b | 5820 0f84 | 1f00 0000 | 483b c30f | 8416 0000 - 0x00007f78c5a43404: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a43404: 0050 53e8 | d405 b406 | 5858 85c0 | 0f84 c370 | 0000 e900 | 0000 0049 | 8bf1 483b | 0648 8bfe - 0x00007f78c5a43424: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43424: 48bb 4899 | d292 787f | 0000 8b7f | 0848 3bbb | 2005 0000 | 750d 4883 | 8328 0500 | 0001 e960 - 0x00007f78c5a43444: 0000 0048 | 3bbb 3005 | 0000 750d | 4883 8338 | 0500 0001 | e94a 0000 | 0048 83bb | 2005 0000 - 0x00007f78c5a43464: 0075 1748 | 89bb 2005 | 0000 48c7 | 8328 0500 | 0001 0000 | 00e9 2900 | 0000 4883 | bb30 0500 - 0x00007f78c5a43484: 0000 7517 | 4889 bb30 | 0500 0048 | c783 3805 | 0000 0100 | 0000 e908 | 0000 0048 | 8383 1805 - 0x00007f78c5a434a4: 0000 0166 | 0f1f 4400 | 0048 b860 | 54de 6978 - - 0x00007f78c5a434b4: ; {virtual_call} - 0x00007f78c5a434b4: 7f00 00e8 - - 0x00007f78c5a434b8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface isEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@258 (line 652) - 0x00007f78c5a434b8: 46eb 3307 - - 0x00007f78c5a434bc: ; {post_call_nop} - 0x00007f78c5a434bc: 0f1f 8400 | b413 000b - - 0x00007f78c5a434c4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a434c4: 85c0 48be | 4899 d292 | 787f 0000 | bf48 0500 | 0075 05bf | 5805 0000 | 488b 1c3e | 488d 5b01 - 0x00007f78c5a434e4: 4889 1c3e | 0f85 1a00 - - 0x00007f78c5a434ec: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a434ec: 0000 48be | 4899 d292 | 787f 0000 | ff86 6805 | 0000 be01 | 0000 00e9 | 0500 0000 | be00 0000 - 0x00007f78c5a4350c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4350c: 0085 f648 | be48 99d2 | 9278 7f00 | 00bf 8005 | 0000 7405 | bf90 0500 | 0048 8b1c | 3e48 8d5b - 0x00007f78c5a4352c: 0148 891c | 3e0f 84cd | 2200 004c | 8b84 24e0 | 0000 004d - - 0x00007f78c5a43540: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43540: 85c0 48b8 | 4899 d292 | 787f 0000 | 750c 8088 | 9905 0000 | 01e9 b100 | 0000 418b | 5808 483b - 0x00007f78c5a43560: 98a8 0500 | 0075 0d48 | 8380 b005 | 0000 01e9 | 6000 0000 | 483b 98b8 | 0500 0075 | 0d48 8380 - 0x00007f78c5a43580: c005 0000 | 01e9 4a00 | 0000 4883 | b8a8 0500 | 0000 7517 | 4889 98a8 | 0500 0048 | c780 b005 - 0x00007f78c5a435a0: 0000 0100 | 0000 e929 | 0000 0048 | 83b8 b805 | 0000 0075 | 1748 8998 | b805 0000 | 48c7 80c0 - 0x00007f78c5a435c0: 0500 0001 | 0000 00e9 | 0800 0000 | 4883 80a0 | 0500 0001 - - 0x00007f78c5a435d4: ; {metadata('java/util/Collection')} - 0x00007f78c5a435d4: 48bb b05e | 0121 0000 | 0000 418b | 4008 483b | 5820 0f84 | 1f00 0000 | 483b c30f | 8416 0000 - 0x00007f78c5a435f4: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a435f4: 0050 53e8 | e403 b406 | 5858 85c0 | 0f84 e16e | 0000 e900 | 0000 0049 | 8bf0 483b | 0648 8bfe - 0x00007f78c5a43614: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43614: 48bb 4899 | d292 787f | 0000 8b7f | 0848 3bbb | d805 0000 | 750d 4883 | 83e0 0500 | 0001 e960 - 0x00007f78c5a43634: 0000 0048 | 3bbb e805 | 0000 750d | 4883 83f0 | 0500 0001 | e94a 0000 | 0048 83bb | d805 0000 - 0x00007f78c5a43654: 0075 1748 | 89bb d805 | 0000 48c7 | 83e0 0500 | 0001 0000 | 00e9 2900 | 0000 4883 | bbe8 0500 - 0x00007f78c5a43674: 0000 7517 | 4889 bbe8 | 0500 0048 | c783 f005 | 0000 0100 | 0000 e908 | 0000 0048 | 8383 d005 - 0x00007f78c5a43694: 0000 0166 | 0f1f 4400 | 0048 b888 | 54de 6978 - - 0x00007f78c5a436a4: ; {virtual_call} - 0x00007f78c5a436a4: 7f00 00e8 - - 0x00007f78c5a436a8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface size {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@289 (line 1958) - 0x00007f78c5a436a8: e6ad 1607 - - 0x00007f78c5a436ac: ; {post_call_nop} - 0x00007f78c5a436ac: 0f1f 8400 | a415 000c | 8984 242c | 0100 00bf | 0000 0000 | e9cd 0400 | 0066 6690 - - 0x00007f78c5a436c8: ; implicit exception: dispatches to 0x00007f78c5a4a4f5 - 0x00007f78c5a436c8: 493b 004d - - 0x00007f78c5a436cc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a436cc: 8bc8 49bb | 4899 d292 | 787f 0000 | 458b 4908 | 4d3b 8b28 | 0600 0075 | 0d49 8383 | 3006 0000 - 0x00007f78c5a436ec: 01e9 6000 | 0000 4d3b | 8b38 0600 | 0075 0d49 | 8383 4006 | 0000 01e9 | 4a00 0000 | 4983 bb28 - 0x00007f78c5a4370c: 0600 0000 | 7517 4d89 | 8b28 0600 | 0049 c783 | 3006 0000 | 0100 0000 | e929 0000 | 0049 83bb - 0x00007f78c5a4372c: 3806 0000 | 0075 174d | 898b 3806 | 0000 49c7 | 8340 0600 | 0001 0000 | 00e9 0800 | 0000 4983 - 0x00007f78c5a4374c: 8320 0600 | 0001 488b | d749 8bf0 | 89bc 2428 | 0100 0066 | 0f1f 4400 | 0048 b8b0 | 54de 6978 - 0x00007f78c5a4376c: ; {virtual_call} - 0x00007f78c5a4376c: 7f00 00e8 - - 0x00007f78c5a43770: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@307 (line 1959) - 0x00007f78c5a43770: 8e9f 1907 - - 0x00007f78c5a43774: ; {post_call_nop} - 0x00007f78c5a43774: 0f1f 8400 | 6c16 000d - - 0x00007f78c5a4377c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4377c: 4885 c048 | be48 99d2 | 9278 7f00 | 0075 0c80 | 8e49 0600 | 0001 e9af | 0000 008b | 4808 483b - 0x00007f78c5a4379c: 8e58 0600 | 0075 0d48 | 8386 6006 | 0000 01e9 | 6000 0000 | 483b 8e68 | 0600 0075 | 0d48 8386 - 0x00007f78c5a437bc: 7006 0000 | 01e9 4a00 | 0000 4883 | be58 0600 | 0000 7517 | 4889 8e58 | 0600 0048 | c786 6006 - 0x00007f78c5a437dc: 0000 0100 | 0000 e929 | 0000 0048 | 83be 6806 | 0000 0075 | 1748 898e | 6806 0000 | 48c7 8670 - 0x00007f78c5a437fc: 0600 0001 | 0000 00e9 | 0800 0000 | 4883 8650 | 0600 0001 - - 0x00007f78c5a43810: ; {metadata('androidx/compose/runtime/ControlledComposition')} - 0x00007f78c5a43810: 48b9 b8d6 | 1821 0000 | 0000 8b70 | 0848 3b4e | 200f 841f | 0000 0048 | 3bf1 0f84 | 1600 0000 - 0x00007f78c5a43830: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a43830: 5651 e8a9 | 01b4 065e | 5e85 f60f | 84b9 6c00 | 00e9 0000 | 0000 488b - - 0x00007f78c5a43848: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43848: f848 ba48 | 99d2 9278 | 7f00 0048 | 8382 8006 - - 0x00007f78c5a43858: ; {metadata(method data for {method} {0x00007f7890e28000} 'access$performRecompose' '(Landroidx/compose/runtime/Recomposer;Landroidx/compose/runtime/ControlledComposition;Landroidx/collection/MutableScatterSet;)Landroidx/compose/runtime/ControlledComposition;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a43858: 0000 0148 | ba38 d9d2 | 9278 7f00 | 008b 8a90 | 0000 0083 | c102 898a | 9000 0000 | 81e1 feff - 0x00007f78c5a43878: 1f00 85c9 | 0f84 816c | 0000 488b | b424 d800 - - 0x00007f78c5a43888: ; {metadata(method data for {method} {0x00007f7890e28000} 'access$performRecompose' '(Landroidx/compose/runtime/Recomposer;Landroidx/compose/runtime/ControlledComposition;Landroidx/collection/MutableScatterSet;)Landroidx/compose/runtime/ControlledComposition;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a43888: 0000 48ba | 38d9 d292 | 787f 0000 | 4883 82d8 | 0000 0001 | 488b d748 | 8b8c 24e8 | 0000 0048 - 0x00007f78c5a438a8: 8bb4 24d8 | 0000 0048 | 89bc 2430 - - 0x00007f78c5a438b4: ; {optimized virtual_call} - 0x00007f78c5a438b4: 0100 00e8 - - 0x00007f78c5a438b8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [304]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokespecial performRecompose {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::access$performRecompose@3 (line 191) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@328 (line 655) - 0x00007f78c5a438b8: 007b 0000 - - 0x00007f78c5a438bc: ; {post_call_nop} - 0x00007f78c5a438bc: 0f1f 8400 | b417 000e - - 0x00007f78c5a438c4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a438c4: 4885 c048 | ba48 99d2 | 9278 7f00 | 00be 9006 | 0000 7405 | bea0 0600 | 0048 8b3c | 3248 8d7f - 0x00007f78c5a438e4: 0148 893c | 320f 849c | 0100 0048 | 8bbc 2480 | 0200 0048 - - 0x00007f78c5a438f8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a438f8: 85ff 48b9 | 4899 d292 | 787f 0000 | 750c 8089 | a906 0000 | 01e9 af00 | 0000 8b5f | 0848 3b99 - 0x00007f78c5a43918: b806 0000 | 750d 4883 | 81c0 0600 | 0001 e960 | 0000 0048 | 3b99 c806 | 0000 750d | 4883 81d0 - 0x00007f78c5a43938: 0600 0001 | e94a 0000 | 0048 83b9 | b806 0000 | 0075 1748 | 8999 b806 | 0000 48c7 | 81c0 0600 - 0x00007f78c5a43958: 0001 0000 | 00e9 2900 | 0000 4883 | b9c8 0600 | 0000 7517 | 4889 99c8 | 0600 0048 | c781 d006 - 0x00007f78c5a43978: 0000 0100 | 0000 e908 | 0000 0048 | 8381 b006 - - 0x00007f78c5a43988: ; {metadata('java/util/Collection')} - 0x00007f78c5a43988: 0000 0148 | bbb0 5e01 | 2100 0000 | 008b 4f08 | 483b 5920 | 0f84 1f00 | 0000 483b | cb0f 8416 - 0x00007f78c5a439a8: 0000 0051 - - 0x00007f78c5a439ac: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a439ac: 53e8 2e00 | b406 5959 | 85c9 0f84 | 686b 0000 | e900 0000 | 0048 8bf7 - - 0x00007f78c5a439c4: ; implicit exception: dispatches to 0x00007f78c5a4a52d - 0x00007f78c5a439c4: 483b 0648 - - 0x00007f78c5a439c8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a439c8: 8bd6 48bb | 4899 d292 | 787f 0000 | 8b52 0848 | 3b93 e806 | 0000 750d | 4883 83f0 | 0600 0001 - 0x00007f78c5a439e8: e960 0000 | 0048 3b93 | f806 0000 | 750d 4883 | 8300 0700 | 0001 e94a | 0000 0048 | 83bb e806 - 0x00007f78c5a43a08: 0000 0075 | 1748 8993 | e806 0000 | 48c7 83f0 | 0600 0001 | 0000 00e9 | 2900 0000 | 4883 bbf8 - 0x00007f78c5a43a28: 0600 0000 | 7517 4889 | 93f8 0600 | 0048 c783 | 0007 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a43a48: e006 0000 | 0148 8bd0 | 0f1f 4400 | 0048 b8d8 | 54de 6978 - - 0x00007f78c5a43a5c: ; {virtual_call} - 0x00007f78c5a43a5c: 7f00 00e8 - - 0x00007f78c5a43a60: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [304]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@347 (line 656) - 0x00007f78c5a43a60: 1ea8 1a07 - - 0x00007f78c5a43a64: ; {post_call_nop} - 0x00007f78c5a43a64: 0f1f 8400 | 5c19 000f - - 0x00007f78c5a43a6c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43a6c: 48ba 4899 | d292 787f | 0000 ff82 | 1007 0000 - - 0x00007f78c5a43a7c: ; {oop(a 'kotlin/Unit'{0x00000004210022f8})} - 0x00007f78c5a43a7c: 48ba f822 | 0021 0400 | 0000 e90a - - 0x00007f78c5a43a88: ; {oop(nullptr)} - 0x00007f78c5a43a88: 0000 0048 | ba00 0000 | 0000 0000 | 0048 8bbc | 2430 0100 | 0048 8b8c | 24f0 0000 - - 0x00007f78c5a43aa4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43aa4: 0048 ba48 | 99d2 9278 | 7f00 008b | 4908 483b | 8a30 0700 | 0075 0d48 | 8382 3807 | 0000 01e9 - 0x00007f78c5a43ac4: 6000 0000 | 483b 8a40 | 0700 0075 | 0d48 8382 | 4807 0000 | 01e9 4a00 | 0000 4883 | ba30 0700 - 0x00007f78c5a43ae4: 0000 7517 | 4889 8a30 | 0700 0048 | c782 3807 | 0000 0100 | 0000 e929 | 0000 0048 | 83ba 4007 - 0x00007f78c5a43b04: 0000 0075 | 1748 898a | 4007 0000 | 48c7 8248 | 0700 0001 | 0000 00e9 | 0800 0000 | 4883 8228 - 0x00007f78c5a43b24: 0700 0001 | 488b d748 | 8bb4 24f0 | 0000 000f - - 0x00007f78c5a43b34: ; {optimized virtual_call} - 0x00007f78c5a43b34: 1f40 00e8 - - 0x00007f78c5a43b38: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@370 (line 658) - 0x00007f78c5a43b38: d42a 3f07 - - 0x00007f78c5a43b3c: ; {post_call_nop} - 0x00007f78c5a43b3c: 0f1f 8400 | 341a 0010 | 8bbc 2428 | 0100 00ff - - 0x00007f78c5a43b4c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43b4c: c748 be48 | 99d2 9278 | 7f00 008b | 9e94 0000 | 0083 c302 | 899e 9400 | 0000 81e3 | fe3f 0000 - 0x00007f78c5a43b6c: 85db 0f84 | be69 0000 - - 0x00007f78c5a43b74: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@379 (line 1958) - 0x00007f78c5a43b74: 4d8b 5730 - - 0x00007f78c5a43b78: ; {poll} - 0x00007f78c5a43b78: 4185 0248 | be48 99d2 | 9278 7f00 | 00ff 8658 | 0700 008b | 8424 2c01 | 0000 4c8b | 8424 e000 - 0x00007f78c5a43b98: 0000 3bf8 - - 0x00007f78c5a43b9c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43b9c: 49b9 4899 | d292 787f | 0000 41bb | 1006 0000 | 7c06 41bb | 0006 0000 | 4f8b 2c19 | 4d8d 6d01 - 0x00007f78c5a43bbc: 4f89 2c19 | 0f8c 02fb | ffff 493b | 0049 8bf0 - - 0x00007f78c5a43bcc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43bcc: 48bf 4899 | d292 787f | 0000 8b76 | 0848 3bb7 | 7807 0000 | 750d 4883 | 8780 0700 | 0001 e960 - 0x00007f78c5a43bec: 0000 0048 | 3bb7 8807 | 0000 750d | 4883 8790 | 0700 0001 | e94a 0000 | 0048 83bf | 7807 0000 - 0x00007f78c5a43c0c: 0075 1748 | 89b7 7807 | 0000 48c7 | 8780 0700 | 0001 0000 | 00e9 2900 | 0000 4883 | bf88 0700 - 0x00007f78c5a43c2c: 0000 7517 | 4889 b788 | 0700 0048 | c787 9007 | 0000 0100 | 0000 e908 | 0000 0048 | 8387 7007 - 0x00007f78c5a43c4c: 0000 0149 | 8bf0 6666 | 9048 b800 | 55de 6978 - - 0x00007f78c5a43c5c: ; {virtual_call} - 0x00007f78c5a43c5c: 7f00 00e8 - - 0x00007f78c5a43c60: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@384 (line 665) - 0x00007f78c5a43c60: 1e84 3807 - - 0x00007f78c5a43c64: ; {post_call_nop} - 0x00007f78c5a43c64: 0f1f 8400 | 5c1b 0012 - - 0x00007f78c5a43c6c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43c6c: 48b8 4899 | d292 787f | 0000 ff80 | a007 0000 | 488b 9424 | e800 0000 - - 0x00007f78c5a43c84: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43c84: 488b c248 | be48 99d2 | 9278 7f00 | 008b 4008 | 483b 8670 | 0800 0075 | 0d48 8386 | 7808 0000 - 0x00007f78c5a43ca4: 01e9 6000 | 0000 483b | 8680 0800 | 0075 0d48 | 8386 8808 | 0000 01e9 | 4a00 0000 | 4883 be70 - 0x00007f78c5a43cc4: 0800 0000 | 7517 4889 | 8670 0800 | 0048 c786 | 7808 0000 | 0100 0000 | e929 0000 | 0048 83be - 0x00007f78c5a43ce4: 8008 0000 | 0075 1748 | 8986 8008 | 0000 48c7 | 8688 0800 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a43d04: 8668 0800 - - 0x00007f78c5a43d08: ; {metadata(method data for {method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a43d08: 0001 48b8 | b851 a992 | 787f 0000 | 8bb0 9000 | 0000 83c6 | 0289 b090 | 0000 0081 | e6fe ff1f - 0x00007f78c5a43d28: 0085 f60f | 8427 6800 | 008b 4210 - - 0x00007f78c5a43d34: ; {metadata(method data for {method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a43d34: 85c0 48b8 | b851 a992 | 787f 0000 | bed8 0000 | 0074 05be | e800 0000 | 488b 3c30 | 488d 7f01 - 0x00007f78c5a43d54: 4889 3c30 | 0f84 1a00 - - 0x00007f78c5a43d5c: ; {metadata(method data for {method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a43d5c: 0000 48b8 | b851 a992 | 787f 0000 | ff80 f800 | 0000 b801 | 0000 00e9 | 0500 0000 | b800 0000 - 0x00007f78c5a43d7c: 0083 e001 - - 0x00007f78c5a43d80: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43d80: 85c0 48b8 | 4899 d292 | 787f 0000 | be98 0800 | 0075 05be | a808 0000 | 488b 3c30 | 488d 7f01 - 0x00007f78c5a43da0: 4889 3c30 | 488b b424 | d800 0000 | 0f85 1801 - - 0x00007f78c5a43db0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43db0: 0000 48b8 | 4899 d292 | 787f 0000 | 4883 80b8 | 0800 0001 | 8b46 3c48 | c1e0 0348 | 3b00 488b - 0x00007f78c5a43dd0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43dd0: f848 bb48 | 99d2 9278 | 7f00 008b | 7f08 483b | bbd0 0800 | 0075 0d48 | 8383 d808 | 0000 01e9 - 0x00007f78c5a43df0: 6000 0000 | 483b bbe0 | 0800 0075 | 0d48 8383 | e808 0000 | 01e9 4a00 | 0000 4883 | bbd0 0800 - 0x00007f78c5a43e10: 0000 7517 | 4889 bbd0 | 0800 0048 | c783 d808 | 0000 0100 | 0000 e929 | 0000 0048 | 83bb e008 - 0x00007f78c5a43e30: 0000 0075 | 1748 89bb | e008 0000 | 48c7 83e8 | 0800 0001 | 0000 00e9 | 0800 0000 | 4883 83c8 - 0x00007f78c5a43e50: 0800 0001 | 8b40 0c85 - - 0x00007f78c5a43e58: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43e58: c048 b848 | 99d2 9278 | 7f00 00bf | f808 0000 | 7405 bf08 | 0900 0048 | 8b1c 3848 | 8d5b 0148 - 0x00007f78c5a43e78: 891c 380f | 841a 0000 - - 0x00007f78c5a43e80: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43e80: 0048 b848 | 99d2 9278 | 7f00 00ff | 8018 0900 | 00b8 0100 | 0000 e905 | 0000 00b8 | 0000 0000 - 0x00007f78c5a43ea0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43ea0: 85c0 48b8 | 4899 d292 | 787f 0000 | bf30 0900 | 0074 05bf | 4009 0000 | 488b 1c38 | 488d 5b01 - 0x00007f78c5a43ec0: 4889 1c38 | 0f84 d510 - - 0x00007f78c5a43ec8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43ec8: 0000 48b8 | 4899 d292 | 787f 0000 | 4883 8050 | 0900 0001 | 8b7e 2448 | c1e7 0348 | 89bc 2438 - 0x00007f78c5a43ee8: 0100 0048 | 8d9c 2450 | 0200 0048 | 897b 0848 | 8b07 418b | 8fd0 0700 | 0081 f920 | 0800 000f - 0x00007f78c5a43f08: 8d76 6600 | 0049 3b7c | 0ff8 0f84 | 2500 0000 | a802 0f85 | 6366 0000 | 488b c848 | 83e1 fe48 - 0x00007f78c5a43f28: 83c8 01f0 | 480f b10f | 0f85 4d66 | 0000 418b | 8fd0 0700 | 0049 893c | 0f83 c108 | 4189 8fd0 - 0x00007f78c5a43f48: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43f48: 0700 0048 | bb48 99d2 | 9278 7f00 | 0048 8383 | 6009 0000 - - 0x00007f78c5a43f5c: ; {metadata(method data for {method} {0x00007f7890e280c0} 'access$knownCompositionsLocked' '(Landroidx/compose/runtime/Recomposer;)Ljava/util/List;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a43f5c: 0148 bbf0 | ead2 9278 | 7f00 008b | 8390 0000 | 0083 c002 | 8983 9000 | 0000 25fe | ff1f 0085 - 0x00007f78c5a43f7c: c00f 8413 | 6600 0048 - - 0x00007f78c5a43f84: ; {metadata(method data for {method} {0x00007f7890e280c0} 'access$knownCompositionsLocked' '(Landroidx/compose/runtime/Recomposer;)Ljava/util/List;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a43f84: 8bde 48b8 | f0ea d292 | 787f 0000 | 4883 80d8 | 0000 0001 | 488b de48 - - 0x00007f78c5a43f9c: ; {optimized virtual_call} - 0x00007f78c5a43f9c: 8bf3 90e8 - - 0x00007f78c5a43fa0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokespecial knownCompositionsLocked {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::access$knownCompositionsLocked@1 (line 191) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@509 (line 676) - 0x00007f78c5a43fa0: 7c99 c7ff - - 0x00007f78c5a43fa4: ; {post_call_nop} - 0x00007f78c5a43fa4: 0f1f 8400 | 9c1e 0013 | 4889 8424 | 4001 0000 - - 0x00007f78c5a43fb4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a43fb4: 4885 c048 | ba48 99d2 | 9278 7f00 | 0075 0c80 | 8a69 0900 | 0001 e9af | 0000 008b | 5808 483b - 0x00007f78c5a43fd4: 9a78 0900 | 0075 0d48 | 8382 8009 | 0000 01e9 | 6000 0000 | 483b 9a88 | 0900 0075 | 0d48 8382 - 0x00007f78c5a43ff4: 9009 0000 | 01e9 4a00 | 0000 4883 | ba78 0900 | 0000 7517 | 4889 9a78 | 0900 0048 | c782 8009 - 0x00007f78c5a44014: 0000 0100 | 0000 e929 | 0000 0048 | 83ba 8809 | 0000 0075 | 1748 899a | 8809 0000 | 48c7 8290 - 0x00007f78c5a44034: 0900 0001 | 0000 00e9 | 0800 0000 | 4883 8270 | 0900 0001 - - 0x00007f78c5a44048: ; {metadata('java/util/Collection')} - 0x00007f78c5a44048: 48bb b05e | 0121 0000 | 0000 8b50 | 0848 3b5a | 200f 841f | 0000 0048 | 3bd3 0f84 | 1600 0000 - 0x00007f78c5a44068: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a44068: 5253 e871 | f9b3 065a | 5a85 d20f | 843e 6500 | 00e9 0000 | 0000 488b | f048 3b06 - - 0x00007f78c5a44084: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44084: 488b fe48 | bb48 99d2 | 9278 7f00 | 008b 7f08 | 483b bba8 | 0900 0075 | 0d48 8383 | b009 0000 - 0x00007f78c5a440a4: 01e9 6000 | 0000 483b | bbb8 0900 | 0075 0d48 | 8383 c009 | 0000 01e9 | 4a00 0000 | 4883 bba8 - 0x00007f78c5a440c4: 0900 0000 | 7517 4889 | bba8 0900 | 0048 c783 | b009 0000 | 0100 0000 | e929 0000 | 0048 83bb - 0x00007f78c5a440e4: b809 0000 | 0075 1748 | 89bb b809 | 0000 48c7 | 83c0 0900 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a44104: 83a0 0900 | 0001 6666 | 9048 b828 | 55de 6978 - - 0x00007f78c5a44114: ; {virtual_call} - 0x00007f78c5a44114: 7f00 00e8 - - 0x00007f78c5a44118: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface size {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@525 (line 1966) - 0x00007f78c5a44118: 76a3 1607 - - 0x00007f78c5a4411c: ; {post_call_nop} - 0x00007f78c5a4411c: 0f1f 8400 | 1420 0014 | 8984 244c | 0100 00bf | 0000 0000 | 488b 9c24 | 4001 0000 - - 0x00007f78c5a44138: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44138: 3bf8 48ba | 4899 d292 | 787f 0000 | bed0 0900 | 007d 05be | e009 0000 | 4c8b 0c32 | 4d8d 4901 - 0x00007f78c5a44158: 4c89 0c32 | 0f8d cd04 | 0000 483b | 0348 8bd3 - - 0x00007f78c5a44168: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44168: 48be 4899 | d292 787f | 0000 8b52 | 0848 3b96 | f809 0000 | 750d 4883 | 8600 0a00 | 0001 e960 - 0x00007f78c5a44188: 0000 0048 | 3b96 080a | 0000 750d | 4883 8610 | 0a00 0001 | e94a 0000 | 0048 83be | f809 0000 - 0x00007f78c5a441a8: 0075 1748 | 8996 f809 | 0000 48c7 | 8600 0a00 | 0001 0000 | 00e9 2900 | 0000 4883 | be08 0a00 - 0x00007f78c5a441c8: 0000 7517 | 4889 9608 | 0a00 0048 | c786 100a | 0000 0100 | 0000 e908 | 0000 0048 | 8386 f009 - 0x00007f78c5a441e8: 0000 0148 | 8bd7 488b | f389 bc24 | 4801 0000 | 0f1f 4400 | 0048 b850 | 55de 6978 - - 0x00007f78c5a44204: ; {virtual_call} - 0x00007f78c5a44204: 7f00 00e8 - - 0x00007f78c5a44208: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@543 (line 1967) - 0x00007f78c5a44208: f694 1907 - - 0x00007f78c5a4420c: ; {post_call_nop} - 0x00007f78c5a4420c: 0f1f 8400 | 0421 0015 - - 0x00007f78c5a44214: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44214: 4885 c048 | bb48 99d2 | 9278 7f00 | 0075 0c80 | 8b19 0a00 | 0001 e9af | 0000 008b | 7008 483b - 0x00007f78c5a44234: b328 0a00 | 0075 0d48 | 8383 300a | 0000 01e9 | 6000 0000 | 483b b338 | 0a00 0075 | 0d48 8383 - 0x00007f78c5a44254: 400a 0000 | 01e9 4a00 | 0000 4883 | bb28 0a00 | 0000 7517 | 4889 b328 | 0a00 0048 | c783 300a - 0x00007f78c5a44274: 0000 0100 | 0000 e929 | 0000 0048 | 83bb 380a | 0000 0075 | 1748 89b3 | 380a 0000 | 48c7 8340 - 0x00007f78c5a44294: 0a00 0001 | 0000 00e9 | 0800 0000 | 4883 8320 | 0a00 0001 - - 0x00007f78c5a442a8: ; {metadata('androidx/compose/runtime/ControlledComposition')} - 0x00007f78c5a442a8: 48be b8d6 | 1821 0000 | 0000 8b58 | 0848 3b73 | 200f 841f | 0000 0048 | 3bde 0f84 | 1600 0000 - 0x00007f78c5a442c8: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a442c8: 5356 e811 | f7b3 065b | 5b85 db0f | 84f1 6200 | 00e9 0000 | 0000 488b | f848 89bc | 2450 0100 - 0x00007f78c5a442e8: 0048 8b8c | 24f0 0000 - - 0x00007f78c5a442f0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a442f0: 0048 ba48 | 99d2 9278 | 7f00 008b | 4908 483b | 8a58 0a00 | 0075 0d48 | 8382 600a | 0000 01e9 - 0x00007f78c5a44310: 6000 0000 | 483b 8a68 | 0a00 0075 | 0d48 8382 | 700a 0000 | 01e9 4a00 | 0000 4883 | ba58 0a00 - 0x00007f78c5a44330: 0000 7517 | 4889 8a58 | 0a00 0048 | c782 600a | 0000 0100 | 0000 e929 | 0000 0048 | 83ba 680a - 0x00007f78c5a44350: 0000 0075 | 1748 898a | 680a 0000 | 48c7 8270 | 0a00 0001 | 0000 00e9 | 0800 0000 | 4883 8250 - 0x00007f78c5a44370: 0a00 0001 | 488b d748 | 8bb4 24f0 - - 0x00007f78c5a4437c: ; {optimized virtual_call} - 0x00007f78c5a4437c: 0000 00e8 - - 0x00007f78c5a44380: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [336]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual contains {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@563 (line 678) - 0x00007f78c5a44380: 9c2c cdff - - 0x00007f78c5a44384: ; {post_call_nop} - 0x00007f78c5a44384: 0f1f 8400 | 7c22 0016 - - 0x00007f78c5a4438c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4438c: 85c0 48ba | 4899 d292 | 787f 0000 | be80 0a00 | 0075 05be | 900a 0000 | 488b 3c32 | 488d 7f01 - 0x00007f78c5a443ac: 4889 3c32 | 0f85 2602 | 0000 488b | bc24 5001 | 0000 4885 | ff0f 841b - - 0x00007f78c5a443c4: ; {metadata('androidx/compose/runtime/CompositionImpl')} - 0x00007f78c5a443c4: 0000 0048 | bb30 7919 | 2100 0000 | 008b 7708 | 483b de0f | 85f6 6100 | 00e9 0000 | 0000 488b - 0x00007f78c5a443e4: d748 3b07 - - 0x00007f78c5a443e8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a443e8: 488b d748 | be48 99d2 | 9278 7f00 - - 0x00007f78c5a443f4: ; {metadata('androidx/compose/runtime/CompositionImpl')} - 0x00007f78c5a443f4: 0049 ba30 | 7919 2100 | 0000 004c | 8996 a80a | 0000 4883 | 86b0 0a00 | 0001 488b | 9424 9002 - 0x00007f78c5a44414: 0000 488b | f766 0f1f - - 0x00007f78c5a4441c: ; {optimized virtual_call} - 0x00007f78c5a4441c: 4400 00e8 - - 0x00007f78c5a44420: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [336]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface observesAnyOf {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@573 (line 679) - 0x00007f78c5a44420: e96c 0000 - - 0x00007f78c5a44424: ; {post_call_nop} - 0x00007f78c5a44424: 0f1f 8400 | 1c23 0017 - - 0x00007f78c5a4442c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4442c: 85c0 48ba | 4899 d292 | 787f 0000 | bed0 0a00 | 0074 05be | e00a 0000 | 488b 3c32 | 488d 7f01 - 0x00007f78c5a4444c: 4889 3c32 | 0f84 8601 | 0000 488b | bc24 5001 | 0000 4c8b | 8424 e000 | 0000 4d85 - - 0x00007f78c5a44468: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44468: c048 b848 | 99d2 9278 | 7f00 0075 | 0c80 88e9 | 0a00 0001 | e9b1 0000 | 0041 8b58 | 0848 3b98 - 0x00007f78c5a44488: f80a 0000 | 750d 4883 | 8000 0b00 | 0001 e960 | 0000 0048 | 3b98 080b | 0000 750d | 4883 8010 - 0x00007f78c5a444a8: 0b00 0001 | e94a 0000 | 0048 83b8 | f80a 0000 | 0075 1748 | 8998 f80a | 0000 48c7 | 8000 0b00 - 0x00007f78c5a444c8: 0001 0000 | 00e9 2900 | 0000 4883 | b808 0b00 | 0000 7517 | 4889 9808 | 0b00 0048 | c780 100b - 0x00007f78c5a444e8: 0000 0100 | 0000 e908 | 0000 0048 | 8380 f00a - - 0x00007f78c5a444f8: ; {metadata('java/util/Collection')} - 0x00007f78c5a444f8: 0000 0148 | bbb0 5e01 | 2100 0000 | 0041 8b40 | 0848 3b58 | 200f 841f | 0000 0048 | 3bc3 0f84 - 0x00007f78c5a44518: 1600 0000 - - 0x00007f78c5a4451c: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a4451c: 5053 e8bd | f4b3 0658 | 5885 c00f | 84b0 6000 | 00e9 0000 | 0000 498b | f048 3b06 - - 0x00007f78c5a44538: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44538: 488b d648 | bb48 99d2 | 9278 7f00 | 008b 5208 | 483b 9328 | 0b00 0075 | 0d48 8383 | 300b 0000 - 0x00007f78c5a44558: 01e9 6000 | 0000 483b | 9338 0b00 | 0075 0d48 | 8383 400b | 0000 01e9 | 4a00 0000 | 4883 bb28 - 0x00007f78c5a44578: 0b00 0000 | 7517 4889 | 9328 0b00 | 0048 c783 | 300b 0000 | 0100 0000 | e929 0000 | 0048 83bb - 0x00007f78c5a44598: 380b 0000 | 0075 1748 | 8993 380b | 0000 48c7 | 8340 0b00 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a445b8: 8320 0b00 | 0001 488b | d70f 1f40 | 0048 b878 | 55de 6978 - - 0x00007f78c5a445cc: ; {virtual_call} - 0x00007f78c5a445cc: 7f00 00e8 - - 0x00007f78c5a445d0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@587 (line 681) - 0x00007f78c5a445d0: 0c9a a606 - - 0x00007f78c5a445d4: ; {post_call_nop} - 0x00007f78c5a445d4: 0f1f 8400 | cc24 0018 | 8bbc 2448 | 0100 00ff - - 0x00007f78c5a445e4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a445e4: c748 ba48 | 99d2 9278 | 7f00 008b | b294 0000 | 0083 c602 | 89b2 9400 | 0000 81e6 | fe3f 0000 - 0x00007f78c5a44604: 85f6 0f84 | df5f 0000 - - 0x00007f78c5a4460c: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@598 (line 1966) - 0x00007f78c5a4460c: 4d8b 5730 - - 0x00007f78c5a44610: ; {poll} - 0x00007f78c5a44610: 4185 0248 | ba48 99d2 | 9278 7f00 | 00ff 8250 | 0b00 008b | 8424 4c01 | 0000 e901 | fbff ff48 - 0x00007f78c5a44630: 8bb4 24d8 - - 0x00007f78c5a44634: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44634: 0000 0048 | ba48 99d2 | 9278 7f00 | 0048 8382 | 680b 0000 | 018b 7e3c | 48c1 e703 | 4889 bc24 - 0x00007f78c5a44654: 6801 0000 - - 0x00007f78c5a44658: ; implicit exception: dispatches to 0x00007f78c5a4a60c - 0x00007f78c5a44658: 483b 0748 - - 0x00007f78c5a4465c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4465c: 8bd7 48bb | 4899 d292 | 787f 0000 | 8b52 0848 | 3b93 800b | 0000 750d | 4883 8388 | 0b00 0001 - 0x00007f78c5a4467c: e960 0000 | 0048 3b93 | 900b 0000 | 750d 4883 | 8398 0b00 | 0001 e94a | 0000 0048 | 83bb 800b - 0x00007f78c5a4469c: 0000 0075 | 1748 8993 | 800b 0000 | 48c7 8388 | 0b00 0001 | 0000 00e9 | 2900 0000 | 4883 bb90 - 0x00007f78c5a446bc: 0b00 0000 | 7517 4889 | 9390 0b00 | 0048 c783 | 980b 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a446dc: 780b 0000 | 018b 5f0c | 899c 2470 | 0100 00b8 | 0000 0000 | b900 0000 | 0089 8c24 | 6001 0000 - 0x00007f78c5a446fc: 0f1f 4000 - - 0x00007f78c5a44700: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44700: 3bc3 48ba | 4899 d292 | 787f 0000 | 41b9 a80b | 0000 7d06 | 41b9 b80b | 0000 4e8b | 1c0a 4d8d - 0x00007f78c5a44720: 5b01 4e89 | 1c0a 0f8d | 6e06 0000 | 8b57 1048 | c1e2 033b | 420c 0f83 | df5e 0000 | 4c63 c842 - 0x00007f78c5a44740: 8b54 8a10 | 48c1 e203 - - 0x00007f78c5a44748: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44748: 4885 d249 | bd48 99d2 | 9278 7f00 | 0075 0d41 | 808d c10b | 0000 01e9 | b600 0000 | 448b 5a08 - 0x00007f78c5a44768: 4d3b 9dd0 | 0b00 0075 | 0d49 8385 | d80b 0000 | 01e9 6000 | 0000 4d3b | 9de0 0b00 | 0075 0d49 - 0x00007f78c5a44788: 8385 e80b | 0000 01e9 | 4a00 0000 | 4983 bdd0 | 0b00 0000 | 7517 4d89 | 9dd0 0b00 | 0049 c785 - 0x00007f78c5a447a8: d80b 0000 | 0100 0000 | e929 0000 | 0049 83bd | e00b 0000 | 0075 174d | 899d e00b | 0000 49c7 - 0x00007f78c5a447c8: 85e8 0b00 | 0001 0000 | 00e9 0800 | 0000 4983 | 85c8 0b00 - - 0x00007f78c5a447dc: ; {metadata('androidx/compose/runtime/ControlledComposition')} - 0x00007f78c5a447dc: 0001 49bb | b8d6 1821 | 0000 0000 | 448b 6a08 | 4d3b 5d20 | 0f84 2400 | 0000 4d3b | eb0f 841b - 0x00007f78c5a447fc: 0000 0041 - - 0x00007f78c5a44800: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a44800: 5541 53e8 | d8f1 b306 | 415d 415d | 4585 ed0f | 8414 5e00 | 00e9 0000 | 0000 4c8b | ca48 8b94 - 0x00007f78c5a44820: 24f0 0000 - - 0x00007f78c5a44824: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44824: 0049 bb48 | 99d2 9278 | 7f00 008b | 5208 493b | 9300 0c00 | 0075 0d49 | 8383 080c | 0000 01e9 - 0x00007f78c5a44844: 6000 0000 | 493b 9310 | 0c00 0075 | 0d49 8383 | 180c 0000 | 01e9 4a00 | 0000 4983 | bb00 0c00 - 0x00007f78c5a44864: 0000 7517 | 4989 9300 | 0c00 0049 | c783 080c | 0000 0100 | 0000 e929 | 0000 0049 | 83bb 100c - 0x00007f78c5a44884: 0000 0075 | 1749 8993 | 100c 0000 | 49c7 8318 | 0c00 0001 | 0000 00e9 | 0800 0000 | 4983 83f8 - 0x00007f78c5a448a4: 0b00 0001 | 498b d148 | 8bb4 24f0 | 0000 0089 | 8424 6401 | 0000 4c89 | 8c24 5801 | 0000 0f1f - 0x00007f78c5a448c4: ; {optimized virtual_call} - 0x00007f78c5a448c4: 4400 00e8 - - 0x00007f78c5a448c8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [344]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual contains {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@650 (line 690) - 0x00007f78c5a448c8: 1492 a606 - - 0x00007f78c5a448cc: ; {post_call_nop} - 0x00007f78c5a448cc: 0f1f 8400 | c427 001a - - 0x00007f78c5a448d4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a448d4: 85c0 48ba | 4899 d292 | 787f 0000 | be28 0c00 | 0075 05be | 380c 0000 | 488b 3c32 | 488d 7f01 - 0x00007f78c5a448f4: 4889 3c32 | 0f85 e000 | 0000 4c8b | 8424 e000 - - 0x00007f78c5a44904: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44904: 0000 48ba | 4899 d292 | 787f 0000 | 458b 4008 | 4c3b 8250 | 0c00 0075 | 0d48 8382 | 580c 0000 - 0x00007f78c5a44924: 01e9 6000 | 0000 4c3b | 8260 0c00 | 0075 0d48 | 8382 680c | 0000 01e9 | 4a00 0000 | 4883 ba50 - 0x00007f78c5a44944: 0c00 0000 | 7517 4c89 | 8250 0c00 | 0048 c782 | 580c 0000 | 0100 0000 | e929 0000 | 0048 83ba - 0x00007f78c5a44964: 600c 0000 | 0075 174c | 8982 600c | 0000 48c7 | 8268 0c00 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a44984: 8248 0c00 | 0001 488b | 9424 5801 | 0000 488b | b424 e000 | 0000 6666 | 9048 b8a0 | 55de 6978 - 0x00007f78c5a449a4: ; {virtual_call} - 0x00007f78c5a449a4: 7f00 00e8 - - 0x00007f78c5a449a8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [344]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface contains {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@659 (line 690) - 0x00007f78c5a449a8: 3496 a606 - - 0x00007f78c5a449ac: ; {post_call_nop} - 0x00007f78c5a449ac: 0f1f 8400 | a428 001b - - 0x00007f78c5a449b4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a449b4: 85c0 48ba | 4899 d292 | 787f 0000 | be88 0c00 | 0074 05be | 780c 0000 | 488b 3c32 | 488d 7f01 - 0x00007f78c5a449d4: 4889 3c32 | 0f84 0a00 | 0000 be00 | 0000 00e9 | 9901 0000 | 4c8b 8c24 | 5801 0000 | 4c8b 8424 - 0x00007f78c5a449f4: e000 0000 - - 0x00007f78c5a449f8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a449f8: 4d85 c048 | bb48 99d2 | 9278 7f00 | 0075 0c80 | 8b91 0c00 | 0001 e9b1 | 0000 0041 | 8b78 0848 - 0x00007f78c5a44a18: 3bbb a00c | 0000 750d | 4883 83a8 | 0c00 0001 | e960 0000 | 0048 3bbb | b00c 0000 | 750d 4883 - 0x00007f78c5a44a38: 83b8 0c00 | 0001 e94a | 0000 0048 | 83bb a00c | 0000 0075 | 1748 89bb | a00c 0000 | 48c7 83a8 - 0x00007f78c5a44a58: 0c00 0001 | 0000 00e9 | 2900 0000 | 4883 bbb0 | 0c00 0000 | 7517 4889 | bbb0 0c00 | 0048 c783 - 0x00007f78c5a44a78: b80c 0000 | 0100 0000 | e908 0000 | 0048 8383 | 980c 0000 - - 0x00007f78c5a44a8c: ; {metadata('java/util/Collection')} - 0x00007f78c5a44a8c: 0148 bfb0 | 5e01 2100 | 0000 0041 | 8b58 0848 | 3b7b 200f | 841f 0000 | 0048 3bdf | 0f84 1600 - 0x00007f78c5a44aac: 0000 5357 - - 0x00007f78c5a44ab0: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a44ab0: e82b efb3 | 065b 5b85 | db0f 8473 | 5b00 00e9 | 0000 0000 | 498b f048 | 3b06 488b - - 0x00007f78c5a44acc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44acc: d648 bf48 | 99d2 9278 | 7f00 008b | 5208 483b | 97d0 0c00 | 0075 0d48 | 8387 d80c | 0000 01e9 - 0x00007f78c5a44aec: 6000 0000 | 483b 97e0 | 0c00 0075 | 0d48 8387 | e80c 0000 | 01e9 4a00 | 0000 4883 | bfd0 0c00 - 0x00007f78c5a44b0c: 0000 7517 | 4889 97d0 | 0c00 0048 | c787 d80c | 0000 0100 | 0000 e929 | 0000 0048 | 83bf e00c - 0x00007f78c5a44b2c: 0000 0075 | 1748 8997 | e00c 0000 | 48c7 87e8 | 0c00 0001 | 0000 00e9 | 0800 0000 | 4883 87c8 - 0x00007f78c5a44b4c: 0c00 0001 | 498b d166 | 9048 b8c8 | 55de 6978 - - 0x00007f78c5a44b5c: ; {virtual_call} - 0x00007f78c5a44b5c: 7f00 00e8 - - 0x00007f78c5a44b60: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@673 (line 691) - 0x00007f78c5a44b60: 7c94 a606 - - 0x00007f78c5a44b64: ; {post_call_nop} - 0x00007f78c5a44b64: 0f1f 8400 | 5c2a 001c - - 0x00007f78c5a44b6c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44b6c: 48be 4899 | d292 787f | 0000 ff86 | f80c 0000 | be01 0000 - - 0x00007f78c5a44b80: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44b80: 0085 f648 | be48 99d2 | 9278 7f00 | 00ba 100d | 0000 7405 | ba20 0d00 | 0048 8b0c | 1648 8d49 - 0x00007f78c5a44ba0: 0148 890c | 168b 8c24 | 6001 0000 | 0f84 2600 | 0000 ffc1 - - 0x00007f78c5a44bb4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44bb4: 48be 4899 | d292 787f | 0000 ff86 | 300d 0000 | 8b84 2464 | 0100 0048 | 8bbc 2468 | 0100 00e9 - 0x00007f78c5a44bd4: 6f01 0000 - - 0x00007f78c5a44bd8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44bd8: 85c9 48be | 4899 d292 | 787f 0000 | ba48 0d00 | 007e 05ba | 580d 0000 | 4c8b 0416 | 4d8d 4001 - 0x00007f78c5a44bf8: 4c89 0416 | 8b84 2464 | 0100 0048 | 8bbc 2468 | 0100 000f | 8e36 0100 | 008b 7710 | 48c1 e603 - 0x00007f78c5a44c18: ; implicit exception: dispatches to 0x00007f78c5a4a640 - 0x00007f78c5a44c18: 3b46 0c0f | 8329 5a00 | 0048 63d0 | 8b54 9610 | 48c1 e203 | 4c8b c044 | 2bc1 443b | 460c 0f83 - 0x00007f78c5a44c38: 1c5a 0000 - - 0x00007f78c5a44c3c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44c3c: 4885 d249 | b948 99d2 | 9278 7f00 | 0075 0d41 | 8089 610d | 0000 01e9 | bb00 0000 | 8b5a 0849 - 0x00007f78c5a44c5c: 3b99 700d | 0000 750d | 4983 8178 | 0d00 0001 | e960 0000 | 0049 3b99 | 800d 0000 | 750d 4983 - 0x00007f78c5a44c7c: 8188 0d00 | 0001 e94a | 0000 0049 | 83b9 700d | 0000 0075 | 1749 8999 | 700d 0000 | 49c7 8178 - 0x00007f78c5a44c9c: 0d00 0001 | 0000 00e9 | 2900 0000 | 4983 b980 | 0d00 0000 | 7517 4989 | 9980 0d00 | 0049 c781 - 0x00007f78c5a44cbc: 880d 0000 | 0100 0000 | e908 0000 | 0049 8381 | 680d 0000 | 018b 5e08 | 448b 4a08 | 488b 9b18 - 0x00007f78c5a44cdc: 0100 004c | 3bcb 0f84 | 2b00 0000 | 448b 5b14 | 4b3b 1c19 | 0f84 1d00 | 0000 4183 | fb20 0f85 - 0x00007f78c5a44cfc: 6b59 0000 - - 0x00007f78c5a44d00: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a44d00: 4151 53e8 | d8ec b306 | 4159 5b85 | db0f 8458 | 5900 004d | 63c0 4a8d | 7486 1045 | 0fbe 4748 - 0x00007f78c5a44d20: 4585 c00f | 854b 5900 | 004c 8bd2 | 49c1 ea03 | 4489 164c | 8bc6 4c33 | c249 c1e8 | 1749 83f8 - 0x00007f78c5a44d40: 000f 854c | 5900 00ff - - 0x00007f78c5a44d48: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44d48: c048 be48 | 99d2 9278 | 7f00 008b | 9694 0000 | 0083 c202 | 8996 9400 | 0000 81e2 | fe3f 0000 - 0x00007f78c5a44d68: 85d2 0f84 | 3b59 0000 - - 0x00007f78c5a44d70: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@724 (line 1973) - 0x00007f78c5a44d70: 4d8b 5730 - - 0x00007f78c5a44d74: ; {poll} - 0x00007f78c5a44d74: 4185 0248 | be48 99d2 | 9278 7f00 | 00ff 8698 | 0d00 0089 | 8c24 6001 | 0000 8b9c | 2470 0100 - 0x00007f78c5a44d94: 00e9 66f9 | ffff 8b8c | 2460 0100 | 008b 5f10 | 48c1 e303 | 4889 9c24 | 8001 0000 | 8b84 2470 - 0x00007f78c5a44db4: 0100 002b | c189 8424 | 7401 0000 - - 0x00007f78c5a44dc0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44dc0: 48be 4899 | d292 787f | 0000 4883 | 86b0 0d00 - - 0x00007f78c5a44dd0: ; {metadata(method data for {method} {0x00007f7890f34620} 'fill' '([Ljava/lang/Object;Ljava/lang/Object;II)V' in 'kotlin/collections/ArraysKt___ArraysJvmKt')} - 0x00007f78c5a44dd0: 0001 48be | 001f 8c92 | 787f 0000 | 8b96 9000 | 0000 83c2 | 0289 9690 | 0000 0081 | e2fe ff1f - 0x00007f78c5a44df0: 0085 d20f | 84d3 5800 - - 0x00007f78c5a44df8: ; {metadata(method data for {method} {0x00007f7890f34620} 'fill' '([Ljava/lang/Object;Ljava/lang/Object;II)V' in 'kotlin/collections/ArraysKt___ArraysJvmKt')} - 0x00007f78c5a44df8: 0048 be00 | 1f8c 9278 | 7f00 0048 | 8386 d800 - - 0x00007f78c5a44e08: ; {metadata(method data for {method} {0x00007f789051b880} 'checkNotNullParameter' '(Ljava/lang/Object;Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a44e08: 0000 0148 | be28 e1ca | 9078 7f00 | 008b 9690 | 0000 0083 | c202 8996 | 9000 0000 | 81e2 feff - 0x00007f78c5a44e28: 1f00 85d2 | 0f84 bb58 | 0000 4885 - - 0x00007f78c5a44e34: ; {metadata(method data for {method} {0x00007f789051b880} 'checkNotNullParameter' '(Ljava/lang/Object;Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a44e34: db48 be28 | e1ca 9078 | 7f00 00ba | e800 0000 | 7405 bad8 | 0000 004c | 8b04 164d | 8d40 014c - 0x00007f78c5a44e54: 8904 1689 | 8c24 6001 | 0000 0f84 | 2f07 0000 - - 0x00007f78c5a44e64: ; {metadata(method data for {method} {0x00007f7890f34620} 'fill' '([Ljava/lang/Object;Ljava/lang/Object;II)V' in 'kotlin/collections/ArraysKt___ArraysJvmKt')} - 0x00007f78c5a44e64: 48be 001f | 8c92 787f | 0000 4883 | 86e8 0000 | 0001 488b | f348 8bd0 | 8b8c 2470 - - 0x00007f78c5a44e80: ; {oop(nullptr)} - 0x00007f78c5a44e80: 0100 0049 | b800 0000 | 0000 0000 - - 0x00007f78c5a44e8c: ; {static_call} - 0x00007f78c5a44e8c: 0066 90e8 - - 0x00007f78c5a44e90: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokestatic fill {reexecute=0 rethrow=0 return_oop=0} - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@10 (line 1563) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a44e90: ece8 3607 - - 0x00007f78c5a44e94: ; {post_call_nop} - 0x00007f78c5a44e94: 0f1f 8400 | 8c2d 001e | 8b84 2474 | 0100 0048 | 8bbc 2468 | 0100 0048 - - 0x00007f78c5a44eac: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44eac: 8bf7 48bb | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb3 c80d | 0000 750d | 4883 83d0 | 0d00 0001 - 0x00007f78c5a44ecc: e960 0000 | 0048 3bb3 | d80d 0000 | 750d 4883 | 83e0 0d00 | 0001 e94a | 0000 0048 | 83bb c80d - 0x00007f78c5a44eec: 0000 0075 | 1748 89b3 | c80d 0000 | 48c7 83d0 | 0d00 0001 | 0000 00e9 | 2900 0000 | 4883 bbd8 - 0x00007f78c5a44f0c: 0d00 0000 | 7517 4889 | b3d8 0d00 | 0048 c783 | e00d 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a44f2c: c00d 0000 | 0189 470c | 488d 8424 | 5002 0000 | 488b 7808 | 418b b7d0 | 0700 0049 | 3b7c 37f8 - 0x00007f78c5a44f4c: 0f85 bc57 | 0000 4183 | afd0 0700 | 0008 493b | 7c37 f00f | 842a 0000 | 0048 8b07 | a802 0f85 - 0x00007f78c5a44f6c: 1200 0000 | 488b f048 | 83ce 01f0 | 480f b137 | 0f84 0d00 | 0000 4183 | 87d0 0700 | 0008 e97f - 0x00007f78c5a44f8c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44f8c: 5700 0048 | be48 99d2 | 9278 7f00 | 00ff 86f0 | 0d00 004c | 8b84 24e0 - - 0x00007f78c5a44fa4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a44fa4: 0000 0048 | be48 99d2 | 9278 7f00 | 0045 8b40 | 084c 3b86 | 100e 0000 | 750d 4883 | 8618 0e00 - 0x00007f78c5a44fc4: 0001 e960 | 0000 004c | 3b86 200e | 0000 750d | 4883 8628 | 0e00 0001 | e94a 0000 | 0048 83be - 0x00007f78c5a44fe4: 100e 0000 | 0075 174c | 8986 100e | 0000 48c7 | 8618 0e00 | 0001 0000 | 00e9 2900 | 0000 4883 - 0x00007f78c5a45004: be20 0e00 | 0000 7517 | 4c89 8620 | 0e00 0048 | c786 280e | 0000 0100 | 0000 e908 | 0000 0048 - 0x00007f78c5a45024: 8386 080e | 0000 0148 | 8bb4 24e0 | 0000 0066 | 9048 b8f0 | 55de 6978 - - 0x00007f78c5a4503c: ; {virtual_call} - 0x00007f78c5a4503c: 7f00 00e8 - - 0x00007f78c5a45040: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface isEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@779 (line 700) - 0x00007f78c5a45040: becf 3307 - - 0x00007f78c5a45044: ; {post_call_nop} - 0x00007f78c5a45044: 0f1f 8400 | 3c2f 001f | 85c0 be02 | 0000 0074 | 05be 0000 - - 0x00007f78c5a45058: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45058: 0000 48ba | 4899 d292 | 787f 0000 | 8bba 9400 | 0000 03fe | 89ba 9400 | 0000 85f6 | 7505 bf02 - 0x00007f78c5a45078: 0000 0081 | e7fe 3f00 | 0085 ff0f | 849b 5600 | 004d 8b57 - - 0x00007f78c5a4508c: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*ifeq {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@784 (line 700) - ; {poll} - 0x00007f78c5a4508c: 3041 8502 - - 0x00007f78c5a45090: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45090: 85c0 48be | 4899 d292 | 787f 0000 | ba38 0e00 | 0074 05ba | 480e 0000 | 488b 3c16 | 488d 7f01 - 0x00007f78c5a450b0: 4889 3c16 | 0f84 9ee0 - - 0x00007f78c5a450b8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a450b8: ffff 48be | 4899 d292 | 787f 0000 | 4883 8658 | 0e00 0001 | 488b b424 | f800 0000 | 488b 9424 - 0x00007f78c5a450d8: d800 0000 - - 0x00007f78c5a450dc: ; {static_call} - 0x00007f78c5a450dc: 6666 90e8 - - 0x00007f78c5a450e0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokestatic invokeSuspend$fillToInsert {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@791 (line 702) - 0x00007f78c5a450e0: 8d62 0000 - - 0x00007f78c5a450e4: ; {post_call_nop} - 0x00007f78c5a450e4: 0f1f 8400 | dc2f 0021 | e9f1 0200 | 000f 1f80 | 0000 0000 - - 0x00007f78c5a450f8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a450f8: 48ba 4899 | d292 787f | 0000 ff82 | e80e 0000 | ba01 0000 | 00e9 0500 | 0000 ba00 | 0000 0085 - 0x00007f78c5a45118: d2b9 0200 | 0000 7405 | b900 0000 - - 0x00007f78c5a45124: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45124: 0048 be48 | 99d2 9278 | 7f00 008b | be94 0000 | 0003 f989 | be94 0000 | 0085 c975 | 05bf 0200 - 0x00007f78c5a45144: 0000 81e7 | fe3f 0000 | 85ff 0f84 | f155 0000 - - 0x00007f78c5a45154: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*ifeq {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@812 (line 703) - 0x00007f78c5a45154: 4d8b 5730 - - 0x00007f78c5a45158: ; {poll} - 0x00007f78c5a45158: 4185 0285 - - 0x00007f78c5a4515c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4515c: d248 ba48 | 99d2 9278 | 7f00 00b9 | 000f 0000 | 7405 b910 | 0f00 0048 | 8b34 0a48 | 8d76 0148 - 0x00007f78c5a4517c: 8934 0a0f | 84d3 dfff - - 0x00007f78c5a45184: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45184: ff48 ba48 | 99d2 9278 | 7f00 0048 | 8382 200f - - 0x00007f78c5a45194: ; {metadata(method data for {method} {0x00007f7890e281a0} 'access$performInsertValues' '(Landroidx/compose/runtime/Recomposer;Ljava/util/List;Landroidx/collection/MutableScatterSet;)Ljava/util/List;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a45194: 0000 0148 | baf0 f3d2 | 9278 7f00 | 008b 8a90 | 0000 0083 | c102 898a | 9000 0000 | 81e1 feff - 0x00007f78c5a451b4: 1f00 85c9 | 0f84 a855 | 0000 488b | b424 d800 - - 0x00007f78c5a451c4: ; {metadata(method data for {method} {0x00007f7890e281a0} 'access$performInsertValues' '(Landroidx/compose/runtime/Recomposer;Ljava/util/List;Landroidx/collection/MutableScatterSet;)Ljava/util/List;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a451c4: 0000 48ba | f0f3 d292 | 787f 0000 | 4883 82d8 | 0000 0001 | 488b 9424 | f800 0000 | 488b 8c24 - 0x00007f78c5a451e4: e800 0000 | 488b b424 | d800 0000 | 0f1f 8000 - - 0x00007f78c5a451f4: ; {optimized virtual_call} - 0x00007f78c5a451f4: 0000 00e8 - - 0x00007f78c5a451f8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokespecial performInsertValues {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::access$performInsertValues@3 (line 191) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@821 (line 704) - 0x00007f78c5a451f8: e488 a606 - - 0x00007f78c5a451fc: ; {post_call_nop} - 0x00007f78c5a451fc: 0f1f 8400 | f430 0023 - - 0x00007f78c5a45204: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45204: 4885 c048 | bb48 99d2 | 9278 7f00 | 0075 0c80 | 8b29 0f00 | 0001 e9af | 0000 008b | 7808 483b - 0x00007f78c5a45224: bb38 0f00 | 0075 0d48 | 8383 400f | 0000 01e9 | 6000 0000 | 483b bb48 | 0f00 0075 | 0d48 8383 - 0x00007f78c5a45244: 500f 0000 | 01e9 4a00 | 0000 4883 | bb38 0f00 | 0000 7517 | 4889 bb38 | 0f00 0048 | c783 400f - 0x00007f78c5a45264: 0000 0100 | 0000 e929 | 0000 0048 | 83bb 480f | 0000 0075 | 1748 89bb | 480f 0000 | 48c7 8350 - 0x00007f78c5a45284: 0f00 0001 | 0000 00e9 | 0800 0000 | 4883 8330 | 0f00 0001 - - 0x00007f78c5a45298: ; {metadata('java/lang/Iterable')} - 0x00007f78c5a45298: 48bf 705c | 0121 0000 | 0000 8b58 | 0848 3b7b | 200f 841f | 0000 0048 | 3bdf 0f84 | 1600 0000 - 0x00007f78c5a452b8: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a452b8: 5357 e821 | e7b3 065b | 5b85 db0f | 84be 5400 | 00e9 0000 | 0000 488b | d048 8bbc | 2400 0100 - 0x00007f78c5a452d8: 0048 3b07 - - 0x00007f78c5a452dc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a452dc: 488b f748 | bb48 99d2 | 9278 7f00 | 008b 7608 | 483b b368 | 0f00 0075 | 0d48 8383 | 700f 0000 - 0x00007f78c5a452fc: 01e9 6000 | 0000 483b | b378 0f00 | 0075 0d48 | 8383 800f | 0000 01e9 | 4a00 0000 | 4883 bb68 - 0x00007f78c5a4531c: 0f00 0000 | 7517 4889 | b368 0f00 | 0048 c783 | 700f 0000 | 0100 0000 | e929 0000 | 0048 83bb - 0x00007f78c5a4533c: 780f 0000 | 0075 1748 | 89b3 780f | 0000 48c7 | 8380 0f00 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a4535c: 8360 0f00 | 0001 488b - - 0x00007f78c5a45364: ; {optimized virtual_call} - 0x00007f78c5a45364: f766 90e8 - - 0x00007f78c5a45368: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual plusAssign {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@827 (line 704) - 0x00007f78c5a45368: 7487 a606 - - 0x00007f78c5a4536c: ; {post_call_nop} - 0x00007f78c5a4536c: 0f1f 8400 | 6432 0024 - - 0x00007f78c5a45374: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45374: 48be 4899 | d292 787f | 0000 4883 | 8690 0f00 | 0001 488b | b424 f800 | 0000 488b | 9424 d800 - 0x00007f78c5a45394: ; {static_call} - 0x00007f78c5a45394: 0000 90e8 - - 0x00007f78c5a45398: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokestatic invokeSuspend$fillToInsert {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@833 (line 705) - 0x00007f78c5a45398: 4491 a606 - - 0x00007f78c5a4539c: ; {post_call_nop} - 0x00007f78c5a4539c: 0f1f 8400 | 9432 0025 - - 0x00007f78c5a453a4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a453a4: 48ba 4899 | d292 787f | 0000 8bb2 | 9400 0000 | 83c6 0289 | b294 0000 | 0081 e6fe | 3f00 0085 - 0x00007f78c5a453c4: f60f 84ca | 5300 004d - - 0x00007f78c5a453cc: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@836 (line 705) - ; {poll} - 0x00007f78c5a453cc: 8b57 3041 - - 0x00007f78c5a453d0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a453d0: 8502 48ba | 4899 d292 | 787f 0000 | ff82 a00f | 0000 4c8b | 8c24 f800 | 0000 4d85 - - 0x00007f78c5a453ec: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a453ec: c949 b848 | 99d2 9278 | 7f00 0075 | 0d41 8088 | 610e 0000 | 01e9 b500 | 0000 418b | 4908 493b - 0x00007f78c5a4540c: 8870 0e00 | 0075 0d49 | 8380 780e | 0000 01e9 | 6000 0000 | 493b 8880 | 0e00 0075 | 0d49 8380 - 0x00007f78c5a4542c: 880e 0000 | 01e9 4a00 | 0000 4983 | b870 0e00 | 0000 7517 | 4989 8870 | 0e00 0049 | c780 780e - 0x00007f78c5a4544c: 0000 0100 | 0000 e929 | 0000 0049 | 83b8 800e | 0000 0075 | 1749 8988 | 800e 0000 | 49c7 8088 - 0x00007f78c5a4546c: 0e00 0001 | 0000 00e9 | 0800 0000 | 4983 8068 | 0e00 0001 - - 0x00007f78c5a45480: ; {metadata('java/util/Collection')} - 0x00007f78c5a45480: 48b9 b05e | 0121 0000 | 0000 458b | 4108 493b | 4820 0f84 | 2300 0000 | 4c3b c10f | 841a 0000 - 0x00007f78c5a454a0: 0041 5051 - - 0x00007f78c5a454a4: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a454a4: e837 e5b3 | 0641 5841 | 5845 85c0 | 0f84 0053 | 0000 e900 | 0000 0049 | 8bd9 483b | 0348 8bc3 - 0x00007f78c5a454c4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a454c4: 48b9 4899 | d292 787f | 0000 8b40 | 0848 3b81 | a00e 0000 | 750d 4883 | 81a8 0e00 | 0001 e960 - 0x00007f78c5a454e4: 0000 0048 | 3b81 b00e | 0000 750d | 4883 81b8 | 0e00 0001 | e94a 0000 | 0048 83b9 | a00e 0000 - 0x00007f78c5a45504: 0075 1748 | 8981 a00e | 0000 48c7 | 81a8 0e00 | 0001 0000 | 00e9 2900 | 0000 4883 | b9b0 0e00 - 0x00007f78c5a45524: 0000 7517 | 4889 81b0 | 0e00 0048 | c781 b80e | 0000 0100 | 0000 e908 | 0000 0048 | 8381 980e - 0x00007f78c5a45544: 0000 0148 | 8bf3 6666 | 9048 b818 | 56de 6978 - - 0x00007f78c5a45554: ; {virtual_call} - 0x00007f78c5a45554: 7f00 00e8 - - 0x00007f78c5a45558: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface isEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@799 (line 703) - 0x00007f78c5a45558: a6ca 3307 - - 0x00007f78c5a4555c: ; {post_call_nop} - 0x00007f78c5a4555c: 0f1f 8400 | 5434 0027 - - 0x00007f78c5a45564: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45564: 85c0 48ba | 4899 d292 | 787f 0000 | b9c8 0e00 | 0075 05b9 | d80e 0000 | 488b 340a | 488d 7601 - 0x00007f78c5a45584: 4889 340a | 0f85 84fb | ffff e965 - - 0x00007f78c5a45590: ; {metadata(method data for {method} {0x00007f789051b880} 'checkNotNullParameter' '(Ljava/lang/Object;Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a45590: fbff ff48 | ba28 e1ca | 9078 7f00 | 0048 8382 | f800 0000 - - 0x00007f78c5a455a4: ; {metadata(method data for {method} {0x00007f789051ba30} 'throwParameterIsNullNPE' '(Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a455a4: 0148 ba78 | e2ca 9078 | 7f00 008b | b290 0000 | 0083 c602 | 89b2 9000 | 0000 81e6 | feff 1f00 - 0x00007f78c5a455c4: 85f6 0f84 | f851 0000 | 0f1f 4000 - - 0x00007f78c5a455d0: ; {other} - 0x00007f78c5a455d0: e91f 5200 | 0000 0000 | 0000 498b | 87c8 0100 | 0048 8d78 | 3049 3bbf | d801 0000 | 0f87 0c52 - 0x00007f78c5a455f0: 0000 4989 | bfc8 0100 | 0048 c700 | 0100 0000 | 488b ca89 | 4808 4833 | c989 480c | 4833 c948 - 0x00007f78c5a45610: 8948 1048 | 8948 1848 | 8948 2048 - - 0x00007f78c5a4561c: ; {metadata(method data for {method} {0x00007f789051ba30} 'throwParameterIsNullNPE' '(Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a4561c: 8948 2848 | be78 e2ca | 9078 7f00 | 0048 8386 | d800 0000 - - 0x00007f78c5a45630: ; {oop(""{0x0000000421516cc8})} - 0x00007f78c5a45630: 0148 bec8 | 6c51 2104 | 0000 0048 | 8984 2478 | 0100 000f - - 0x00007f78c5a45644: ; {static_call} - 0x00007f78c5a45644: 1f40 00e8 - - 0x00007f78c5a45648: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [376]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokestatic createParameterIsNullExceptionMessage {reexecute=0 rethrow=0 return_oop=0} - ; - kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@5 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a45648: 948e a606 - - 0x00007f78c5a4564c: ; {post_call_nop} - 0x00007f78c5a4564c: 0f1f 8400 | 4435 0028 | 488b 9424 | 7801 0000 - - 0x00007f78c5a4565c: ; {metadata(method data for {method} {0x00007f789051ba30} 'throwParameterIsNullNPE' '(Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a4565c: 48be 78e2 | ca90 787f | 0000 4883 | 86e8 0000 | 0001 488b | d048 8bb4 | 2478 0100 | 0066 0f1f - 0x00007f78c5a4567c: ; {optimized virtual_call} - 0x00007f78c5a4567c: 4400 00e8 - - 0x00007f78c5a45680: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [376]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokespecial {reexecute=0 rethrow=0 return_oop=0} - ; - kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@8 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a45680: 5c84 a606 - - 0x00007f78c5a45684: ; {post_call_nop} - 0x00007f78c5a45684: 0f1f 8400 | 7c35 0029 - - 0x00007f78c5a4568c: ; {metadata(method data for {method} {0x00007f789051ba30} 'throwParameterIsNullNPE' '(Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a4568c: 48be 78e2 | ca90 787f | 0000 4883 | 86f8 0000 - - 0x00007f78c5a4569c: ; {metadata(method data for {method} {0x00007f789051cc70} 'sanitizeStackTrace' '(Ljava/lang/Throwable;)Ljava/lang/Throwable;' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a4569c: 0001 48be | 68e8 ca90 | 787f 0000 | 8bbe 9000 | 0000 83c7 | 0289 be90 | 0000 0081 | e7fe ff1f - 0x00007f78c5a456bc: 0085 ff0f | 8446 5100 - - 0x00007f78c5a456c4: ; {oop(a 'java/lang/Class'{0x0000000421173878} = 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a456c4: 0048 be78 | 3817 2104 | 0000 0048 - - 0x00007f78c5a456d0: ; {metadata(method data for {method} {0x00007f789051cc70} 'sanitizeStackTrace' '(Ljava/lang/Throwable;)Ljava/lang/Throwable;' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a456d0: 8bfe 48bb | 68e8 ca90 | 787f 0000 | 8b7f 0848 | 3bbb e000 | 0000 750d | 4883 83e8 | 0000 0001 - 0x00007f78c5a456f0: e960 0000 | 0048 3bbb | f000 0000 | 750d 4883 | 83f8 0000 | 0001 e94a | 0000 0048 | 83bb e000 - 0x00007f78c5a45710: 0000 0075 | 1748 89bb | e000 0000 | 48c7 83e8 | 0000 0001 | 0000 00e9 | 2900 0000 | 4883 bbf0 - 0x00007f78c5a45730: 0000 0000 | 7517 4889 | bbf0 0000 | 0048 c783 | f800 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a45750: d800 0000 - - 0x00007f78c5a45754: ; {metadata(method data for {method} {0x00007f78900180d8} 'getName' '()Ljava/lang/String;' in 'java/lang/Class')} - 0x00007f78c5a45754: 0148 bf50 | fe57 9078 | 7f00 008b | 9f90 0000 | 0083 c302 | 899f 9000 | 0000 81e3 | feff 1f00 - 0x00007f78c5a45774: 85db 0f84 | b050 0000 | 8b76 3048 | c1e6 0348 - - 0x00007f78c5a45784: ; {metadata(method data for {method} {0x00007f78900180d8} 'getName' '()Ljava/lang/String;' in 'java/lang/Class')} - 0x00007f78c5a45784: 85f6 48bf | 50fe 5790 | 787f 0000 | bbd8 0000 | 0074 05bb | e800 0000 | 488b 041f | 488d 4001 - 0x00007f78c5a457a4: 4889 041f | 0f84 1800 - - 0x00007f78c5a457ac: ; {metadata(method data for {method} {0x00007f78900180d8} 'getName' '()Ljava/lang/String;' in 'java/lang/Class')} - 0x00007f78c5a457ac: 0000 48bf | 50fe 5790 | 787f 0000 | ff87 f800 | 0000 488b | fee9 3100 - - 0x00007f78c5a457c4: ; {oop(a 'java/lang/Class'{0x0000000421173878} = 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a457c4: 0000 48be | 7838 1721 | 0400 0000 - - 0x00007f78c5a457d0: ; {metadata(method data for {method} {0x00007f78900180d8} 'getName' '()Ljava/lang/String;' in 'java/lang/Class')} - 0x00007f78c5a457d0: 488b fe48 | bb50 fe57 | 9078 7f00 | 0048 8383 | 1001 0000 - - 0x00007f78c5a457e4: ; {optimized virtual_call} - 0x00007f78c5a457e4: 0166 90e8 - - 0x00007f78c5a457e8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [376]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual initClassName {reexecute=0 rethrow=0 return_oop=0} - ; - java.lang.Class::getName@14 - ; - kotlin.jvm.internal.Intrinsics::sanitizeStackTrace@3 (line 253) - ; - kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@11 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a457e8: f482 a606 - - 0x00007f78c5a457ec: ; {post_call_nop} - 0x00007f78c5a457ec: 0f1f 8400 | e436 002a | 488b f848 | 8b84 2478 | 0100 00e9 | b83f 0000 - - 0x00007f78c5a45804: ; {oop(a 'androidx/compose/runtime/snapshots/Snapshot$Companion'{0x0000000421145890})} - 0x00007f78c5a45804: 48be 9058 | 1421 0400 - - 0x00007f78c5a4580c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4580c: 0000 48bf | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb7 1010 | 0000 750d | 4883 8718 | 1000 0001 - 0x00007f78c5a4582c: e960 0000 | 0048 3bb7 | 2010 0000 | 750d 4883 | 8728 1000 | 0001 e94a | 0000 0048 | 83bf 1010 - 0x00007f78c5a4584c: 0000 0075 | 1748 89b7 | 1010 0000 | 48c7 8718 | 1000 0001 | 0000 00e9 | 2900 0000 | 4883 bf20 - 0x00007f78c5a4586c: 1000 0000 | 7517 4889 | b720 1000 | 0048 c787 | 2810 0000 | 0100 0000 | e908 0000 | 0048 8387 - 0x00007f78c5a4588c: 0810 0000 - - 0x00007f78c5a45890: ; {metadata(method data for {method} {0x00007f7890cec178} 'getCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot$Companion')} - 0x00007f78c5a45890: 0148 be68 | c366 9278 | 7f00 008b | be90 0000 | 0083 c702 | 89be 9000 | 0000 81e7 | feff 1f00 - 0x00007f78c5a458b0: 85ff 0f84 | 954f 0000 - - 0x00007f78c5a458b8: ; {metadata(method data for {method} {0x00007f7890cec178} 'getCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot$Companion')} - 0x00007f78c5a458b8: 48be 68c3 | 6692 787f | 0000 4883 | 86d8 0000 - - 0x00007f78c5a458c8: ; {metadata(method data for {method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a458c8: 0001 48be | 2846 4692 | 787f 0000 | 8bbe 9000 | 0000 83c7 | 0289 be90 | 0000 0081 | e7fe ff1f - 0x00007f78c5a458e8: 0085 ff0f | 847d 4f00 - - 0x00007f78c5a458f0: ; {oop(a 'androidx/compose/runtime/internal/SnapshotThreadLocal'{0x00000004214f8a00})} - 0x00007f78c5a458f0: 0048 be00 | 8a4f 2104 | 0000 0048 - - 0x00007f78c5a458fc: ; {metadata(method data for {method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a458fc: 8bfe 48bb | 2846 4692 | 787f 0000 | 8b7f 0848 | 3bbb e000 | 0000 750d | 4883 83e8 | 0000 0001 - 0x00007f78c5a4591c: e960 0000 | 0048 3bbb | f000 0000 | 750d 4883 | 83f8 0000 | 0001 e94a | 0000 0048 | 83bb e000 - 0x00007f78c5a4593c: 0000 0075 | 1748 89bb | e000 0000 | 48c7 83e8 | 0000 0001 | 0000 00e9 | 2900 0000 | 4883 bbf0 - 0x00007f78c5a4595c: 0000 0000 | 7517 4889 | bbf0 0000 | 0048 c783 | f800 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a4597c: d800 0000 | 0166 0f1f - - 0x00007f78c5a45984: ; {optimized virtual_call} - 0x00007f78c5a45984: 4400 00e8 - - 0x00007f78c5a45988: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.SnapshotKt::currentSnapshot@3 (line 1284) - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::getCurrent@0 (line 294) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@891 (line 1986) - 0x00007f78c5a45988: 84db 3307 - - 0x00007f78c5a4598c: ; {post_call_nop} - 0x00007f78c5a4598c: 0f1f 8400 | 8438 002b - - 0x00007f78c5a45994: ; {metadata(method data for {method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a45994: 4885 c048 | ba28 4646 | 9278 7f00 | 0075 0c80 | 8a01 0100 | 0001 e995 | 0000 008b | 7808 483b - 0x00007f78c5a459b4: ba10 0100 | 0075 0d48 | 8382 1801 | 0000 01e9 | 6000 0000 | 483b ba20 | 0100 0075 | 0d48 8382 - 0x00007f78c5a459d4: 2801 0000 | 01e9 4a00 | 0000 4883 | ba10 0100 | 0000 7517 | 4889 ba10 | 0100 0048 | c782 1801 - 0x00007f78c5a459f4: 0000 0100 | 0000 e929 | 0000 0048 | 83ba 2001 | 0000 0075 | 1748 89ba | 2001 0000 | 48c7 8228 - 0x00007f78c5a45a14: 0100 0001 | 0000 00e9 | 0800 0000 | 4883 8208 | 0100 0001 - - 0x00007f78c5a45a28: ; {metadata('androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a45a28: 48bf 5809 | 1821 0000 | 0000 8b50 | 0848 3b7a | 380f 8550 | 4e00 00e9 | 0000 0000 | 488b d048 - 0x00007f78c5a45a48: ; {metadata(method data for {method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a45a48: 85d2 48be | 2846 4692 | 787f 0000 | bf48 0100 | 0074 05bf | 3801 0000 | 488b 043e | 488d 4001 - 0x00007f78c5a45a68: 4889 043e | 0f84 0800 | 0000 488b | dae9 0a00 - - 0x00007f78c5a45a78: ; {oop(a 'androidx/compose/runtime/snapshots/GlobalSnapshot'{0x00000004214f8368})} - 0x00007f78c5a45a78: 0000 48bb | 6883 4f21 | 0400 0000 - - 0x00007f78c5a45a84: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45a84: 4885 db48 | b848 99d2 | 9278 7f00 | 0075 0c80 | 8831 1000 | 0001 e995 | 0000 008b | 7b08 483b - 0x00007f78c5a45aa4: b840 1000 | 0075 0d48 | 8380 4810 | 0000 01e9 | 6000 0000 | 483b b850 | 1000 0075 | 0d48 8380 - 0x00007f78c5a45ac4: 5810 0000 | 01e9 4a00 | 0000 4883 | b840 1000 | 0000 7517 | 4889 b840 | 1000 0048 | c780 4810 - 0x00007f78c5a45ae4: 0000 0100 | 0000 e929 | 0000 0048 | 83b8 5010 | 0000 0075 | 1748 89b8 | 5010 0000 | 48c7 8058 - 0x00007f78c5a45b04: 1000 0001 | 0000 00e9 | 0800 0000 | 4883 8038 | 1000 0001 - - 0x00007f78c5a45b18: ; {metadata('androidx/compose/runtime/snapshots/MutableSnapshot')} - 0x00007f78c5a45b18: 48bf e834 | 1821 0000 | 0000 8b43 | 0848 3b78 | 400f 8505 | 0000 00e9 | 0500 0000 | 4833 d2eb - 0x00007f78c5a45b38: 05ba 0100 | 0000 85d2 - - 0x00007f78c5a45b40: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45b40: 48ba 4899 | d292 787f | 0000 be68 | 1000 0074 | 05be 7810 | 0000 488b | 3c32 488d | 7f01 4889 - 0x00007f78c5a45b60: 3c32 0f84 | 7101 0000 - - 0x00007f78c5a45b68: ; {metadata('androidx/compose/runtime/snapshots/TransparentObserverMutableSnapshot')} - 0x00007f78c5a45b68: 48ba a0c1 | 3c21 0000 | 0000 498b | 87c8 0100 | 0048 8d78 | 5849 3bbf | d801 0000 | 0f87 0e4d - 0x00007f78c5a45b88: 0000 4989 | bfc8 0100 | 0048 c700 | 0100 0000 | 488b ca89 | 4808 4833 | c989 480c | 4833 c9bf - 0x00007f78c5a45ba8: 0900 0000 | 4889 4cf8 | 0848 ffcf | 75f6 4885 - - 0x00007f78c5a45bb8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45bb8: db49 b948 | 99d2 9278 | 7f00 0075 | 0d41 8089 | 8110 0000 | 01e9 9700 | 0000 448b | 4308 4d3b - 0x00007f78c5a45bd8: 8190 1000 | 0075 0d49 | 8381 9810 | 0000 01e9 | 6000 0000 | 4d3b 81a0 | 1000 0075 | 0d49 8381 - 0x00007f78c5a45bf8: a810 0000 | 01e9 4a00 | 0000 4983 | b990 1000 | 0000 7517 | 4d89 8190 | 1000 0049 | c781 9810 - 0x00007f78c5a45c18: 0000 0100 | 0000 e929 | 0000 0049 | 83b9 a010 | 0000 0075 | 174d 8981 | a010 0000 | 49c7 81a8 - 0x00007f78c5a45c38: 1000 0001 | 0000 00e9 | 0800 0000 | 4983 8188 | 1000 0001 - - 0x00007f78c5a45c4c: ; {metadata('androidx/compose/runtime/snapshots/MutableSnapshot')} - 0x00007f78c5a45c4c: 49b8 e834 | 1821 0000 | 0000 448b | 4b08 4d3b | 4140 0f85 | 414c 0000 | e900 0000 | 0048 8bd3 - 0x00007f78c5a45c6c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45c6c: 488b c849 | b848 99d2 | 9278 7f00 | 0049 8380 | b810 0000 - - 0x00007f78c5a45c80: ; {oop(nullptr)} - 0x00007f78c5a45c80: 0148 b900 | 0000 0000 - - 0x00007f78c5a45c88: ; {oop(nullptr)} - 0x00007f78c5a45c88: 0000 0049 | b800 0000 | 0000 0000 | 0041 b901 | 0000 00bf | 0000 0000 | 488b f048 | 8984 2488 - 0x00007f78c5a45ca8: 0100 000f - - 0x00007f78c5a45cac: ; {optimized virtual_call} - 0x00007f78c5a45cac: 1f40 00e8 - - 0x00007f78c5a45cb0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [392]=Oop [640]=Oop [648]=Oop } - ;*invokespecial {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@917 (line 1990) - 0x00007f78c5a45cb0: 6cc4 d9ff - - 0x00007f78c5a45cb4: ; {post_call_nop} - 0x00007f78c5a45cb4: 0f1f 8400 | ac3b 002c - - 0x00007f78c5a45cbc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45cbc: 48ba 4899 | d292 787f | 0000 ff82 | f810 0000 | 488b 8424 | 8801 0000 | e9b3 0000 | 000f 1f80 - 0x00007f78c5a45cdc: 0000 0000 - - 0x00007f78c5a45ce0: ; {other} - 0x00007f78c5a45ce0: e9d8 4b00 | 0000 0000 | 0000 80ba | 6801 0000 | 040f 85d5 | 4b00 0049 | 8b87 c801 | 0000 488d - 0x00007f78c5a45d00: 7838 493b | bfd8 0100 | 000f 87bd | 4b00 0049 | 89bf c801 | 0000 48c7 | 0001 0000 | 0048 8bca - 0x00007f78c5a45d20: 8948 0848 | 33c9 8948 | 0c48 33c9 | bf05 0000 | 0048 894c | f808 48ff | cf75 f648 - - 0x00007f78c5a45d3c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45d3c: 8bd0 48b9 | 4899 d292 | 787f 0000 | 4883 8110 | 1100 0001 - - 0x00007f78c5a45d50: ; {oop(nullptr)} - 0x00007f78c5a45d50: 488b d348 | b900 0000 | 0000 0000 | 0041 b801 | 0000 0041 | b900 0000 | 0048 8bf0 | 4889 8424 - 0x00007f78c5a45d70: 9001 0000 - - 0x00007f78c5a45d74: ; {optimized virtual_call} - 0x00007f78c5a45d74: 6666 90e8 - - 0x00007f78c5a45d78: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [400]=Oop [640]=Oop [648]=Oop } - ;*invokespecial {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@935 (line 1998) - 0x00007f78c5a45d78: 647d a606 - - 0x00007f78c5a45d7c: ; {post_call_nop} - 0x00007f78c5a45d7c: 0f1f 8400 | 743c 002d | 488b 8424 | 9001 0000 - - 0x00007f78c5a45d8c: ; implicit exception: dispatches to 0x00007f78c5a4a8d9 - 0x00007f78c5a45d8c: 483b 0048 - - 0x00007f78c5a45d90: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a45d90: 8bf0 48bf | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb7 5811 | 0000 750d | 4883 8760 | 1100 0001 - 0x00007f78c5a45db0: e960 0000 | 0048 3bb7 | 6811 0000 | 750d 4883 | 8770 1100 | 0001 e94a | 0000 0048 | 83bf 5811 - 0x00007f78c5a45dd0: 0000 0075 | 1748 89b7 | 5811 0000 | 48c7 8760 | 1100 0001 | 0000 00e9 | 2900 0000 | 4883 bf68 - 0x00007f78c5a45df0: 1100 0000 | 7517 4889 | b768 1100 | 0048 c787 | 7011 0000 | 0100 0000 | e908 0000 | 0048 8387 - 0x00007f78c5a45e10: 5011 0000 - - 0x00007f78c5a45e14: ; {metadata(method data for {method} {0x00007f7890ce9ae0} 'makeCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a45e14: 0148 be78 | bd8f 9278 | 7f00 008b | be90 0000 | 0083 c702 | 89be 9000 | 0000 81e7 | feff 1f00 - 0x00007f78c5a45e34: 85ff 0f84 | a24a 0000 - - 0x00007f78c5a45e3c: ; {metadata(method data for {method} {0x00007f7890ce9ae0} 'makeCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a45e3c: 48be 78bd | 8f92 787f | 0000 4883 | 86d8 0000 - - 0x00007f78c5a45e4c: ; {metadata(method data for {method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a45e4c: 0001 48be | b893 6d92 | 787f 0000 | 8bbe 9000 | 0000 83c7 | 0289 be90 | 0000 0081 | e7fe ff1f - 0x00007f78c5a45e6c: 0085 ff0f | 848a 4a00 - - 0x00007f78c5a45e74: ; {oop(a 'androidx/compose/runtime/internal/SnapshotThreadLocal'{0x00000004214f8a00})} - 0x00007f78c5a45e74: 0048 bf00 | 8a4f 2104 | 0000 0048 - - 0x00007f78c5a45e80: ; {metadata(method data for {method} {0x00007f7890ce9ae0} 'makeCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a45e80: 8bf7 48bb | 78bd 8f92 | 787f 0000 | 8b76 0848 | 3bb3 f000 | 0000 750d | 4883 83f8 | 0000 0001 - 0x00007f78c5a45ea0: e960 0000 | 0048 3bb3 | 0001 0000 | 750d 4883 | 8308 0100 | 0001 e94a | 0000 0048 | 83bb f000 - 0x00007f78c5a45ec0: 0000 0075 | 1748 89b3 | f000 0000 | 48c7 83f8 | 0000 0001 | 0000 00e9 | 2900 0000 | 4883 bb00 - 0x00007f78c5a45ee0: 0100 0000 | 7517 4889 | b300 0100 | 0048 c783 | 0801 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a45f00: e800 0000 | 0148 8bf7 | 4889 8424 | a001 0000 | 4889 bc24 | 9801 0000 | 0f1f 8000 - - 0x00007f78c5a45f1c: ; {optimized virtual_call} - 0x00007f78c5a45f1c: 0000 00e8 - - 0x00007f78c5a45f20: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [408]=Oop [416]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot::makeCurrent@3 (line 160) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@953 (line 2007) - 0x00007f78c5a45f20: ecd5 3307 - - 0x00007f78c5a45f24: ; {post_call_nop} - 0x00007f78c5a45f24: 0f1f 8400 | 1c3e 002e - - 0x00007f78c5a45f2c: ; {metadata(method data for {method} {0x00007f7890ce9ae0} 'makeCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a45f2c: 4885 c048 | bb78 bd8f | 9278 7f00 | 0075 0c80 | 8b11 0100 | 0001 e995 | 0000 008b | 7008 483b - 0x00007f78c5a45f4c: b320 0100 | 0075 0d48 | 8383 2801 | 0000 01e9 | 6000 0000 | 483b b330 | 0100 0075 | 0d48 8383 - 0x00007f78c5a45f6c: 3801 0000 | 01e9 4a00 | 0000 4883 | bb20 0100 | 0000 7517 | 4889 b320 | 0100 0048 | c783 2801 - 0x00007f78c5a45f8c: 0000 0100 | 0000 e929 | 0000 0048 | 83bb 3001 | 0000 0075 | 1748 89b3 | 3001 0000 | 48c7 8338 - 0x00007f78c5a45fac: 0100 0001 | 0000 00e9 | 0800 0000 | 4883 8318 | 0100 0001 - - 0x00007f78c5a45fc0: ; {metadata('androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a45fc0: 48be 5809 | 1821 0000 | 0000 8b58 | 0848 3b73 | 380f 8549 | 4900 00e9 | 0000 0000 - - 0x00007f78c5a45fdc: ; {metadata(method data for {method} {0x00007f7890ce9ae0} 'makeCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a45fdc: 488b f848 | ba78 bd8f | 9278 7f00 | 0048 8382 | 4801 0000 - - 0x00007f78c5a45ff0: ; {metadata(method data for {method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a45ff0: 0148 bab8 | 936d 9278 | 7f00 008b | b290 0000 | 0083 c602 | 89b2 9000 | 0000 81e6 | feff 1f00 - 0x00007f78c5a46010: 85f6 0f84 | 1149 0000 | 488b 9424 | 9801 0000 - - 0x00007f78c5a46020: ; {metadata(method data for {method} {0x00007f7890ce9ae0} 'makeCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a46020: 48be 78bd | 8f92 787f | 0000 8b52 | 0848 3b96 | 6001 0000 | 750d 4883 | 8668 0100 | 0001 e960 - 0x00007f78c5a46040: 0000 0048 | 3b96 7001 | 0000 750d | 4883 8678 | 0100 0001 | e94a 0000 | 0048 83be | 6001 0000 - 0x00007f78c5a46060: 0075 1748 | 8996 6001 | 0000 48c7 | 8668 0100 | 0001 0000 | 00e9 2900 | 0000 4883 | be70 0100 - 0x00007f78c5a46080: 0000 7517 | 4889 9670 | 0100 0048 | c786 7801 | 0000 0100 | 0000 e908 | 0000 0048 | 8386 5801 - 0x00007f78c5a460a0: 0000 0148 | 8b94 24a0 | 0100 0048 | 8bb4 2498 | 0100 0048 | 89bc 24a8 | 0100 000f - - 0x00007f78c5a460bc: ; {optimized virtual_call} - 0x00007f78c5a460bc: 1f40 00e8 - - 0x00007f78c5a460c0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual set {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot::makeCurrent@14 (line 161) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@953 (line 2007) - 0x00007f78c5a460c0: dc7e ccff - - 0x00007f78c5a460c4: ; {post_call_nop} - 0x00007f78c5a460c4: 0f1f 8400 | bc3f 002f | 488b bc24 | 8002 0000 - - 0x00007f78c5a460d4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a460d4: 4885 ff48 | ba48 99d2 | 9278 7f00 | 0075 0c80 | 8a79 1100 | 0001 e9af | 0000 008b | 4708 483b - 0x00007f78c5a460f4: 8288 1100 | 0075 0d48 | 8382 9011 | 0000 01e9 | 6000 0000 | 483b 8298 | 1100 0075 | 0d48 8382 - 0x00007f78c5a46114: a011 0000 | 01e9 4a00 | 0000 4883 | ba88 1100 | 0000 7517 | 4889 8288 | 1100 0048 | c782 9011 - 0x00007f78c5a46134: 0000 0100 | 0000 e929 | 0000 0048 | 83ba 9811 | 0000 0075 | 1748 8982 | 9811 0000 | 48c7 82a0 - 0x00007f78c5a46154: 1100 0001 | 0000 00e9 | 0800 0000 | 4883 8280 | 1100 0001 - - 0x00007f78c5a46168: ; {metadata('java/util/Collection')} - 0x00007f78c5a46168: 48b8 b05e | 0121 0000 | 0000 8b57 | 0848 3b42 | 200f 841f | 0000 0048 | 3bd0 0f84 | 1600 0000 - 0x00007f78c5a46188: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a46188: 5250 e851 | d8b3 065a | 5a85 d20f | 84b1 4700 | 00e9 0000 | 0000 488b | f748 3b06 - - 0x00007f78c5a461a4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a461a4: 488b de48 | b848 99d2 | 9278 7f00 | 008b 5b08 | 483b 98b8 | 1100 0075 | 0d48 8380 | c011 0000 - 0x00007f78c5a461c4: 01e9 6000 | 0000 483b | 98c8 1100 | 0075 0d48 | 8380 d011 | 0000 01e9 | 4a00 0000 | 4883 b8b8 - 0x00007f78c5a461e4: 1100 0000 | 7517 4889 | 98b8 1100 | 0048 c780 | c011 0000 | 0100 0000 | e929 0000 | 0048 83b8 - 0x00007f78c5a46204: c811 0000 | 0075 1748 | 8998 c811 | 0000 48c7 | 80d0 1100 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a46224: 80b0 1100 | 0001 6666 | 9048 b840 | 56de 6978 - - 0x00007f78c5a46234: ; {virtual_call} - 0x00007f78c5a46234: 7f00 00e8 - - 0x00007f78c5a46238: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface isEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@967 (line 720) - 0x00007f78c5a46238: c6bd 3307 - - 0x00007f78c5a4623c: ; {post_call_nop} - 0x00007f78c5a4623c: 0f1f 8400 | 3441 0030 - - 0x00007f78c5a46244: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46244: 85c0 48be | 4899 d292 | 787f 0000 | bfe0 1100 | 0075 05bf | f011 0000 | 488b 1c3e | 488d 5b01 - 0x00007f78c5a46264: 4889 1c3e | 0f85 1a00 - - 0x00007f78c5a4626c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4626c: 0000 48be | 4899 d292 | 787f 0000 | ff86 0012 | 0000 be01 | 0000 00e9 | 0500 0000 | be00 0000 - 0x00007f78c5a4628c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4628c: 0085 f648 | be48 99d2 | 9278 7f00 | 00bf 1812 | 0000 7405 | bf28 1200 | 0048 8b1c | 3e48 8d5b - 0x00007f78c5a462ac: 0148 891c | 3e0f 8485 | 0900 0048 | 8bb4 24d8 | 0000 0048 - - 0x00007f78c5a462c0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a462c0: 8bfe 48bb | 4899 d292 | 787f 0000 | 8b7f 0848 | 3bbb 4012 | 0000 750d | 4883 8348 | 1200 0001 - 0x00007f78c5a462e0: e960 0000 | 0048 3bbb | 5012 0000 | 750d 4883 | 8358 1200 | 0001 e94a | 0000 0048 | 83bb 4012 - 0x00007f78c5a46300: 0000 0075 | 1748 89bb | 4012 0000 | 48c7 8348 | 1200 0001 | 0000 00e9 | 2900 0000 | 4883 bb50 - 0x00007f78c5a46320: 1200 0000 | 7517 4889 | bb50 1200 | 0048 c783 | 5812 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a46340: 3812 0000 | 0148 8b7e - - 0x00007f78c5a46348: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46348: 1048 bb48 | 99d2 9278 | 7f00 0048 | 8383 6812 | 0000 0141 | ba01 0000 | 0049 03fa | 4889 7e10 - 0x00007f78c5a46368: 488b bc24 | 8002 0000 - - 0x00007f78c5a46370: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46370: 4885 ff48 | b948 99d2 | 9278 7f00 | 0075 0c80 | 8971 1200 | 0001 e9af | 0000 008b | 5708 483b - 0x00007f78c5a46390: 9180 1200 | 0075 0d48 | 8381 8812 | 0000 01e9 | 6000 0000 | 483b 9190 | 1200 0075 | 0d48 8381 - 0x00007f78c5a463b0: 9812 0000 | 01e9 4a00 | 0000 4883 | b980 1200 | 0000 7517 | 4889 9180 | 1200 0048 | c781 8812 - 0x00007f78c5a463d0: 0000 0100 | 0000 e929 | 0000 0048 | 83b9 9012 | 0000 0075 | 1748 8991 | 9012 0000 | 48c7 8198 - 0x00007f78c5a463f0: 1200 0001 | 0000 00e9 | 0800 0000 | 4883 8178 | 1200 0001 - - 0x00007f78c5a46404: ; {metadata('java/util/Collection')} - 0x00007f78c5a46404: 48ba b05e | 0121 0000 | 0000 8b4f | 0848 3b51 | 200f 841f | 0000 0048 | 3bca 0f84 | 1600 0000 - 0x00007f78c5a46424: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a46424: 5152 e8b5 | d5b3 0659 | 5985 c90f | 8423 4500 | 00e9 0000 | 0000 488b | df48 3b03 - - 0x00007f78c5a46440: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46440: 488b c348 | ba48 99d2 | 9278 7f00 | 008b 4008 | 483b 82b0 | 1200 0075 | 0d48 8382 | b812 0000 - 0x00007f78c5a46460: 01e9 6000 | 0000 483b | 82c0 1200 | 0075 0d48 | 8382 c812 | 0000 01e9 | 4a00 0000 | 4883 bab0 - 0x00007f78c5a46480: 1200 0000 | 7517 4889 | 82b0 1200 | 0048 c782 | b812 0000 | 0100 0000 | e929 0000 | 0048 83ba - 0x00007f78c5a464a0: c012 0000 | 0075 1748 | 8982 c012 | 0000 48c7 | 82c8 1200 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a464c0: 82a8 1200 | 0001 488b | f30f 1f40 | 0048 b868 | 56de 6978 - - 0x00007f78c5a464d4: ; {virtual_call} - 0x00007f78c5a464d4: 7f00 00e8 - - 0x00007f78c5a464d8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface size {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1013 (line 2010) - 0x00007f78c5a464d8: b67f 1607 - - 0x00007f78c5a464dc: ; {post_call_nop} - 0x00007f78c5a464dc: 0f1f 8400 | d443 0031 | 8984 24b4 | 0100 00bf | 0000 0000 | e975 0200 | 0066 6690 - - 0x00007f78c5a464f8: ; implicit exception: dispatches to 0x00007f78c5a4a966 - 0x00007f78c5a464f8: 483b 0348 - - 0x00007f78c5a464fc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a464fc: 8bd3 48be | 4899 d292 | 787f 0000 | 8b52 0848 | 3b96 0013 | 0000 750d | 4883 8608 | 1300 0001 - 0x00007f78c5a4651c: e960 0000 | 0048 3b96 | 1013 0000 | 750d 4883 | 8618 1300 | 0001 e94a | 0000 0048 | 83be 0013 - 0x00007f78c5a4653c: 0000 0075 | 1748 8996 | 0013 0000 | 48c7 8608 | 1300 0001 | 0000 00e9 | 2900 0000 | 4883 be10 - 0x00007f78c5a4655c: 1300 0000 | 7517 4889 | 9610 1300 | 0048 c786 | 1813 0000 | 0100 0000 | e908 0000 | 0048 8386 - 0x00007f78c5a4657c: f812 0000 | 0148 8bd7 | 488b f389 | bc24 b001 | 0000 0f1f | 8000 0000 | 0048 b890 | 56de 6978 - 0x00007f78c5a4659c: ; {virtual_call} - 0x00007f78c5a4659c: 7f00 00e8 - - 0x00007f78c5a465a0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1031 (line 2011) - 0x00007f78c5a465a0: 5e71 1907 - - 0x00007f78c5a465a4: ; {post_call_nop} - 0x00007f78c5a465a4: 0f1f 8400 | 9c44 0032 - - 0x00007f78c5a465ac: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a465ac: 4885 c048 | bb48 99d2 | 9278 7f00 | 0075 0c80 | 8b21 1300 | 0001 e9af | 0000 008b | 7808 483b - 0x00007f78c5a465cc: bb30 1300 | 0075 0d48 | 8383 3813 | 0000 01e9 | 6000 0000 | 483b bb40 | 1300 0075 | 0d48 8383 - 0x00007f78c5a465ec: 4813 0000 | 01e9 4a00 | 0000 4883 | bb30 1300 | 0000 7517 | 4889 bb30 | 1300 0048 | c783 3813 - 0x00007f78c5a4660c: 0000 0100 | 0000 e929 | 0000 0048 | 83bb 4013 | 0000 0075 | 1748 89bb | 4013 0000 | 48c7 8348 - 0x00007f78c5a4662c: 1300 0001 | 0000 00e9 | 0800 0000 | 4883 8328 | 1300 0001 - - 0x00007f78c5a46640: ; {metadata('androidx/compose/runtime/ControlledComposition')} - 0x00007f78c5a46640: 48bf b8d6 | 1821 0000 | 0000 8b58 | 0848 3b7b | 200f 841f | 0000 0048 | 3bdf 0f84 | 1600 0000 - 0x00007f78c5a46660: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a46660: 5357 e879 | d3b3 065b | 5b85 db0f | 84fa 4200 | 00e9 0000 | 0000 488b | d048 8bbc | 2488 0200 - 0x00007f78c5a46680: 0048 3b07 - - 0x00007f78c5a46684: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46684: 488b f748 | bb48 99d2 | 9278 7f00 | 008b 7608 | 483b b360 | 1300 0075 | 0d48 8383 | 6813 0000 - 0x00007f78c5a466a4: 01e9 6000 | 0000 483b | b370 1300 | 0075 0d48 | 8383 7813 | 0000 01e9 | 4a00 0000 | 4883 bb60 - 0x00007f78c5a466c4: 1300 0000 | 7517 4889 | b360 1300 | 0048 c783 | 6813 0000 | 0100 0000 | e929 0000 | 0048 83bb - 0x00007f78c5a466e4: 7013 0000 | 0075 1748 | 89b3 7013 | 0000 48c7 | 8378 1300 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a46704: 8358 1300 | 0001 488b - - 0x00007f78c5a4670c: ; {optimized virtual_call} - 0x00007f78c5a4670c: f766 90e8 - - 0x00007f78c5a46710: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1052 (line 730) - 0x00007f78c5a46710: fcfe 3e07 - - 0x00007f78c5a46714: ; {post_call_nop} - 0x00007f78c5a46714: 0f1f 8400 | 0c46 0033 | 8bbc 24b0 | 0100 00ff - - 0x00007f78c5a46724: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46724: c748 be48 | 99d2 9278 | 7f00 008b | 9e94 0000 | 0083 c302 | 899e 9400 | 0000 81e3 | fe3f 0000 - 0x00007f78c5a46744: 85db 0f84 | 2d42 0000 - - 0x00007f78c5a4674c: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1061 (line 2010) - 0x00007f78c5a4674c: 4d8b 5730 - - 0x00007f78c5a46750: ; {poll} - 0x00007f78c5a46750: 4185 0248 | be48 99d2 | 9278 7f00 | 00ff 8688 | 1300 008b | 8424 b401 | 0000 488b | 9c24 8002 - 0x00007f78c5a46770: 0000 3bf8 - - 0x00007f78c5a46774: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46774: 48ba 4899 | d292 787f | 0000 bee8 | 1200 007c | 05be d812 | 0000 4c8b | 0432 4d8d | 4001 4c89 - 0x00007f78c5a46794: 0432 0f8c | 5cfd ffff | 488b fb48 - - 0x00007f78c5a467a0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a467a0: 85ff 48ba | 4899 d292 | 787f 0000 | 750c 808a | 9913 0000 | 01e9 af00 | 0000 8b47 | 0848 3b82 - 0x00007f78c5a467c0: a813 0000 | 750d 4883 | 82b0 1300 | 0001 e960 | 0000 0048 | 3b82 b813 | 0000 750d | 4883 82c0 - 0x00007f78c5a467e0: 1300 0001 | e94a 0000 | 0048 83ba | a813 0000 | 0075 1748 | 8982 a813 | 0000 48c7 | 82b0 1300 - 0x00007f78c5a46800: 0001 0000 | 00e9 2900 | 0000 4883 | bab8 1300 | 0000 7517 | 4889 82b8 | 1300 0048 | c782 c013 - 0x00007f78c5a46820: 0000 0100 | 0000 e908 | 0000 0048 | 8382 a013 - - 0x00007f78c5a46830: ; {metadata('java/util/Collection')} - 0x00007f78c5a46830: 0000 0148 | b8b0 5e01 | 2100 0000 | 008b 5708 | 483b 4220 | 0f84 1f00 | 0000 483b | d00f 8416 - 0x00007f78c5a46850: 0000 0052 - - 0x00007f78c5a46854: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a46854: 50e8 86d1 | b306 5a5a | 85d2 0f84 | 3641 0000 | e900 0000 | 0048 8bf7 - - 0x00007f78c5a4686c: ; implicit exception: dispatches to 0x00007f78c5a4a9a3 - 0x00007f78c5a4686c: 483b 0648 - - 0x00007f78c5a46870: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46870: 8bde 48b8 | 4899 d292 | 787f 0000 | 8b5b 0848 | 3b98 d813 | 0000 750d | 4883 80e0 | 1300 0001 - 0x00007f78c5a46890: e960 0000 | 0048 3b98 | e813 0000 | 750d 4883 | 80f0 1300 | 0001 e94a | 0000 0048 | 83b8 d813 - 0x00007f78c5a468b0: 0000 0075 | 1748 8998 | d813 0000 | 48c7 80e0 | 1300 0001 | 0000 00e9 | 2900 0000 | 4883 b8e8 - 0x00007f78c5a468d0: 1300 0000 | 7517 4889 | 98e8 1300 | 0048 c780 | f013 0000 | 0100 0000 | e908 0000 | 0048 8380 - 0x00007f78c5a468f0: d013 0000 | 0148 b8b8 | 56de 6978 - - 0x00007f78c5a468fc: ; {virtual_call} - 0x00007f78c5a468fc: 7f00 00e8 - - 0x00007f78c5a46900: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface size {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1080 (line 2015) - 0x00007f78c5a46900: 8e7b 1607 - - 0x00007f78c5a46904: ; {post_call_nop} - 0x00007f78c5a46904: 0f1f 8400 | fc47 0035 | 8984 24bc | 0100 00bf | 0000 0000 | e935 0200 | 0066 6690 - - 0x00007f78c5a46920: ; implicit exception: dispatches to 0x00007f78c5a4a9a8 - 0x00007f78c5a46920: 483b 0348 - - 0x00007f78c5a46924: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46924: 8bd3 48be | 4899 d292 | 787f 0000 | 8b52 0848 | 3b96 2814 | 0000 750d | 4883 8630 | 1400 0001 - 0x00007f78c5a46944: e960 0000 | 0048 3b96 | 3814 0000 | 750d 4883 | 8640 1400 | 0001 e94a | 0000 0048 | 83be 2814 - 0x00007f78c5a46964: 0000 0075 | 1748 8996 | 2814 0000 | 48c7 8630 | 1400 0001 | 0000 00e9 | 2900 0000 | 4883 be38 - 0x00007f78c5a46984: 1400 0000 | 7517 4889 | 9638 1400 | 0048 c786 | 4014 0000 | 0100 0000 | e908 0000 | 0048 8386 - 0x00007f78c5a469a4: 2014 0000 | 0148 8bd7 | 488b f389 | bc24 b801 | 0000 0f1f | 8000 0000 | 0048 b8e0 | 56de 6978 - 0x00007f78c5a469c4: ; {virtual_call} - 0x00007f78c5a469c4: 7f00 00e8 - - 0x00007f78c5a469c8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1098 (line 2016) - 0x00007f78c5a469c8: 366d 1907 - - 0x00007f78c5a469cc: ; {post_call_nop} - 0x00007f78c5a469cc: 0f1f 8400 | c448 0036 - - 0x00007f78c5a469d4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a469d4: 4885 c048 | be48 99d2 | 9278 7f00 | 0075 0c80 | 8e49 1400 | 0001 e9af | 0000 008b | 5808 483b - 0x00007f78c5a469f4: 9e58 1400 | 0075 0d48 | 8386 6014 | 0000 01e9 | 6000 0000 | 483b 9e68 | 1400 0075 | 0d48 8386 - 0x00007f78c5a46a14: 7014 0000 | 01e9 4a00 | 0000 4883 | be58 1400 | 0000 7517 | 4889 9e58 | 1400 0048 | c786 6014 - 0x00007f78c5a46a34: 0000 0100 | 0000 e929 | 0000 0048 | 83be 6814 | 0000 0075 | 1748 899e | 6814 0000 | 48c7 8670 - 0x00007f78c5a46a54: 1400 0001 | 0000 00e9 | 0800 0000 | 4883 8650 | 1400 0001 - - 0x00007f78c5a46a68: ; {metadata('androidx/compose/runtime/ControlledComposition')} - 0x00007f78c5a46a68: 48bb b8d6 | 1821 0000 | 0000 8b70 | 0848 3b5e | 200f 841f | 0000 0048 | 3bf3 0f84 | 1600 0000 - 0x00007f78c5a46a88: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a46a88: 5653 e851 | cfb3 065e | 5e85 f60f | 8414 3f00 | 00e9 0000 | 0000 488b | f048 85f6 | 0f84 1b00 - 0x00007f78c5a46aa8: ; {metadata('androidx/compose/runtime/CompositionImpl')} - 0x00007f78c5a46aa8: 0000 48b8 | 3079 1921 | 0000 0000 | 8b5e 0848 | 3bc3 0f85 | f63e 0000 | e900 0000 | 0048 8bfe - 0x00007f78c5a46ac8: ; implicit exception: dispatches to 0x00007f78c5a4a9bb - 0x00007f78c5a46ac8: 483b 0648 - - 0x00007f78c5a46acc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46acc: 8bfe 48bb | 4899 d292 | 787f 0000 - - 0x00007f78c5a46ad8: ; {metadata('androidx/compose/runtime/CompositionImpl')} - 0x00007f78c5a46ad8: 49ba 3079 | 1921 0000 | 0000 4c89 | 9388 1400 | 0048 8383 | 9014 0000 | 0166 0f1f - - 0x00007f78c5a46af4: ; {optimized virtual_call} - 0x00007f78c5a46af4: 4400 00e8 - - 0x00007f78c5a46af8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface applyChanges {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1117 (line 733) - 0x00007f78c5a46af8: d146 0000 - - 0x00007f78c5a46afc: ; {post_call_nop} - 0x00007f78c5a46afc: 0f1f 8400 | f449 0037 | 8bbc 24b8 | 0100 00ff - - 0x00007f78c5a46b0c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46b0c: c748 be48 | 99d2 9278 | 7f00 008b | 9e94 0000 | 0083 c302 | 899e 9400 | 0000 81e3 | fe3f 0000 - 0x00007f78c5a46b2c: 85db 0f84 | 8c3e 0000 - - 0x00007f78c5a46b34: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1127 (line 2015) - 0x00007f78c5a46b34: 4d8b 5730 - - 0x00007f78c5a46b38: ; {poll} - 0x00007f78c5a46b38: 4185 0248 | be48 99d2 | 9278 7f00 | 00ff 86b0 | 1400 008b | 8424 bc01 | 0000 488b | 9c24 8002 - 0x00007f78c5a46b58: 0000 3bf8 - - 0x00007f78c5a46b5c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46b5c: 48ba 4899 | d292 787f | 0000 be10 | 1400 007c | 05be 0014 | 0000 488b | 0c32 488d | 4901 4889 - 0x00007f78c5a46b7c: 0c32 0f8c | 9cfd ffff | 488b fb48 | 3b07 488b - - 0x00007f78c5a46b8c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46b8c: f748 bb48 | 99d2 9278 | 7f00 008b | 7608 483b | b3d0 1400 | 0075 0d48 | 8383 d814 | 0000 01e9 - 0x00007f78c5a46bac: 6000 0000 | 483b b3e0 | 1400 0075 | 0d48 8383 | e814 0000 | 01e9 4a00 | 0000 4883 | bbd0 1400 - 0x00007f78c5a46bcc: 0000 7517 | 4889 b3d0 | 1400 0048 | c783 d814 | 0000 0100 | 0000 e929 | 0000 0048 | 83bb e014 - 0x00007f78c5a46bec: 0000 0075 | 1748 89b3 | e014 0000 | 48c7 83e8 | 1400 0001 | 0000 00e9 | 0800 0000 | 4883 83c8 - 0x00007f78c5a46c0c: 1400 0001 | 488b f766 | 9048 b808 | 57de 6978 - - 0x00007f78c5a46c1c: ; {virtual_call} - 0x00007f78c5a46c1c: 7f00 00e8 - - 0x00007f78c5a46c20: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1133 (line 740) - 0x00007f78c5a46c20: 5e54 3807 - - 0x00007f78c5a46c24: ; {post_call_nop} - 0x00007f78c5a46c24: 0f1f 8400 | 1c4b 0039 - - 0x00007f78c5a46c2c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46c2c: 48ba 4899 | d292 787f | 0000 ff82 | f814 0000 | 488b bc24 | 0001 0000 - - 0x00007f78c5a46c44: ; implicit exception: dispatches to 0x00007f78c5a4a9e6 - 0x00007f78c5a46c44: 483b 0748 - - 0x00007f78c5a46c48: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46c48: 8bd7 48be | 4899 d292 | 787f 0000 | 8b52 0848 | 3b96 2816 | 0000 750d | 4883 8630 | 1600 0001 - 0x00007f78c5a46c68: e960 0000 | 0048 3b96 | 3816 0000 | 750d 4883 | 8640 1600 | 0001 e94a | 0000 0048 | 83be 2816 - 0x00007f78c5a46c88: 0000 0075 | 1748 8996 | 2816 0000 | 48c7 8630 | 1600 0001 | 0000 00e9 | 2900 0000 | 4883 be38 - 0x00007f78c5a46ca8: 1600 0000 | 7517 4889 | 9638 1600 | 0048 c786 | 4016 0000 | 0100 0000 | e908 0000 | 0048 8386 - 0x00007f78c5a46cc8: 2016 0000 - - 0x00007f78c5a46ccc: ; {metadata(method data for {method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a46ccc: 0148 bab8 | 51a9 9278 | 7f00 008b | b290 0000 | 0083 c602 | 89b2 9000 | 0000 81e6 | feff 1f00 - 0x00007f78c5a46cec: 85f6 0f84 | f73c 0000 | 8b57 1085 - - 0x00007f78c5a46cf8: ; {metadata(method data for {method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a46cf8: d248 bab8 | 51a9 9278 | 7f00 00be | d800 0000 | 7405 bee8 | 0000 0048 | 8b1c 3248 | 8d5b 0148 - 0x00007f78c5a46d18: 891c 320f | 841a 0000 - - 0x00007f78c5a46d20: ; {metadata(method data for {method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a46d20: 0048 bab8 | 51a9 9278 | 7f00 00ff | 82f8 0000 | 00ba 0100 | 0000 e905 | 0000 00ba | 0000 0000 - 0x00007f78c5a46d40: 83e2 0185 - - 0x00007f78c5a46d44: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46d44: d248 ba48 | 99d2 9278 | 7f00 00be | 5016 0000 | 7405 be60 | 1600 0048 | 8b1c 3248 | 8d5b 0148 - 0x00007f78c5a46d64: 891c 320f | 84b7 0500 | 0048 8b9c | 2488 0200 | 0048 3b03 - - 0x00007f78c5a46d78: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46d78: 488b d348 | be48 99d2 | 9278 7f00 | 008b 5208 | 483b 96a8 | 1600 0075 | 0d48 8386 | b016 0000 - 0x00007f78c5a46d98: 01e9 6000 | 0000 483b | 96b8 1600 | 0075 0d48 | 8386 c016 | 0000 01e9 | 4a00 0000 | 4883 bea8 - 0x00007f78c5a46db8: 1600 0000 | 7517 4889 | 96a8 1600 | 0048 c786 | b016 0000 | 0100 0000 | e929 0000 | 0048 83be - 0x00007f78c5a46dd8: b816 0000 | 0075 1748 | 8996 b816 | 0000 48c7 | 86c0 1600 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a46df8: 86a0 1600 | 0001 488b | d748 8bf3 - - 0x00007f78c5a46e04: ; {optimized virtual_call} - 0x00007f78c5a46e04: 6666 90e8 - - 0x00007f78c5a46e08: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual plusAssign {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1244 (line 746) - 0x00007f78c5a46e08: d46c a606 - - 0x00007f78c5a46e0c: ; {post_call_nop} - 0x00007f78c5a46e0c: 0f1f 8400 | 044d 003a | 488b bc24 | 0001 0000 | 8b5f 1848 | c1e3 0348 | 899c 24c8 | 0100 008b - 0x00007f78c5a46e2c: 4714 48c1 | e003 4889 | 8424 e001 | 0000 8b48 | 0c83 e902 | 898c 24c0 | 0100 0085 - - 0x00007f78c5a46e48: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46e48: c948 be48 | 99d2 9278 | 7f00 00ba | 0017 0000 | 7c05 ba10 | 1700 004c | 8b04 164d | 8d40 014c - 0x00007f78c5a46e68: 8904 160f | 8c03 0400 | 00ba 0000 | 0000 6690 | 3b50 0c0f | 8395 3b00 | 0048 63f2 | 4c8b 44f0 - 0x00007f78c5a46e88: 1049 8bf0 | 49ba ffff | ffff ffff | ffff 4933 | f24c 8bc9 | b907 0000 | 0048 d3e6 | 4923 f048 - 0x00007f78c5a46ea8: b980 8080 | 8080 8080 | 8048 23f1 - - 0x00007f78c5a46eb4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46eb4: 483b f148 | be48 99d2 | 9278 7f00 | 00b9 2017 | 0000 7405 | b930 1700 | 004c 8b1c | 0e4d 8d5b - 0x00007f78c5a46ed4: 014c 891c | 0e0f 840a | 0300 0048 | 8bf2 412b | f183 f6ff | c1ee 1fb9 | 0800 0000 | 2bce 898c - 0x00007f78c5a46ef4: 24d8 0100 | 0041 bb00 | 0000 0090 - - 0x00007f78c5a46f00: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46f00: 443b d948 | be48 99d2 | 9278 7f00 | 0041 bd40 | 1700 007d | 0641 bd50 | 1700 004e | 8b34 2e4d - 0x00007f78c5a46f20: 8d76 014e | 8934 2e0f | 8d83 0200 | 0049 8bf0 | 49ba ff00 | 0000 0000 | 0000 4923 | f241 bd80 - 0x00007f78c5a46f40: 0000 0049 - - 0x00007f78c5a46f44: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46f44: 3bf5 48be | 4899 d292 | 787f 0000 | 41bd 6017 | 0000 7d06 | 41bd 7017 | 0000 4e8b | 342e 4d8d - 0x00007f78c5a46f64: 7601 4e89 | 342e 0f8d | 1a00 0000 - - 0x00007f78c5a46f70: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46f70: 48be 4899 | d292 787f | 0000 ff86 | 8017 0000 | be01 0000 | 00e9 0500 | 0000 be00 | 0000 0085 - 0x00007f78c5a46f90: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46f90: f648 be48 | 99d2 9278 | 7f00 0041 | bda8 1700 | 0075 0641 | bd98 1700 | 004e 8b34 | 2e4d 8d76 - 0x00007f78c5a46fb0: 014e 8934 | 2e0f 851c | 0000 0044 | 899c 24c4 | 0100 004c | 8984 24d0 | 0100 0089 | 9424 dc01 - 0x00007f78c5a46fd0: 0000 e965 | 0100 004c | 8984 24d0 | 0100 0048 | 8bf2 c1e6 | 0341 03f3 - - 0x00007f78c5a46fe8: ; implicit exception: dispatches to 0x00007f78c5a4aa24 - 0x00007f78c5a46fe8: 3b73 0c0f | 833d 3a00 | 0048 63f6 | 8b74 b310 | 48c1 e603 - - 0x00007f78c5a46ffc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a46ffc: 4885 f648 | bf48 99d2 | 9278 7f00 | 0075 0c80 | 8fb1 1700 | 0001 e9b1 | 0000 0044 | 8b76 084c - 0x00007f78c5a4701c: 3bb7 c017 | 0000 750d | 4883 87c8 | 1700 0001 | e960 0000 | 004c 3bb7 | d017 0000 | 750d 4883 - 0x00007f78c5a4703c: 87d8 1700 | 0001 e94a | 0000 0048 | 83bf c017 | 0000 0075 | 174c 89b7 | c017 0000 | 48c7 87c8 - 0x00007f78c5a4705c: 1700 0001 | 0000 00e9 | 2900 0000 | 4883 bfd0 | 1700 0000 | 7517 4c89 | b7d0 1700 | 0048 c787 - 0x00007f78c5a4707c: d817 0000 | 0100 0000 | e908 0000 | 0048 8387 | b817 0000 - - 0x00007f78c5a47090: ; {metadata('androidx/compose/runtime/ControlledComposition')} - 0x00007f78c5a47090: 0149 beb8 | d618 2100 | 0000 008b | 7e08 4c3b | 7720 0f84 | 2000 0000 | 493b fe0f | 8417 0000 - 0x00007f78c5a470b0: 0057 4156 - - 0x00007f78c5a470b4: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a470b4: e827 c9b3 | 065f 5f85 | ff0f 8479 | 3900 00e9 | 0000 0000 | 4885 f60f | 841c 0000 - - 0x00007f78c5a470d0: ; {metadata('androidx/compose/runtime/CompositionImpl')} - 0x00007f78c5a470d0: 0049 be30 | 7919 2100 | 0000 0044 | 8b6e 084d | 3bf5 0f85 | 5d39 0000 | e900 0000 | 0048 8bfe - 0x00007f78c5a470f0: ; implicit exception: dispatches to 0x00007f78c5a4aa4a - 0x00007f78c5a470f0: 483b 0648 - - 0x00007f78c5a470f4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a470f4: 8bfe 48bb | 4899 d292 | 787f 0000 - - 0x00007f78c5a47100: ; {metadata('androidx/compose/runtime/CompositionImpl')} - 0x00007f78c5a47100: 49ba 3079 | 1921 0000 | 0000 4c89 | 93f0 1700 | 0048 8383 | f817 0000 | 0189 9424 | dc01 0000 - 0x00007f78c5a47120: 4489 9c24 | c401 0000 | 0f1f 8000 - - 0x00007f78c5a4712c: ; {optimized virtual_call} - 0x00007f78c5a4712c: 0000 00e8 - - 0x00007f78c5a47130: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface applyLateChanges {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1418 (line 748) - 0x00007f78c5a47130: ac69 a606 - - 0x00007f78c5a47134: ; {post_call_nop} - 0x00007f78c5a47134: 0f1f 8400 | 2c50 003b | b908 0000 | 004c 8b84 | 24d0 0100 | 0049 d3f8 | 448b 9c24 | c401 0000 - 0x00007f78c5a47154: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47154: 41ff c348 | be48 99d2 | 9278 7f00 | 008b be94 | 0000 0083 | c702 89be | 9400 0000 | 81e7 fe3f - 0x00007f78c5a47174: 0000 85ff | 0f84 d138 | 0000 4d8b - - 0x00007f78c5a47180: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1436 (line 2034) - ; {poll} - 0x00007f78c5a47180: 5730 4185 - - 0x00007f78c5a47184: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47184: 0248 be48 | 99d2 9278 | 7f00 00ff | 8618 1800 | 008b 8c24 | d801 0000 | 8b94 24dc | 0100 0048 - 0x00007f78c5a471a4: 8b9c 24c8 | 0100 00e9 | 50fd ffff | 8994 24dc | 0100 0083 - - 0x00007f78c5a471b8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a471b8: f908 48be | 4899 d292 | 787f 0000 | bf30 1800 | 0075 05bf | 4018 0000 | 488b 1c3e | 488d 5b01 - 0x00007f78c5a471d8: 4889 1c3e | 0f85 9200 | 0000 8b94 | 24dc 0100 | 003b 9424 | c001 0000 - - 0x00007f78c5a471f0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a471f0: 48be 4899 | d292 787f | 0000 bf50 | 1800 0074 | 05bf 6018 | 0000 488b | 1c3e 488d | 5b01 4889 - 0x00007f78c5a47210: 1c3e 0f84 | 5c00 0000 - - 0x00007f78c5a47218: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47218: ffc2 48be | 4899 d292 | 787f 0000 | 8bbe 9400 | 0000 83c7 | 0289 be94 | 0000 0081 | e7fe 3f00 - 0x00007f78c5a47238: 0085 ff0f | 842f 3800 | 004d 8b57 - - 0x00007f78c5a47244: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1456 (line 2029) - ; {poll} - 0x00007f78c5a47244: 3041 8502 - - 0x00007f78c5a47248: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47248: 48be 4899 | d292 787f | 0000 ff86 | 7018 0000 | 8b8c 24c0 | 0100 0048 | 8b84 24e0 | 0100 0048 - 0x00007f78c5a47268: 8b9c 24c8 | 0100 00e9 | 04fc ffff | 488b bc24 | 0001 0000 - - 0x00007f78c5a4727c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4727c: 48be 4899 | d292 787f | 0000 8b7f | 0848 3bbe | 9018 0000 | 750d 4883 | 8698 1800 | 0001 e960 - 0x00007f78c5a4729c: 0000 0048 | 3bbe a018 | 0000 750d | 4883 86a8 | 1800 0001 | e94a 0000 | 0048 83be | 9018 0000 - 0x00007f78c5a472bc: 0075 1748 | 89be 9018 | 0000 48c7 | 8698 1800 | 0001 0000 | 00e9 2900 | 0000 4883 | bea0 1800 - 0x00007f78c5a472dc: 0000 7517 | 4889 bea0 | 1800 0048 | c786 a818 | 0000 0100 | 0000 e908 | 0000 0048 | 8386 8818 - 0x00007f78c5a472fc: 0000 0148 | 8bb4 2400 - - 0x00007f78c5a47304: ; {optimized virtual_call} - 0x00007f78c5a47304: 0100 00e8 - - 0x00007f78c5a47308: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1463 (line 755) - 0x00007f78c5a47308: d467 a606 - - 0x00007f78c5a4730c: ; {post_call_nop} - 0x00007f78c5a4730c: 0f1f 8400 | 0452 003e - - 0x00007f78c5a47314: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47314: 48b9 4899 | d292 787f | 0000 ff81 | b818 0000 | 488b bc24 | 8802 0000 - - 0x00007f78c5a4732c: ; implicit exception: dispatches to 0x00007f78c5a4aa91 - 0x00007f78c5a4732c: 483b 0748 - - 0x00007f78c5a47330: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47330: 8bcf 48be | 4899 d292 | 787f 0000 | 8b49 0848 | 3b8e e819 | 0000 750d | 4883 86f0 | 1900 0001 - 0x00007f78c5a47350: e960 0000 | 0048 3b8e | f819 0000 | 750d 4883 | 8600 1a00 | 0001 e94a | 0000 0048 | 83be e819 - 0x00007f78c5a47370: 0000 0075 | 1748 898e | e819 0000 | 48c7 86f0 | 1900 0001 | 0000 00e9 | 2900 0000 | 4883 bef8 - 0x00007f78c5a47390: 1900 0000 | 7517 4889 | 8ef8 1900 | 0048 c786 | 001a 0000 | 0100 0000 | e908 0000 | 0048 8386 - 0x00007f78c5a473b0: e019 0000 - - 0x00007f78c5a473b4: ; {metadata(method data for {method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a473b4: 0148 b9b8 | 51a9 9278 | 7f00 008b | b190 0000 | 0083 c602 | 89b1 9000 | 0000 81e6 | feff 1f00 - 0x00007f78c5a473d4: 85f6 0f84 | ba36 0000 | 8b4f 1085 - - 0x00007f78c5a473e0: ; {metadata(method data for {method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a473e0: c948 b9b8 | 51a9 9278 | 7f00 00be | d800 0000 | 7405 bee8 | 0000 0048 | 8b1c 3148 | 8d5b 0148 - 0x00007f78c5a47400: 891c 310f | 841a 0000 - - 0x00007f78c5a47408: ; {metadata(method data for {method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a47408: 0048 b9b8 | 51a9 9278 | 7f00 00ff | 81f8 0000 | 00b9 0100 | 0000 e905 | 0000 00b9 | 0000 0000 - 0x00007f78c5a47428: 83e1 0185 - - 0x00007f78c5a4742c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4742c: c948 b948 | 99d2 9278 | 7f00 00be | 101a 0000 | 7405 be20 | 1a00 0048 | 8b1c 3148 | 8d5b 0148 - 0x00007f78c5a4744c: 891c 310f | 8407 0500 | 008b 5f18 | 48c1 e303 | 4889 9c24 | f001 0000 | 8b47 1448 | c1e0 0348 - 0x00007f78c5a4746c: 8984 2408 | 0200 008b | 480c 83e9 | 0289 8c24 | e801 0000 - - 0x00007f78c5a47480: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47480: 85c9 48be | 4899 d292 | 787f 0000 | ba60 1a00 | 007c 05ba | 701a 0000 | 4c8b 0416 | 4d8d 4001 - 0x00007f78c5a474a0: 4c89 0416 | 0f8c 0204 | 0000 ba00 | 0000 0090 | 3b50 0c0f | 8303 3600 | 0048 63f2 | 4c8b 44f0 - 0x00007f78c5a474c0: 1049 8bf0 | 49ba ffff | ffff ffff | ffff 4933 | f24c 8bc9 | b907 0000 | 0048 d3e6 | 4923 f048 - 0x00007f78c5a474e0: b980 8080 | 8080 8080 | 8048 23f1 - - 0x00007f78c5a474ec: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a474ec: 483b f148 | be48 99d2 | 9278 7f00 | 00b9 801a | 0000 7405 | b990 1a00 | 004c 8b1c | 0e4d 8d5b - 0x00007f78c5a4750c: 014c 891c | 0e0f 840a | 0300 0048 | 8bf2 412b | f183 f6ff | c1ee 1fb9 | 0800 0000 | 2bce 898c - 0x00007f78c5a4752c: 2400 0200 | 0041 bb00 | 0000 0090 - - 0x00007f78c5a47538: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47538: 443b d948 | be48 99d2 | 9278 7f00 | 0041 bda0 | 1a00 007d | 0641 bdb0 | 1a00 004e | 8b34 2e4d - 0x00007f78c5a47558: 8d76 014e | 8934 2e0f | 8d83 0200 | 0049 8bf0 | 49ba ff00 | 0000 0000 | 0000 4923 | f241 bd80 - 0x00007f78c5a47578: 0000 0049 - - 0x00007f78c5a4757c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4757c: 3bf5 48be | 4899 d292 | 787f 0000 | 41bd c01a | 0000 7d06 | 41bd d01a | 0000 4e8b | 342e 4d8d - 0x00007f78c5a4759c: 7601 4e89 | 342e 0f8d | 1a00 0000 - - 0x00007f78c5a475a8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a475a8: 48be 4899 | d292 787f | 0000 ff86 | e01a 0000 | be01 0000 | 00e9 0500 | 0000 be00 | 0000 0085 - 0x00007f78c5a475c8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a475c8: f648 be48 | 99d2 9278 | 7f00 0041 | bd08 1b00 | 0075 0641 | bdf8 1a00 | 004e 8b34 | 2e4d 8d76 - 0x00007f78c5a475e8: 014e 8934 | 2e0f 851c | 0000 0044 | 899c 24ec | 0100 004c | 8984 24f8 | 0100 0089 | 9424 0402 - 0x00007f78c5a47608: 0000 e965 | 0100 004c | 8984 24f8 | 0100 0048 | 8bf2 c1e6 | 0341 03f3 - - 0x00007f78c5a47620: ; implicit exception: dispatches to 0x00007f78c5a4aaca - 0x00007f78c5a47620: 3b73 0c0f | 83ab 3400 | 0048 63f6 | 8b74 b310 | 48c1 e603 - - 0x00007f78c5a47634: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47634: 4885 f648 | bf48 99d2 | 9278 7f00 | 0075 0c80 | 8f11 1b00 | 0001 e9b1 | 0000 0044 | 8b76 084c - 0x00007f78c5a47654: 3bb7 201b | 0000 750d | 4883 8728 | 1b00 0001 | e960 0000 | 004c 3bb7 | 301b 0000 | 750d 4883 - 0x00007f78c5a47674: 8738 1b00 | 0001 e94a | 0000 0048 | 83bf 201b | 0000 0075 | 174c 89b7 | 201b 0000 | 48c7 8728 - 0x00007f78c5a47694: 1b00 0001 | 0000 00e9 | 2900 0000 | 4883 bf30 | 1b00 0000 | 7517 4c89 | b730 1b00 | 0048 c787 - 0x00007f78c5a476b4: 381b 0000 | 0100 0000 | e908 0000 | 0048 8387 | 181b 0000 - - 0x00007f78c5a476c8: ; {metadata('androidx/compose/runtime/ControlledComposition')} - 0x00007f78c5a476c8: 0149 beb8 | d618 2100 | 0000 008b | 7e08 4c3b | 7720 0f84 | 2000 0000 | 493b fe0f | 8417 0000 - 0x00007f78c5a476e8: 0057 4156 - - 0x00007f78c5a476ec: ; {runtime_call C1 Runtime slow_subtype_check_blob} - 0x00007f78c5a476ec: e8ef c2b3 | 065f 5f85 | ff0f 84e7 | 3300 00e9 | 0000 0000 | 4885 f60f | 841c 0000 - - 0x00007f78c5a47708: ; {metadata('androidx/compose/runtime/CompositionImpl')} - 0x00007f78c5a47708: 0049 be30 | 7919 2100 | 0000 0044 | 8b6e 084d | 3bf5 0f85 | cb33 0000 | e900 0000 | 0048 8bfe - 0x00007f78c5a47728: ; implicit exception: dispatches to 0x00007f78c5a4aaf0 - 0x00007f78c5a47728: 483b 0648 - - 0x00007f78c5a4772c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4772c: 8bfe 48bb | 4899 d292 | 787f 0000 - - 0x00007f78c5a47738: ; {metadata('androidx/compose/runtime/CompositionImpl')} - 0x00007f78c5a47738: 49ba 3079 | 1921 0000 | 0000 4c89 | 9350 1b00 | 0048 8383 | 581b 0000 | 0189 9424 | 0402 0000 - 0x00007f78c5a47758: 4489 9c24 | ec01 0000 | 0f1f 8000 - - 0x00007f78c5a47764: ; {optimized virtual_call} - 0x00007f78c5a47764: 0000 00e8 - - 0x00007f78c5a47768: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface changesApplied {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1732 (line 762) - 0x00007f78c5a47768: 913a 0000 - - 0x00007f78c5a4776c: ; {post_call_nop} - 0x00007f78c5a4776c: 0f1f 8400 | 6456 003f | b908 0000 | 004c 8b84 | 24f8 0100 | 0049 d3f8 | 448b 9c24 | ec01 0000 - 0x00007f78c5a4778c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4778c: 41ff c348 | be48 99d2 | 9278 7f00 | 008b be94 | 0000 0083 | c702 89be | 9400 0000 | 81e7 fe3f - 0x00007f78c5a477ac: 0000 85ff | 0f84 3f33 | 0000 4d8b - - 0x00007f78c5a477b8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1750 (line 2059) - ; {poll} - 0x00007f78c5a477b8: 5730 4185 - - 0x00007f78c5a477bc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a477bc: 0248 be48 | 99d2 9278 | 7f00 00ff | 8678 1b00 | 008b 8c24 | 0002 0000 | 8b94 2404 | 0200 0048 - 0x00007f78c5a477dc: 8b9c 24f0 | 0100 00e9 | 50fd ffff | 8994 2404 | 0200 0083 - - 0x00007f78c5a477f0: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a477f0: f908 48be | 4899 d292 | 787f 0000 | bf90 1b00 | 0075 05bf | a01b 0000 | 488b 1c3e | 488d 5b01 - 0x00007f78c5a47810: 4889 1c3e | 0f85 9200 | 0000 8b94 | 2404 0200 | 003b 9424 | e801 0000 - - 0x00007f78c5a47828: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47828: 48be 4899 | d292 787f | 0000 bfb0 | 1b00 0074 | 05bf c01b | 0000 488b | 1c3e 488d | 5b01 4889 - 0x00007f78c5a47848: 1c3e 0f84 | 5c00 0000 - - 0x00007f78c5a47850: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47850: ffc2 48be | 4899 d292 | 787f 0000 | 8bbe 9400 | 0000 83c7 | 0289 be94 | 0000 0081 | e7fe 3f00 - 0x00007f78c5a47870: 0085 ff0f | 849d 3200 | 004d 8b57 - - 0x00007f78c5a4787c: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1770 (line 2054) - ; {poll} - 0x00007f78c5a4787c: 3041 8502 - - 0x00007f78c5a47880: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47880: 48be 4899 | d292 787f | 0000 ff86 | d01b 0000 | 8b8c 24e8 | 0100 0048 | 8b84 2408 | 0200 0048 - 0x00007f78c5a478a0: 8b9c 24f0 | 0100 00e9 | 04fc ffff | 488b bc24 | 8802 0000 - - 0x00007f78c5a478b4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a478b4: 48be 4899 | d292 787f | 0000 8b7f | 0848 3bbe | f01b 0000 | 750d 4883 | 86f8 1b00 | 0001 e960 - 0x00007f78c5a478d4: 0000 0048 | 3bbe 001c | 0000 750d | 4883 8608 | 1c00 0001 | e94a 0000 | 0048 83be | f01b 0000 - 0x00007f78c5a478f4: 0075 1748 | 89be f01b | 0000 48c7 | 86f8 1b00 | 0001 0000 | 00e9 2900 | 0000 4883 | be00 1c00 - 0x00007f78c5a47914: 0000 7517 | 4889 be00 | 1c00 0048 | c786 081c | 0000 0100 | 0000 e908 | 0000 0048 | 8386 e81b - 0x00007f78c5a47934: 0000 0148 | 8bb4 2488 - - 0x00007f78c5a4793c: ; {optimized virtual_call} - 0x00007f78c5a4793c: 0200 00e8 - - 0x00007f78c5a47940: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [416]=Oop [424]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1777 (line 769) - 0x00007f78c5a47940: 5c37 f0ff - - 0x00007f78c5a47944: ; {post_call_nop} - 0x00007f78c5a47944: 0f1f 8400 | 3c58 0042 - - 0x00007f78c5a4794c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4794c: 48ba 4899 | d292 787f | 0000 ff82 | 181c 0000 | 488b 8424 | a001 0000 - - 0x00007f78c5a47964: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47964: 48ba 4899 | d292 787f | 0000 8b40 | 0848 3b82 | 481d 0000 | 750d 4883 | 8250 1d00 | 0001 e960 - 0x00007f78c5a47984: 0000 0048 | 3b82 581d | 0000 750d | 4883 8260 | 1d00 0001 | e94a 0000 | 0048 83ba | 481d 0000 - 0x00007f78c5a479a4: 0075 1748 | 8982 481d | 0000 48c7 | 8250 1d00 | 0001 0000 | 00e9 2900 | 0000 4883 | ba58 1d00 - 0x00007f78c5a479c4: 0000 7517 | 4889 8258 | 1d00 0048 | c782 601d | 0000 0100 | 0000 e908 | 0000 0048 | 8382 401d - 0x00007f78c5a479e4: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a479e4: 0000 0148 | ba40 bf8f | 9278 7f00 | 008b b290 | 0000 0083 | c602 89b2 | 9000 0000 | 81e6 feff - 0x00007f78c5a47a04: 1f00 85f6 | 0f84 2931 - - 0x00007f78c5a47a0c: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a47a0c: 0000 48ba | 40bf 8f92 | 787f 0000 | 4883 82d8 | 0000 0001 - - 0x00007f78c5a47a20: ; {metadata(method data for {method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a47a20: 48ba b893 | 6d92 787f | 0000 8bb2 | 9000 0000 | 83c6 0289 | b290 0000 | 0081 e6fe | ff1f 0085 - 0x00007f78c5a47a40: f60f 8411 - - 0x00007f78c5a47a44: ; {oop(a 'androidx/compose/runtime/internal/SnapshotThreadLocal'{0x00000004214f8a00})} - 0x00007f78c5a47a44: 3100 0048 | be00 8a4f | 2104 0000 | 0048 8bd6 - - 0x00007f78c5a47a54: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a47a54: 48bf 40bf | 8f92 787f | 0000 8b52 | 0848 3b97 | f000 0000 | 750d 4883 | 87f8 0000 | 0001 e960 - 0x00007f78c5a47a74: 0000 0048 | 3b97 0001 | 0000 750d | 4883 8708 | 0100 0001 | e94a 0000 | 0048 83bf | f000 0000 - 0x00007f78c5a47a94: 0075 1748 | 8997 f000 | 0000 48c7 | 87f8 0000 | 0001 0000 | 00e9 2900 | 0000 4883 | bf00 0100 - 0x00007f78c5a47ab4: 0000 7517 | 4889 9700 | 0100 0048 | c787 0801 | 0000 0100 | 0000 e908 | 0000 0048 | 8387 e800 - 0x00007f78c5a47ad4: 0000 0148 | 8b94 24a8 - - 0x00007f78c5a47adc: ; {optimized virtual_call} - 0x00007f78c5a47adc: 0100 00e8 - - 0x00007f78c5a47ae0: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [416]=Oop } - ;*invokevirtual set {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@4 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1876 (line 2020) - 0x00007f78c5a47ae0: bc64 ccff - - 0x00007f78c5a47ae4: ; {post_call_nop} - 0x00007f78c5a47ae4: 0f1f 8400 | dc59 0043 - - 0x00007f78c5a47aec: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47aec: 48be 4899 | d292 787f | 0000 ff86 | 701d 0000 | 488b 8424 | a001 0000 - - 0x00007f78c5a47b04: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47b04: 48be 4899 | d292 787f | 0000 8b40 | 0848 3b86 | c01d 0000 | 750d 4883 | 86c8 1d00 | 0001 e960 - 0x00007f78c5a47b24: 0000 0048 | 3b86 d01d | 0000 750d | 4883 86d8 | 1d00 0001 | e94a 0000 | 0048 83be | c01d 0000 - 0x00007f78c5a47b44: 0075 1748 | 8986 c01d | 0000 48c7 | 86c8 1d00 | 0001 0000 | 00e9 2900 | 0000 4883 | bed0 1d00 - 0x00007f78c5a47b64: 0000 7517 | 4889 86d0 | 1d00 0048 | c786 d81d | 0000 0100 | 0000 e908 | 0000 0048 | 8386 b81d - 0x00007f78c5a47b84: 0000 0148 | 8bb4 24a0 | 0100 0066 | 0f1f 4400 | 0048 b830 | 57de 6978 - - 0x00007f78c5a47b9c: ; {virtual_call} - 0x00007f78c5a47b9c: 7f00 00e8 - - 0x00007f78c5a47ba0: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop } - ;*invokevirtual dispose {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1899 (line 2021) - 0x00007f78c5a47ba0: 6e59 d1ff - - 0x00007f78c5a47ba4: ; {post_call_nop} - 0x00007f78c5a47ba4: 0f1f 8400 | 9c5a 0044 - - 0x00007f78c5a47bac: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47bac: 48b8 4899 | d292 787f | 0000 ff80 | e81d 0000 | 488b b424 | d800 0000 - - 0x00007f78c5a47bc4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47bc4: 48b8 4899 | d292 787f | 0000 4883 | 8030 1e00 | 0001 8b7e | 2448 c1e7 | 0348 89bc | 2410 0200 - 0x00007f78c5a47be4: 0048 8d9c | 2450 0200 | 0048 897b | 0848 8b07 | 418b 97d0 | 0700 0081 | fa20 0800 | 000f 8d77 - 0x00007f78c5a47c04: 2f00 0049 | 3b7c 17f8 | 0f84 2500 | 0000 a802 | 0f85 642f | 0000 488b | d048 83e2 | fe48 83c8 - 0x00007f78c5a47c24: 01f0 480f | b117 0f85 | 4e2f 0000 | 418b 97d0 | 0700 0049 | 893c 1783 | c208 4189 | 97d0 0700 - 0x00007f78c5a47c44: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47c44: 0048 bb48 | 99d2 9278 | 7f00 0048 | 8383 401e - - 0x00007f78c5a47c54: ; {metadata(method data for {method} {0x00007f7890e28330} 'access$deriveStateLocked' '(Landroidx/compose/runtime/Recomposer;)Lkotlinx/coroutines/CancellableContinuation;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a47c54: 0000 0148 | bb38 c1ad | 9278 7f00 | 008b 8390 | 0000 0083 | c002 8983 | 9000 0000 | 25fe ff1f - 0x00007f78c5a47c74: 0085 c00f | 8414 2f00 | 0048 8bde - - 0x00007f78c5a47c80: ; {metadata(method data for {method} {0x00007f7890e28330} 'access$deriveStateLocked' '(Landroidx/compose/runtime/Recomposer;)Lkotlinx/coroutines/CancellableContinuation;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a47c80: 48b8 38c1 | ad92 787f | 0000 4883 | 80d8 0000 | 0001 488b | de48 8bf3 | 0f1f 8000 - - 0x00007f78c5a47c9c: ; {optimized virtual_call} - 0x00007f78c5a47c9c: 0000 00e8 - - 0x00007f78c5a47ca0: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [528]=Oop [600]=Oop } - ;*invokespecial deriveStateLocked {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::access$deriveStateLocked@1 (line 191) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1937 (line 775) - 0x00007f78c5a47ca0: 6c23 4107 - - 0x00007f78c5a47ca4: ; {post_call_nop} - 0x00007f78c5a47ca4: 0f1f 8400 | 9c5b 0045 - - 0x00007f78c5a47cac: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47cac: 4885 c048 | b848 99d2 | 9278 7f00 | 00be 501e | 0000 7505 | be60 1e00 | 0048 8b3c | 3048 8d7f - 0x00007f78c5a47ccc: 0148 893c | 300f 851a - - 0x00007f78c5a47cd4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47cd4: 0000 0048 | b848 99d2 | 9278 7f00 | 00ff 8070 | 1e00 00b8 | 0100 0000 | e905 0000 | 00b8 0000 - 0x00007f78c5a47cf4: 0000 85c0 - - 0x00007f78c5a47cf8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47cf8: 48b8 4899 | d292 787f | 0000 be98 | 1e00 0074 | 05be 881e | 0000 488b | 3c30 488d | 7f01 4889 - 0x00007f78c5a47d18: 3c30 0f84 | 6e19 0000 | 488d 8424 | 5002 0000 | 488b 7808 | 418b b7d0 | 0700 0049 | 3b7c 37f8 - 0x00007f78c5a47d38: 0f85 742e | 0000 4183 | afd0 0700 | 0008 493b | 7c37 f00f | 842a 0000 | 0048 8b07 | a802 0f85 - 0x00007f78c5a47d58: 1200 0000 | 488b f048 | 83ce 01f0 | 480f b137 | 0f84 0d00 | 0000 4183 | 87d0 0700 | 0008 e937 - 0x00007f78c5a47d78: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47d78: 2e00 0048 | be48 99d2 | 9278 7f00 | 00ff 86b8 - - 0x00007f78c5a47d88: ; {oop(a 'androidx/compose/runtime/snapshots/Snapshot$Companion'{0x0000000421145890})} - 0x00007f78c5a47d88: 1e00 0048 | be90 5814 | 2104 0000 - - 0x00007f78c5a47d94: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a47d94: 0048 bf48 | 99d2 9278 | 7f00 008b | 7608 483b | b7d8 1e00 | 0075 0d48 | 8387 e01e | 0000 01e9 - 0x00007f78c5a47db4: 6000 0000 | 483b b7e8 | 1e00 0075 | 0d48 8387 | f01e 0000 | 01e9 4a00 | 0000 4883 | bfd8 1e00 - 0x00007f78c5a47dd4: 0000 7517 | 4889 b7d8 | 1e00 0048 | c787 e01e | 0000 0100 | 0000 e929 | 0000 0048 | 83bf e81e - 0x00007f78c5a47df4: 0000 0075 | 1748 89b7 | e81e 0000 | 48c7 87f0 | 1e00 0001 | 0000 00e9 | 0800 0000 | 4883 87d0 - 0x00007f78c5a47e14: 1e00 0001 - - 0x00007f78c5a47e18: ; {metadata(method data for {method} {0x00007f7890cee578} 'notifyObjectsInitialized' '()V' in 'androidx/compose/runtime/snapshots/Snapshot$Companion')} - 0x00007f78c5a47e18: 48be e8df | ce92 787f | 0000 8bbe | 9000 0000 | 83c7 0289 | be90 0000 | 0081 e7fe | ff1f 0085 - 0x00007f78c5a47e38: ff0f 8489 - - 0x00007f78c5a47e3c: ; {metadata(method data for {method} {0x00007f7890cee578} 'notifyObjectsInitialized' '()V' in 'androidx/compose/runtime/snapshots/Snapshot$Companion')} - 0x00007f78c5a47e3c: 2d00 0048 | bee8 dfce | 9278 7f00 | 0048 8386 | d800 0000 - - 0x00007f78c5a47e50: ; {metadata(method data for {method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a47e50: 0148 be28 | 4646 9278 | 7f00 008b | be90 0000 | 0083 c702 | 89be 9000 | 0000 81e7 | feff 1f00 - 0x00007f78c5a47e70: 85ff 0f84 | 712d 0000 - - 0x00007f78c5a47e78: ; {oop(a 'androidx/compose/runtime/internal/SnapshotThreadLocal'{0x00000004214f8a00})} - 0x00007f78c5a47e78: 48be 008a | 4f21 0400 | 0000 488b - - 0x00007f78c5a47e84: ; {metadata(method data for {method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a47e84: fe48 bb28 | 4646 9278 | 7f00 008b | 7f08 483b | bbe0 0000 | 0075 0d48 | 8383 e800 | 0000 01e9 - 0x00007f78c5a47ea4: 6000 0000 | 483b bbf0 | 0000 0075 | 0d48 8383 | f800 0000 | 01e9 4a00 | 0000 4883 | bbe0 0000 - 0x00007f78c5a47ec4: 0000 7517 | 4889 bbe0 | 0000 0048 | c783 e800 | 0000 0100 | 0000 e929 | 0000 0048 | 83bb f000 - 0x00007f78c5a47ee4: 0000 0075 | 1748 89bb | f000 0000 | 48c7 83f8 | 0000 0001 | 0000 00e9 | 0800 0000 | 4883 83d8 - 0x00007f78c5a47f04: 0000 0001 | 0f1f 8000 - - 0x00007f78c5a47f0c: ; {optimized virtual_call} - 0x00007f78c5a47f0c: 0000 00e8 - - 0x00007f78c5a47f10: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop } - ;*invokevirtual get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.SnapshotKt::currentSnapshot@3 (line 1284) - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::notifyObjectsInitialized@0 (line 676) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1995 (line 785) - 0x00007f78c5a47f10: fcb5 3307 - - 0x00007f78c5a47f14: ; {post_call_nop} - 0x00007f78c5a47f14: 0f1f 8400 | 0c5e 0046 - - 0x00007f78c5a47f1c: ; {metadata(method data for {method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a47f1c: 4885 c048 | be28 4646 | 9278 7f00 | 0075 0c80 | 8e01 0100 | 0001 e995 | 0000 008b | 5808 483b - 0x00007f78c5a47f3c: 9e10 0100 | 0075 0d48 | 8386 1801 | 0000 01e9 | 6000 0000 | 483b 9e20 | 0100 0075 | 0d48 8386 - 0x00007f78c5a47f5c: 2801 0000 | 01e9 4a00 | 0000 4883 | be10 0100 | 0000 7517 | 4889 9e10 | 0100 0048 | c786 1801 - 0x00007f78c5a47f7c: 0000 0100 | 0000 e929 | 0000 0048 | 83be 2001 | 0000 0075 | 1748 899e | 2001 0000 | 48c7 8628 - 0x00007f78c5a47f9c: 0100 0001 | 0000 00e9 | 0800 0000 | 4883 8608 | 0100 0001 - - 0x00007f78c5a47fb0: ; {metadata('androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a47fb0: 48bb 5809 | 1821 0000 | 0000 8b70 | 0848 3b5e | 380f 8543 | 2c00 00e9 | 0000 0000 | 488b f048 - 0x00007f78c5a47fd0: ; {metadata(method data for {method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a47fd0: 85f6 48bf | 2846 4692 | 787f 0000 | bb38 0100 | 0075 05bb | 4801 0000 | 488b 041f | 488d 4001 - 0x00007f78c5a47ff0: 4889 041f | 0f85 0a00 - - 0x00007f78c5a47ff8: ; {oop(a 'androidx/compose/runtime/snapshots/GlobalSnapshot'{0x00000004214f8368})} - 0x00007f78c5a47ff8: 0000 48be | 6883 4f21 | 0400 0000 - - 0x00007f78c5a48004: ; implicit exception: dispatches to 0x00007f78c5a4ac13 - 0x00007f78c5a48004: 483b 0648 - - 0x00007f78c5a48008: ; {metadata(method data for {method} {0x00007f7890cee578} 'notifyObjectsInitialized' '()V' in 'androidx/compose/runtime/snapshots/Snapshot$Companion')} - 0x00007f78c5a48008: 8bfe 48bb | e8df ce92 | 787f 0000 | 8b7f 0848 | 3bbb f000 | 0000 750d | 4883 83f8 | 0000 0001 - 0x00007f78c5a48028: e960 0000 | 0048 3bbb | 0001 0000 | 750d 4883 | 8308 0100 | 0001 e94a | 0000 0048 | 83bb f000 - 0x00007f78c5a48048: 0000 0075 | 1748 89bb | f000 0000 | 48c7 83f8 | 0000 0001 | 0000 00e9 | 2900 0000 | 4883 bb00 - 0x00007f78c5a48068: 0100 0000 | 7517 4889 | bb00 0100 | 0048 c783 | 0801 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a48088: e800 0000 | 0148 b858 | 57de 6978 - - 0x00007f78c5a48094: ; {virtual_call} - 0x00007f78c5a48094: 7f00 00e8 - - 0x00007f78c5a48098: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop } - ;*invokevirtual notifyObjectsInitialized$runtime {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::notifyObjectsInitialized@3 (line 676) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1995 (line 785) - 0x00007f78c5a48098: 764c f4ff - - 0x00007f78c5a4809c: ; {post_call_nop} - 0x00007f78c5a4809c: 0f1f 8400 | 945f 0047 | 488b 8c24 | f000 0000 - - 0x00007f78c5a480ac: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a480ac: 48bf 4899 | d292 787f | 0000 8b49 | 0848 3b8f | 081f 0000 | 750d 4883 | 8710 1f00 | 0001 e960 - 0x00007f78c5a480cc: 0000 0048 | 3b8f 181f | 0000 750d | 4883 8720 | 1f00 0001 | e94a 0000 | 0048 83bf | 081f 0000 - 0x00007f78c5a480ec: 0075 1748 | 898f 081f | 0000 48c7 | 8710 1f00 | 0001 0000 | 00e9 2900 | 0000 4883 | bf18 1f00 - 0x00007f78c5a4810c: 0000 7517 | 4889 8f18 | 1f00 0048 | c787 201f | 0000 0100 | 0000 e908 | 0000 0048 | 8387 001f - 0x00007f78c5a4812c: 0000 0148 | 8bb4 24f0 - - 0x00007f78c5a48134: ; {optimized virtual_call} - 0x00007f78c5a48134: 0000 00e8 - - 0x00007f78c5a48138: ; ImmutableOopMap {[216]=Oop [232]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1999 (line 786) - 0x00007f78c5a48138: 642f f0ff - - 0x00007f78c5a4813c: ; {post_call_nop} - 0x00007f78c5a4813c: 0f1f 8400 | 3460 0048 | 488b 9424 | e800 0000 - - 0x00007f78c5a4814c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4814c: 48be 4899 | d292 787f | 0000 8b52 | 0848 3b96 | 381f 0000 | 750d 4883 | 8640 1f00 | 0001 e960 - 0x00007f78c5a4816c: 0000 0048 | 3b96 481f | 0000 750d | 4883 8650 | 1f00 0001 | e94a 0000 | 0048 83be | 381f 0000 - 0x00007f78c5a4818c: 0075 1748 | 8996 381f | 0000 48c7 | 8640 1f00 | 0001 0000 | 00e9 2900 | 0000 4883 | be48 1f00 - 0x00007f78c5a481ac: 0000 7517 | 4889 9648 | 1f00 0048 | c786 501f | 0000 0100 | 0000 e908 | 0000 0048 | 8386 301f - 0x00007f78c5a481cc: 0000 0148 | 8bb4 24e8 - - 0x00007f78c5a481d4: ; {optimized virtual_call} - 0x00007f78c5a481d4: 0000 00e8 - - 0x00007f78c5a481d8: ; ImmutableOopMap {[216]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@2003 (line 787) - 0x00007f78c5a481d8: c42e f0ff - - 0x00007f78c5a481dc: ; {post_call_nop} - 0x00007f78c5a481dc: 0f1f 8400 | d460 0049 - - 0x00007f78c5a481e4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a481e4: 48b8 4899 | d292 787f | 0000 4883 | 8060 1f00 | 0001 410f | be47 4885 | c048 8bb4 | 24d8 0000 - 0x00007f78c5a48204: 000f 850d | 2a00 00c7 | 465c 0000 | 0000 e996 | 1300 0048 | 8bb4 24d8 | 0000 0049 | 8b87 e005 - 0x00007f78c5a48224: 0000 4d33 | d24d 8997 | e005 0000 | 4d33 d24d | 8997 e805 - - 0x00007f78c5a48238: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48238: 0000 48ba | 4899 d292 | 787f 0000 | 4883 8230 | 1c00 0001 - - 0x00007f78c5a4824c: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4824c: 48ba f0bf | d292 787f | 0000 8b8a | 9000 0000 | 83c1 0289 | 8a90 0000 | 0081 e1fe | ff1f 0085 - 0x00007f78c5a4826c: c90f 84c4 - - 0x00007f78c5a48270: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48270: 2900 0048 | baf0 bfd2 | 9278 7f00 | 00ff 82e8 - - 0x00007f78c5a48280: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48280: 0000 0048 | baf0 bfd2 | 9278 7f00 | 00ff 8208 | 0100 0048 - - 0x00007f78c5a48294: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48294: 8bd6 48b9 | f0bf d292 | 787f 0000 | 4883 8118 | 0100 0001 - - 0x00007f78c5a482a8: ; {oop(nullptr)} - 0x00007f78c5a482a8: 488b d048 | b900 0000 | 0000 0000 | 0041 b800 | 0000 0048 | 8bfe 488b | f766 0f1f - - 0x00007f78c5a482c4: ; {optimized virtual_call} - 0x00007f78c5a482c4: 4400 00e8 - - 0x00007f78c5a482c8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokespecial processCompositionError {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@22 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1793 (line 765) - 0x00007f78c5a482c8: 1458 a606 - - 0x00007f78c5a482cc: ; {post_call_nop} - 0x00007f78c5a482cc: 0f1f 8400 | c461 004a | 488b 8c24 | f000 0000 | 488b 9424 | e800 0000 - - 0x00007f78c5a482e4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a482e4: 48be 4899 | d292 787f | 0000 4883 | 8640 1c00 | 0001 488b | b424 d800 | 0000 488b | da48 8b94 - 0x00007f78c5a48304: 24e0 0000 | 004c 8bd9 | 488b 8c24 | f800 0000 | 4c8b 8424 | 8002 0000 | 4c8b 8c24 | 0001 0000 - 0x00007f78c5a48324: 488b bc24 | 8802 0000 | 4889 1c24 | 4c89 5c24 - - 0x00007f78c5a48334: ; {static_call} - 0x00007f78c5a48334: 0866 90e8 - - 0x00007f78c5a48338: ; ImmutableOopMap {[416]=Oop [424]=Oop [648]=Oop } - ;*invokestatic invokeSuspend$clearRecompositionState {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1808 (line 766) - 0x00007f78c5a48338: a461 a606 - - 0x00007f78c5a4833c: ; {post_call_nop} - 0x00007f78c5a4833c: 0f1f 8400 | 3462 004b | 488b bc24 | 8802 0000 - - 0x00007f78c5a4834c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4834c: 48be 4899 | d292 787f | 0000 8b7f | 0848 3bbe | 581c 0000 | 750d 4883 | 8660 1c00 | 0001 e960 - 0x00007f78c5a4836c: 0000 0048 | 3bbe 681c | 0000 750d | 4883 8670 | 1c00 0001 | e94a 0000 | 0048 83be | 581c 0000 - 0x00007f78c5a4838c: 0075 1748 | 89be 581c | 0000 48c7 | 8660 1c00 | 0001 0000 | 00e9 2900 | 0000 4883 | be68 1c00 - 0x00007f78c5a483ac: 0000 7517 | 4889 be68 | 1c00 0048 | c786 701c | 0000 0100 | 0000 e908 | 0000 0048 | 8386 501c - 0x00007f78c5a483cc: 0000 0148 | 8bb4 2488 - - 0x00007f78c5a483d4: ; {optimized virtual_call} - 0x00007f78c5a483d4: 0200 00e8 - - 0x00007f78c5a483d8: ; ImmutableOopMap {[416]=Oop [424]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1818 (line 769) - 0x00007f78c5a483d8: 0457 a606 - - 0x00007f78c5a483dc: ; {post_call_nop} - 0x00007f78c5a483dc: 0f1f 8400 | d462 004c | 488b 8424 | a001 0000 - - 0x00007f78c5a483ec: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a483ec: 48ba 4899 | d292 787f | 0000 8b40 | 0848 3b82 | 881c 0000 | 750d 4883 | 8290 1c00 | 0001 e960 - 0x00007f78c5a4840c: 0000 0048 | 3b82 981c | 0000 750d | 4883 82a0 | 1c00 0001 | e94a 0000 | 0048 83ba | 881c 0000 - 0x00007f78c5a4842c: 0075 1748 | 8982 881c | 0000 48c7 | 8290 1c00 | 0001 0000 | 00e9 2900 | 0000 4883 | ba98 1c00 - 0x00007f78c5a4844c: 0000 7517 | 4889 8298 | 1c00 0048 | c782 a01c | 0000 0100 | 0000 e908 | 0000 0048 | 8382 801c - 0x00007f78c5a4846c: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a4846c: 0000 0148 | ba40 bf8f | 9278 7f00 | 008b b290 | 0000 0083 | c602 89b2 | 9000 0000 | 81e6 feff - 0x00007f78c5a4848c: 1f00 85f6 | 0f84 c227 - - 0x00007f78c5a48494: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a48494: 0000 48ba | 40bf 8f92 | 787f 0000 | 4883 82d8 | 0000 0001 - - 0x00007f78c5a484a8: ; {metadata(method data for {method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a484a8: 48ba b893 | 6d92 787f | 0000 8bb2 | 9000 0000 | 83c6 0289 | b290 0000 | 0081 e6fe | ff1f 0085 - 0x00007f78c5a484c8: f60f 84aa - - 0x00007f78c5a484cc: ; {oop(a 'androidx/compose/runtime/internal/SnapshotThreadLocal'{0x00000004214f8a00})} - 0x00007f78c5a484cc: 2700 0048 | be00 8a4f | 2104 0000 | 0048 8bd6 - - 0x00007f78c5a484dc: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a484dc: 48bf 40bf | 8f92 787f | 0000 8b52 | 0848 3b97 | f000 0000 | 750d 4883 | 87f8 0000 | 0001 e960 - 0x00007f78c5a484fc: 0000 0048 | 3b97 0001 | 0000 750d | 4883 8708 | 0100 0001 | e94a 0000 | 0048 83bf | f000 0000 - 0x00007f78c5a4851c: 0075 1748 | 8997 f000 | 0000 48c7 | 87f8 0000 | 0001 0000 | 00e9 2900 | 0000 4883 | bf00 0100 - 0x00007f78c5a4853c: 0000 7517 | 4889 9700 | 0100 0048 | c787 0801 | 0000 0100 | 0000 e908 | 0000 0048 | 8387 e800 - 0x00007f78c5a4855c: 0000 0148 | 8b94 24a8 - - 0x00007f78c5a48564: ; {optimized virtual_call} - 0x00007f78c5a48564: 0100 00e8 - - 0x00007f78c5a48568: ; ImmutableOopMap {[416]=Oop } - ;*invokevirtual set {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@4 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1829 (line 2020) - 0x00007f78c5a48568: 7455 a606 - - 0x00007f78c5a4856c: ; {post_call_nop} - 0x00007f78c5a4856c: 0f1f 8400 | 6464 004d | e9b4 0e00 | 004c 8b9c | 24f0 0000 | 0048 8b9c | 24e8 0000 | 0049 8b87 - 0x00007f78c5a4858c: e005 0000 | 4d33 d24d | 8997 e005 | 0000 4d33 | d24d 8997 | e805 0000 - - 0x00007f78c5a485a4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a485a4: 48ba 4899 | d292 787f | 0000 4883 | 82d0 1800 - - 0x00007f78c5a485b4: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a485b4: 0001 48ba | f0bf d292 | 787f 0000 | 8b8a 9000 | 0000 83c1 | 0289 8a90 | 0000 0081 | e1fe ff1f - 0x00007f78c5a485d4: 0085 c90f | 84bd 2600 - - 0x00007f78c5a485dc: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a485dc: 0048 baf0 | bfd2 9278 | 7f00 00ff | 82e8 0000 - - 0x00007f78c5a485ec: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a485ec: 0048 baf0 | bfd2 9278 | 7f00 00ff | 8208 0100 | 0048 8bb4 | 24d8 0000 - - 0x00007f78c5a48604: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48604: 0049 b8f0 | bfd2 9278 | 7f00 0049 | 8380 1801 | 0000 0148 - - 0x00007f78c5a48618: ; {oop(nullptr)} - 0x00007f78c5a48618: 8bd0 48b9 | 0000 0000 | 0000 0000 | 41b8 0000 | 0000 488b | b424 d800 | 0000 0f1f - - 0x00007f78c5a48634: ; {optimized virtual_call} - 0x00007f78c5a48634: 4400 00e8 - - 0x00007f78c5a48638: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokespecial processCompositionError {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@22 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1479 (line 751) - 0x00007f78c5a48638: a454 a606 - - 0x00007f78c5a4863c: ; {post_call_nop} - 0x00007f78c5a4863c: 0f1f 8400 | 3465 004e | 488b 8c24 | f000 0000 | 488b 9424 | e800 0000 - - 0x00007f78c5a48654: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48654: 48be 4899 | d292 787f | 0000 4883 | 86e0 1800 | 0001 488b | b424 d800 | 0000 488b | da48 8b94 - 0x00007f78c5a48674: 24e0 0000 | 004c 8bd9 | 488b 8c24 | f800 0000 | 4c8b 8424 | 8002 0000 | 4c8b 8c24 | 0001 0000 - 0x00007f78c5a48694: 488b bc24 | 8802 0000 | 4889 1c24 | 4c89 5c24 - - 0x00007f78c5a486a4: ; {static_call} - 0x00007f78c5a486a4: 0866 90e8 - - 0x00007f78c5a486a8: ; ImmutableOopMap {[256]=Oop [416]=Oop [424]=Oop } - ;*invokestatic invokeSuspend$clearRecompositionState {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1494 (line 752) - 0x00007f78c5a486a8: 345e a606 - - 0x00007f78c5a486ac: ; {post_call_nop} - 0x00007f78c5a486ac: 0f1f 8400 | a465 004f | 488b bc24 | 0001 0000 - - 0x00007f78c5a486bc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a486bc: 48be 4899 | d292 787f | 0000 8b7f | 0848 3bbe | f818 0000 | 750d 4883 | 8600 1900 | 0001 e960 - 0x00007f78c5a486dc: 0000 0048 | 3bbe 0819 | 0000 750d | 4883 8610 | 1900 0001 | e94a 0000 | 0048 83be | f818 0000 - 0x00007f78c5a486fc: 0075 1748 | 89be f818 | 0000 48c7 | 8600 1900 | 0001 0000 | 00e9 2900 | 0000 4883 | be08 1900 - 0x00007f78c5a4871c: 0000 7517 | 4889 be08 | 1900 0048 | c786 1019 | 0000 0100 | 0000 e908 | 0000 0048 | 8386 f018 - 0x00007f78c5a4873c: 0000 0148 | 8bb4 2400 - - 0x00007f78c5a48744: ; {optimized virtual_call} - 0x00007f78c5a48744: 0100 00e8 - - 0x00007f78c5a48748: ; ImmutableOopMap {[416]=Oop [424]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1504 (line 755) - 0x00007f78c5a48748: 9453 a606 - - 0x00007f78c5a4874c: ; {post_call_nop} - 0x00007f78c5a4874c: 0f1f 8400 | 4466 0050 | 488b 8424 | a001 0000 - - 0x00007f78c5a4875c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4875c: 48ba 4899 | d292 787f | 0000 8b40 | 0848 3b82 | 2819 0000 | 750d 4883 | 8230 1900 | 0001 e960 - 0x00007f78c5a4877c: 0000 0048 | 3b82 3819 | 0000 750d | 4883 8240 | 1900 0001 | e94a 0000 | 0048 83ba | 2819 0000 - 0x00007f78c5a4879c: 0075 1748 | 8982 2819 | 0000 48c7 | 8230 1900 | 0001 0000 | 00e9 2900 | 0000 4883 | ba38 1900 - 0x00007f78c5a487bc: 0000 7517 | 4889 8238 | 1900 0048 | c782 4019 | 0000 0100 | 0000 e908 | 0000 0048 | 8382 2019 - 0x00007f78c5a487dc: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a487dc: 0000 0148 | ba40 bf8f | 9278 7f00 | 008b b290 | 0000 0083 | c602 89b2 | 9000 0000 | 81e6 feff - 0x00007f78c5a487fc: 1f00 85f6 | 0f84 b524 - - 0x00007f78c5a48804: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a48804: 0000 48ba | 40bf 8f92 | 787f 0000 | 4883 82d8 | 0000 0001 - - 0x00007f78c5a48818: ; {metadata(method data for {method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a48818: 48ba b893 | 6d92 787f | 0000 8bb2 | 9000 0000 | 83c6 0289 | b290 0000 | 0081 e6fe | ff1f 0085 - 0x00007f78c5a48838: f60f 849d - - 0x00007f78c5a4883c: ; {oop(a 'androidx/compose/runtime/internal/SnapshotThreadLocal'{0x00000004214f8a00})} - 0x00007f78c5a4883c: 2400 0048 | be00 8a4f | 2104 0000 | 0048 8bd6 - - 0x00007f78c5a4884c: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a4884c: 48bf 40bf | 8f92 787f | 0000 8b52 | 0848 3b97 | f000 0000 | 750d 4883 | 87f8 0000 | 0001 e960 - 0x00007f78c5a4886c: 0000 0048 | 3b97 0001 | 0000 750d | 4883 8708 | 0100 0001 | e94a 0000 | 0048 83bf | f000 0000 - 0x00007f78c5a4888c: 0075 1748 | 8997 f000 | 0000 48c7 | 87f8 0000 | 0001 0000 | 00e9 2900 | 0000 4883 | bf00 0100 - 0x00007f78c5a488ac: 0000 7517 | 4889 9700 | 0100 0048 | c787 0801 | 0000 0100 | 0000 e908 | 0000 0048 | 8387 e800 - 0x00007f78c5a488cc: 0000 0148 | 8b94 24a8 - - 0x00007f78c5a488d4: ; {optimized virtual_call} - 0x00007f78c5a488d4: 0100 00e8 - - 0x00007f78c5a488d8: ; ImmutableOopMap {[416]=Oop } - ;*invokevirtual set {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@4 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1515 (line 2020) - 0x00007f78c5a488d8: 0452 a606 - - 0x00007f78c5a488dc: ; {post_call_nop} - 0x00007f78c5a488dc: 0f1f 8400 | d467 0051 | e9c4 0900 | 004c 8b9c | 24f0 0000 | 0048 8b9c | 24e8 0000 | 0049 8b87 - 0x00007f78c5a488fc: e005 0000 | 4d33 d24d | 8997 e005 | 0000 4d33 | d24d 8997 | e805 0000 - - 0x00007f78c5a48914: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48914: 48ba 4899 | d292 787f | 0000 4883 | 8210 1500 - - 0x00007f78c5a48924: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48924: 0001 48ba | f0bf d292 | 787f 0000 | 8b8a 9000 | 0000 83c1 | 0289 8a90 | 0000 0081 | e1fe ff1f - 0x00007f78c5a48944: 0085 c90f | 84b0 2300 - - 0x00007f78c5a4894c: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4894c: 0048 baf0 | bfd2 9278 | 7f00 00ff | 82e8 0000 - - 0x00007f78c5a4895c: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4895c: 0048 baf0 | bfd2 9278 | 7f00 00ff | 8208 0100 | 0048 8bb4 | 24d8 0000 - - 0x00007f78c5a48974: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48974: 0049 b8f0 | bfd2 9278 | 7f00 0049 | 8380 1801 | 0000 0148 - - 0x00007f78c5a48988: ; {oop(nullptr)} - 0x00007f78c5a48988: 8bd0 48b9 | 0000 0000 | 0000 0000 | 41b8 0000 | 0000 488b | b424 d800 | 0000 0f1f - - 0x00007f78c5a489a4: ; {optimized virtual_call} - 0x00007f78c5a489a4: 4400 00e8 - - 0x00007f78c5a489a8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokespecial processCompositionError {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@22 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1151 (line 736) - 0x00007f78c5a489a8: 3451 a606 - - 0x00007f78c5a489ac: ; {post_call_nop} - 0x00007f78c5a489ac: 0f1f 8400 | a468 0052 | 488b 8c24 | f000 0000 | 488b 9424 | e800 0000 - - 0x00007f78c5a489c4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a489c4: 48be 4899 | d292 787f | 0000 4883 | 8620 1500 | 0001 488b | b424 d800 | 0000 488b | da48 8b94 - 0x00007f78c5a489e4: 24e0 0000 | 004c 8bd9 | 488b 8c24 | f800 0000 | 4c8b 8424 | 8002 0000 | 4c8b 8c24 | 0001 0000 - 0x00007f78c5a48a04: 488b bc24 | 8802 0000 | 4889 1c24 | 4c89 5c24 - - 0x00007f78c5a48a14: ; {static_call} - 0x00007f78c5a48a14: 0866 90e8 - - 0x00007f78c5a48a18: ; ImmutableOopMap {[416]=Oop [424]=Oop [640]=Oop } - ;*invokestatic invokeSuspend$clearRecompositionState {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1166 (line 737) - 0x00007f78c5a48a18: c45a a606 - - 0x00007f78c5a48a1c: ; {post_call_nop} - 0x00007f78c5a48a1c: 0f1f 8400 | 1469 0053 | 488b bc24 | 8002 0000 - - 0x00007f78c5a48a2c: ; implicit exception: dispatches to 0x00007f78c5a4ad1e - 0x00007f78c5a48a2c: 483b 0748 - - 0x00007f78c5a48a30: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48a30: 8bf7 48bb | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb3 3815 | 0000 750d | 4883 8340 | 1500 0001 - 0x00007f78c5a48a50: e960 0000 | 0048 3bb3 | 4815 0000 | 750d 4883 | 8350 1500 | 0001 e94a | 0000 0048 | 83bb 3815 - 0x00007f78c5a48a70: 0000 0075 | 1748 89b3 | 3815 0000 | 48c7 8340 | 1500 0001 | 0000 00e9 | 2900 0000 | 4883 bb48 - 0x00007f78c5a48a90: 1500 0000 | 7517 4889 | b348 1500 | 0048 c783 | 5015 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a48ab0: 3015 0000 | 0148 8bf7 | 0f1f 4400 | 0048 b880 | 57de 6978 - - 0x00007f78c5a48ac4: ; {virtual_call} - 0x00007f78c5a48ac4: 7f00 00e8 - - 0x00007f78c5a48ac8: ; ImmutableOopMap {[416]=Oop [424]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1176 (line 740) - 0x00007f78c5a48ac8: 1455 a606 - - 0x00007f78c5a48acc: ; {post_call_nop} - 0x00007f78c5a48acc: 0f1f 8400 | c469 0054 | 488b 8424 | a001 0000 - - 0x00007f78c5a48adc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48adc: 48ba 4899 | d292 787f | 0000 8b40 | 0848 3b82 | 6815 0000 | 750d 4883 | 8270 1500 | 0001 e960 - 0x00007f78c5a48afc: 0000 0048 | 3b82 7815 | 0000 750d | 4883 8280 | 1500 0001 | e94a 0000 | 0048 83ba | 6815 0000 - 0x00007f78c5a48b1c: 0075 1748 | 8982 6815 | 0000 48c7 | 8270 1500 | 0001 0000 | 00e9 2900 | 0000 4883 | ba78 1500 - 0x00007f78c5a48b3c: 0000 7517 | 4889 8278 | 1500 0048 | c782 8015 | 0000 0100 | 0000 e908 | 0000 0048 | 8382 6015 - 0x00007f78c5a48b5c: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a48b5c: 0000 0148 | ba40 bf8f | 9278 7f00 | 008b b290 | 0000 0083 | c602 89b2 | 9000 0000 | 81e6 feff - 0x00007f78c5a48b7c: 1f00 85f6 | 0f84 9d21 - - 0x00007f78c5a48b84: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a48b84: 0000 48ba | 40bf 8f92 | 787f 0000 | 4883 82d8 | 0000 0001 - - 0x00007f78c5a48b98: ; {metadata(method data for {method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a48b98: 48ba b893 | 6d92 787f | 0000 8bb2 | 9000 0000 | 83c6 0289 | b290 0000 | 0081 e6fe | ff1f 0085 - 0x00007f78c5a48bb8: f60f 8485 - - 0x00007f78c5a48bbc: ; {oop(a 'androidx/compose/runtime/internal/SnapshotThreadLocal'{0x00000004214f8a00})} - 0x00007f78c5a48bbc: 2100 0048 | be00 8a4f | 2104 0000 | 0048 8bd6 - - 0x00007f78c5a48bcc: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a48bcc: 48bf 40bf | 8f92 787f | 0000 8b52 | 0848 3b97 | f000 0000 | 750d 4883 | 87f8 0000 | 0001 e960 - 0x00007f78c5a48bec: 0000 0048 | 3b97 0001 | 0000 750d | 4883 8708 | 0100 0001 | e94a 0000 | 0048 83bf | f000 0000 - 0x00007f78c5a48c0c: 0075 1748 | 8997 f000 | 0000 48c7 | 87f8 0000 | 0001 0000 | 00e9 2900 | 0000 4883 | bf00 0100 - 0x00007f78c5a48c2c: 0000 7517 | 4889 9700 | 0100 0048 | c787 0801 | 0000 0100 | 0000 e908 | 0000 0048 | 8387 e800 - 0x00007f78c5a48c4c: 0000 0148 | 8b94 24a8 - - 0x00007f78c5a48c54: ; {optimized virtual_call} - 0x00007f78c5a48c54: 0100 00e8 - - 0x00007f78c5a48c58: ; ImmutableOopMap {[416]=Oop } - ;*invokevirtual set {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@4 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1189 (line 2020) - 0x00007f78c5a48c58: 844e a606 - - 0x00007f78c5a48c5c: ; {post_call_nop} - 0x00007f78c5a48c5c: 0f1f 8400 | 546b 0055 | e9c4 0400 | 0048 8bbc | 2480 0200 | 004c 8b9c | 24f0 0000 | 0048 8b9c - 0x00007f78c5a48c7c: 24e8 0000 | 0049 8b87 | e005 0000 | 4d33 d24d | 8997 e005 | 0000 4d33 | d24d 8997 | e805 0000 - 0x00007f78c5a48c9c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48c9c: 48ba 4899 | d292 787f | 0000 4883 | 82b8 0f00 - - 0x00007f78c5a48cac: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48cac: 0001 48ba | f0bf d292 | 787f 0000 | 8b8a 9000 | 0000 83c1 | 0289 8a90 | 0000 0081 | e1fe ff1f - 0x00007f78c5a48ccc: 0085 c90f | 8490 2000 - - 0x00007f78c5a48cd4: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48cd4: 0048 baf0 | bfd2 9278 | 7f00 00ff | 82e8 0000 - - 0x00007f78c5a48ce4: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48ce4: 0048 baf0 | bfd2 9278 | 7f00 00ff | 82f8 0000 | 0048 8bb4 | 24d8 0000 - - 0x00007f78c5a48cfc: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48cfc: 0049 b8f0 | bfd2 9278 | 7f00 0049 | 8380 1801 | 0000 0148 - - 0x00007f78c5a48d10: ; {oop(nullptr)} - 0x00007f78c5a48d10: 8bd0 48b9 | 0000 0000 | 0000 0000 | 41b8 0100 | 0000 488b | b424 d800 | 0000 0f1f - - 0x00007f78c5a48d2c: ; {optimized virtual_call} - 0x00007f78c5a48d2c: 4400 00e8 - - 0x00007f78c5a48d30: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*invokespecial processCompositionError {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@22 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@848 (line 708) - 0x00007f78c5a48d30: ac4d a606 - - 0x00007f78c5a48d34: ; {post_call_nop} - 0x00007f78c5a48d34: 0f1f 8400 | 2c6c 0056 | 4c8b 8424 | e000 0000 | 4c8b 9c24 | f000 0000 | 488b 9c24 | e800 0000 - 0x00007f78c5a48d54: e9ac 0200 | 0049 8b87 | e005 0000 | 4d33 d24d | 8997 e005 | 0000 4d33 | d24d 8997 | e805 0000 - 0x00007f78c5a48d74: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48d74: 48ba 4899 | d292 787f | 0000 4883 | 82b8 0700 - - 0x00007f78c5a48d84: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48d84: 0001 48ba | f0bf d292 | 787f 0000 | 8b8a 9000 | 0000 83c1 | 0289 8a90 | 0000 0081 | e1fe ff1f - 0x00007f78c5a48da4: 0085 c90f | 84d9 1f00 - - 0x00007f78c5a48dac: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48dac: 0048 baf0 | bfd2 9278 | 7f00 00ff | 82e8 0000 - - 0x00007f78c5a48dbc: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48dbc: 0048 baf0 | bfd2 9278 | 7f00 00ff | 82f8 0000 | 0048 8bb4 | 24d8 0000 - - 0x00007f78c5a48dd4: ; {metadata(method data for {method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a48dd4: 0048 baf0 | bfd2 9278 | 7f00 0048 | 8382 1801 | 0000 0148 - - 0x00007f78c5a48de8: ; {oop(nullptr)} - 0x00007f78c5a48de8: 8bd0 48b9 | 0000 0000 | 0000 0000 | 41b8 0100 | 0000 488b | b424 d800 | 0000 0f1f - - 0x00007f78c5a48e04: ; {optimized virtual_call} - 0x00007f78c5a48e04: 4400 00e8 - - 0x00007f78c5a48e08: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*invokespecial processCompositionError {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@22 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@401 (line 661) - 0x00007f78c5a48e08: d44c a606 - - 0x00007f78c5a48e0c: ; {post_call_nop} - 0x00007f78c5a48e0c: 0f1f 8400 | 046d 0057 | 488b 8c24 | f000 0000 | 488b 9424 | e800 0000 - - 0x00007f78c5a48e24: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48e24: 48be 4899 | d292 787f | 0000 4883 | 86c8 0700 | 0001 488b | b424 d800 | 0000 488b | da48 8b94 - 0x00007f78c5a48e44: 24e0 0000 | 004c 8bd9 | 488b 8c24 | f800 0000 | 4c8b 8424 | 8002 0000 | 4c8b 8c24 | 0001 0000 - 0x00007f78c5a48e64: 488b bc24 | 8802 0000 | 4889 1c24 | 4c89 5c24 - - 0x00007f78c5a48e74: ; {static_call} - 0x00007f78c5a48e74: 0866 90e8 - - 0x00007f78c5a48e78: ; ImmutableOopMap {[224]=Oop } - ;*invokestatic invokeSuspend$clearRecompositionState {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@416 (line 662) - 0x00007f78c5a48e78: 6456 a606 - - 0x00007f78c5a48e7c: ; {post_call_nop} - 0x00007f78c5a48e7c: 0f1f 8400 | 746d 0058 | 4c8b 8424 | e000 0000 - - 0x00007f78c5a48e8c: ; implicit exception: dispatches to 0x00007f78c5a4ada7 - 0x00007f78c5a48e8c: 493b 0049 - - 0x00007f78c5a48e90: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48e90: 8bf0 48bf | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb7 e007 | 0000 750d | 4883 87e8 | 0700 0001 - 0x00007f78c5a48eb0: e960 0000 | 0048 3bb7 | f007 0000 | 750d 4883 | 87f8 0700 | 0001 e94a | 0000 0048 | 83bf e007 - 0x00007f78c5a48ed0: 0000 0075 | 1748 89b7 | e007 0000 | 48c7 87e8 | 0700 0001 | 0000 00e9 | 2900 0000 | 4883 bff0 - 0x00007f78c5a48ef0: 0700 0000 | 7517 4889 | b7f0 0700 | 0048 c787 | f807 0000 | 0100 0000 | e908 0000 | 0048 8387 - 0x00007f78c5a48f10: d807 0000 | 0149 8bf0 | 0f1f 4400 | 0048 b8a8 | 57de 6978 - - 0x00007f78c5a48f24: ; {virtual_call} - 0x00007f78c5a48f24: 7f00 00e8 - - 0x00007f78c5a48f28: ; ImmutableOopMap {} - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@425 (line 665) - 0x00007f78c5a48f28: b450 a606 - - 0x00007f78c5a48f2c: ; {post_call_nop} - 0x00007f78c5a48f2c: 0f1f 8400 | 246e 0059 - - 0x00007f78c5a48f34: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a48f34: 48b8 88da | 4f21 0400 - - 0x00007f78c5a48f3c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a48f3c: 0000 48be | 4899 d292 | 787f 0000 | 8b40 0848 | 3b86 1008 | 0000 750d | 4883 8618 | 0800 0001 - 0x00007f78c5a48f5c: e960 0000 | 0048 3b86 | 2008 0000 | 750d 4883 | 8628 0800 | 0001 e94a | 0000 0048 | 83be 1008 - 0x00007f78c5a48f7c: 0000 0075 | 1748 8986 | 1008 0000 | 48c7 8618 | 0800 0001 | 0000 00e9 | 2900 0000 | 4883 be20 - 0x00007f78c5a48f9c: 0800 0000 | 7517 4889 | 8620 0800 | 0048 c786 | 2808 0000 | 0100 0000 | e908 0000 | 0048 8386 - 0x00007f78c5a48fbc: 0808 0000 - - 0x00007f78c5a48fc0: ; {metadata(method data for {method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a48fc0: 0148 b8b0 | ddae 9278 | 7f00 008b | b090 0000 | 0083 c602 | 89b0 9000 | 0000 81e6 | feff 1f00 - 0x00007f78c5a48fe0: 85f6 0f84 | c41d 0000 - - 0x00007f78c5a48fe8: ; {oop(a 'kotlin/Unit'{0x00000004210022f8})} - 0x00007f78c5a48fe8: 48b8 f822 | 0021 0400 | 0000 4881 | c470 0200 - - 0x00007f78c5a48ff8: ; {poll_return} - 0x00007f78c5a48ff8: 005d 493b | 6728 0f87 | c91d 0000 - - 0x00007f78c5a49004: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49004: c348 be48 | 99d2 9278 | 7f00 0048 | 8386 c80f | 0000 0148 | 8bb4 24d8 | 0000 0049 | 8bd0 488b - 0x00007f78c5a49024: 8c24 f800 | 0000 4c8b | 8424 8002 | 0000 4c8b | 8c24 0001 | 0000 488b | bc24 8802 | 0000 4889 - 0x00007f78c5a49044: 1c24 4c89 | 5c24 080f - - 0x00007f78c5a4904c: ; {static_call} - 0x00007f78c5a4904c: 1f40 00e8 - - 0x00007f78c5a49050: ; ImmutableOopMap {} - ;*invokestatic invokeSuspend$clearRecompositionState {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@863 (line 709) - 0x00007f78c5a49050: 8c54 a606 - - 0x00007f78c5a49054: ; {post_call_nop} - 0x00007f78c5a49054: 0f1f 8400 | 4c6f 005a - - 0x00007f78c5a4905c: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a4905c: 48b8 88da | 4f21 0400 - - 0x00007f78c5a49064: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49064: 0000 48be | 4899 d292 | 787f 0000 | 8b40 0848 | 3b86 e00f | 0000 750d | 4883 86e8 | 0f00 0001 - 0x00007f78c5a49084: e960 0000 | 0048 3b86 | f00f 0000 | 750d 4883 | 86f8 0f00 | 0001 e94a | 0000 0048 | 83be e00f - 0x00007f78c5a490a4: 0000 0075 | 1748 8986 | e00f 0000 | 48c7 86e8 | 0f00 0001 | 0000 00e9 | 2900 0000 | 4883 bef0 - 0x00007f78c5a490c4: 0f00 0000 | 7517 4889 | 86f0 0f00 | 0048 c786 | f80f 0000 | 0100 0000 | e908 0000 | 0048 8386 - 0x00007f78c5a490e4: d80f 0000 - - 0x00007f78c5a490e8: ; {metadata(method data for {method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a490e8: 0148 b8b0 | ddae 9278 | 7f00 008b | b090 0000 | 0083 c602 | 89b0 9000 | 0000 81e6 | feff 1f00 - 0x00007f78c5a49108: 85f6 0f84 | d31c 0000 - - 0x00007f78c5a49110: ; {oop(a 'kotlin/Unit'{0x00000004210022f8})} - 0x00007f78c5a49110: 48b8 f822 | 0021 0400 | 0000 4881 | c470 0200 - - 0x00007f78c5a49120: ; {poll_return} - 0x00007f78c5a49120: 005d 493b | 6728 0f87 | d81c 0000 | c348 8b84 | 24a0 0100 - - 0x00007f78c5a49134: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49134: 0048 be48 | 99d2 9278 | 7f00 008b | 4008 483b | 8698 1500 | 0075 0d48 | 8386 a015 | 0000 01e9 - 0x00007f78c5a49154: 6000 0000 | 483b 86a8 | 1500 0075 | 0d48 8386 | b015 0000 | 01e9 4a00 | 0000 4883 | be98 1500 - 0x00007f78c5a49174: 0000 7517 | 4889 8698 | 1500 0048 | c786 a015 | 0000 0100 | 0000 e929 | 0000 0048 | 83be a815 - 0x00007f78c5a49194: 0000 0075 | 1748 8986 | a815 0000 | 48c7 86b0 | 1500 0001 | 0000 00e9 | 0800 0000 | 4883 8690 - 0x00007f78c5a491b4: 1500 0001 | 488b b424 | a001 0000 | 0f1f 4400 | 0048 b8d0 | 57de 6978 - - 0x00007f78c5a491cc: ; {virtual_call} - 0x00007f78c5a491cc: 7f00 00e8 - - 0x00007f78c5a491d0: ; ImmutableOopMap {} - ;*invokevirtual dispose {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1198 (line 2021) - 0x00007f78c5a491d0: 0c4e a606 - - 0x00007f78c5a491d4: ; {post_call_nop} - 0x00007f78c5a491d4: 0f1f 8400 | cc70 005b - - 0x00007f78c5a491dc: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a491dc: 48b8 88da | 4f21 0400 - - 0x00007f78c5a491e4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a491e4: 0000 48be | 4899 d292 | 787f 0000 | 8b40 0848 | 3b86 c815 | 0000 750d | 4883 86d0 | 1500 0001 - 0x00007f78c5a49204: e960 0000 | 0048 3b86 | d815 0000 | 750d 4883 | 86e0 1500 | 0001 e94a | 0000 0048 | 83be c815 - 0x00007f78c5a49224: 0000 0075 | 1748 8986 | c815 0000 | 48c7 86d0 | 1500 0001 | 0000 00e9 | 2900 0000 | 4883 bed8 - 0x00007f78c5a49244: 1500 0000 | 7517 4889 | 86d8 1500 | 0048 c786 | e015 0000 | 0100 0000 | e908 0000 | 0048 8386 - 0x00007f78c5a49264: c015 0000 - - 0x00007f78c5a49268: ; {metadata(method data for {method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a49268: 0148 b8b0 | ddae 9278 | 7f00 008b | b090 0000 | 0083 c602 | 89b0 9000 | 0000 81e6 | feff 1f00 - 0x00007f78c5a49288: 85f6 0f84 | 8a1b 0000 - - 0x00007f78c5a49290: ; {oop(a 'kotlin/Unit'{0x00000004210022f8})} - 0x00007f78c5a49290: 48b8 f822 | 0021 0400 | 0000 4881 | c470 0200 - - 0x00007f78c5a492a0: ; {poll_return} - 0x00007f78c5a492a0: 005d 493b | 6728 0f87 | 8f1b 0000 | c348 8b84 | 24a0 0100 - - 0x00007f78c5a492b4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a492b4: 0048 be48 | 99d2 9278 | 7f00 008b | 4008 483b | 8658 1900 | 0075 0d48 | 8386 6019 | 0000 01e9 - 0x00007f78c5a492d4: 6000 0000 | 483b 8668 | 1900 0075 | 0d48 8386 | 7019 0000 | 01e9 4a00 | 0000 4883 | be58 1900 - 0x00007f78c5a492f4: 0000 7517 | 4889 8658 | 1900 0048 | c786 6019 | 0000 0100 | 0000 e929 | 0000 0048 | 83be 6819 - 0x00007f78c5a49314: 0000 0075 | 1748 8986 | 6819 0000 | 48c7 8670 | 1900 0001 | 0000 00e9 | 0800 0000 | 4883 8650 - 0x00007f78c5a49334: 1900 0001 | 488b b424 | a001 0000 | 0f1f 4400 | 0048 b8f8 | 57de 6978 - - 0x00007f78c5a4934c: ; {virtual_call} - 0x00007f78c5a4934c: 7f00 00e8 - - 0x00007f78c5a49350: ; ImmutableOopMap {} - ;*invokevirtual dispose {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1524 (line 2021) - 0x00007f78c5a49350: 8c4c a606 - - 0x00007f78c5a49354: ; {post_call_nop} - 0x00007f78c5a49354: 0f1f 8400 | 4c72 005c - - 0x00007f78c5a4935c: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a4935c: 48b8 88da | 4f21 0400 - - 0x00007f78c5a49364: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49364: 0000 48be | 4899 d292 | 787f 0000 | 8b40 0848 | 3b86 8819 | 0000 750d | 4883 8690 | 1900 0001 - 0x00007f78c5a49384: e960 0000 | 0048 3b86 | 9819 0000 | 750d 4883 | 86a0 1900 | 0001 e94a | 0000 0048 | 83be 8819 - 0x00007f78c5a493a4: 0000 0075 | 1748 8986 | 8819 0000 | 48c7 8690 | 1900 0001 | 0000 00e9 | 2900 0000 | 4883 be98 - 0x00007f78c5a493c4: 1900 0000 | 7517 4889 | 8698 1900 | 0048 c786 | a019 0000 | 0100 0000 | e908 0000 | 0048 8386 - 0x00007f78c5a493e4: 8019 0000 - - 0x00007f78c5a493e8: ; {metadata(method data for {method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a493e8: 0148 b8b0 | ddae 9278 | 7f00 008b | b090 0000 | 0083 c602 | 89b0 9000 | 0000 81e6 | feff 1f00 - 0x00007f78c5a49408: 85f6 0f84 | 411a 0000 - - 0x00007f78c5a49410: ; {oop(a 'kotlin/Unit'{0x00000004210022f8})} - 0x00007f78c5a49410: 48b8 f822 | 0021 0400 | 0000 4881 | c470 0200 - - 0x00007f78c5a49420: ; {poll_return} - 0x00007f78c5a49420: 005d 493b | 6728 0f87 | 461a 0000 | c348 8b84 | 24a0 0100 - - 0x00007f78c5a49434: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49434: 0048 be48 | 99d2 9278 | 7f00 008b | 4008 483b | 86b8 1c00 | 0075 0d48 | 8386 c01c | 0000 01e9 - 0x00007f78c5a49454: 6000 0000 | 483b 86c8 | 1c00 0075 | 0d48 8386 | d01c 0000 | 01e9 4a00 | 0000 4883 | beb8 1c00 - 0x00007f78c5a49474: 0000 7517 | 4889 86b8 | 1c00 0048 | c786 c01c | 0000 0100 | 0000 e929 | 0000 0048 | 83be c81c - 0x00007f78c5a49494: 0000 0075 | 1748 8986 | c81c 0000 | 48c7 86d0 | 1c00 0001 | 0000 00e9 | 0800 0000 | 4883 86b0 - 0x00007f78c5a494b4: 1c00 0001 | 488b b424 | a001 0000 | 0f1f 4400 | 0048 b820 | 58de 6978 - - 0x00007f78c5a494cc: ; {virtual_call} - 0x00007f78c5a494cc: 7f00 00e8 - - 0x00007f78c5a494d0: ; ImmutableOopMap {} - ;*invokevirtual dispose {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1838 (line 2021) - 0x00007f78c5a494d0: 0c4b a606 - - 0x00007f78c5a494d4: ; {post_call_nop} - 0x00007f78c5a494d4: 0f1f 8400 | cc73 005d - - 0x00007f78c5a494dc: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a494dc: 48b8 88da | 4f21 0400 - - 0x00007f78c5a494e4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a494e4: 0000 48ba | 4899 d292 | 787f 0000 | 8b40 0848 | 3b82 e81c | 0000 750d | 4883 82f0 | 1c00 0001 - 0x00007f78c5a49504: e960 0000 | 0048 3b82 | f81c 0000 | 750d 4883 | 8200 1d00 | 0001 e94a | 0000 0048 | 83ba e81c - 0x00007f78c5a49524: 0000 0075 | 1748 8982 | e81c 0000 | 48c7 82f0 | 1c00 0001 | 0000 00e9 | 2900 0000 | 4883 baf8 - 0x00007f78c5a49544: 1c00 0000 | 7517 4889 | 82f8 1c00 | 0048 c782 | 001d 0000 | 0100 0000 | e908 0000 | 0048 8382 - 0x00007f78c5a49564: e01c 0000 - - 0x00007f78c5a49568: ; {metadata(method data for {method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a49568: 0148 b8b0 | ddae 9278 | 7f00 008b | 9090 0000 | 0083 c202 | 8990 9000 | 0000 81e2 | feff 1f00 - 0x00007f78c5a49588: 85d2 0f84 | f818 0000 - - 0x00007f78c5a49590: ; {oop(a 'kotlin/Unit'{0x00000004210022f8})} - 0x00007f78c5a49590: 48b8 f822 | 0021 0400 | 0000 4881 | c470 0200 - - 0x00007f78c5a495a0: ; {poll_return} - 0x00007f78c5a495a0: 005d 493b | 6728 0f87 | fd18 0000 - - 0x00007f78c5a495ac: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a495ac: c348 b888 | da4f 2104 - - 0x00007f78c5a495b4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a495b4: 0000 0048 | ba48 99d2 | 9278 7f00 | 008b 4008 | 483b 8278 | 1f00 0075 | 0d48 8382 | 801f 0000 - 0x00007f78c5a495d4: 01e9 6000 | 0000 483b | 8288 1f00 | 0075 0d48 | 8382 901f | 0000 01e9 | 4a00 0000 | 4883 ba78 - 0x00007f78c5a495f4: 1f00 0000 | 7517 4889 | 8278 1f00 | 0048 c782 | 801f 0000 | 0100 0000 | e929 0000 | 0048 83ba - 0x00007f78c5a49614: 881f 0000 | 0075 1748 | 8982 881f | 0000 48c7 | 8290 1f00 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a49634: 8270 1f00 - - 0x00007f78c5a49638: ; {metadata(method data for {method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a49638: 0001 48b8 | b0dd ae92 | 787f 0000 | 8b90 9000 | 0000 83c2 | 0289 9090 | 0000 0081 | e2fe ff1f - 0x00007f78c5a49658: 0085 d20f | 845e 1800 - - 0x00007f78c5a49660: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49660: 0048 b848 | 99d2 9278 | 7f00 00ff | 80a0 1f00 - - 0x00007f78c5a49670: ; {oop(a 'kotlin/Unit'{0x00000004210022f8})} - 0x00007f78c5a49670: 0048 b8f8 | 2200 2104 | 0000 0048 | 81c4 7002 - - 0x00007f78c5a49680: ; {poll_return} - 0x00007f78c5a49680: 0000 5d49 | 3b67 280f | 8753 1800 - - 0x00007f78c5a4968c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4968c: 00c3 48be | 4899 d292 | 787f 0000 | 4883 86a8 | 1e00 0001 - - 0x00007f78c5a496a0: ; {metadata(method data for {method} {0x00007f7890faf9e0} 'composeImmediateRuntimeError' '(Ljava/lang/String;)V' in 'androidx/compose/runtime/ComposerKt')} - 0x00007f78c5a496a0: 48be c092 | 0b92 787f | 0000 8b86 | 9000 0000 | 83c0 0289 | 8690 0000 | 0025 feff | 1f00 85c0 - 0x00007f78c5a496c0: 0f84 3018 | 0000 6690 - - 0x00007f78c5a496c8: ; {other} - 0x00007f78c5a496c8: e959 1800 | 0000 0000 | 0000 80ba | 6801 0000 | 040f 8556 | 1800 0049 | 8b87 c801 | 0000 488d - 0x00007f78c5a496e8: 7828 493b | bfd8 0100 | 000f 873e | 1800 0049 | 89bf c801 | 0000 48c7 | 0001 0000 | 0048 8bca - 0x00007f78c5a49708: 8948 0848 | 33c9 8948 | 0c48 33c9 | 4889 4810 | 4889 4818 | 4889 4820 - - 0x00007f78c5a49720: ; {other} - 0x00007f78c5a49720: e92c 1800 | 0090 9090 - - 0x00007f78c5a49728: ; {oop("unexpected to get continuation here"{0x00000004229523f8})} - 0x00007f78c5a49728: 9090 48be | f823 9522 | 0400 0000 | 4889 8424 | 1802 0000 - - 0x00007f78c5a4973c: ; {static_call} - 0x00007f78c5a4973c: 6666 90e8 - - 0x00007f78c5a49740: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [528]=Oop [536]=Oop [600]=Oop } - ;*invokedynamic {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.ComposerKt::composeImmediateRuntimeError@5 (line 1431) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1964 (line 2076) - 0x00007f78c5a49740: 9c4d a606 - - 0x00007f78c5a49744: ; {post_call_nop} - 0x00007f78c5a49744: 0f1f 8400 | 3c76 005e - - 0x00007f78c5a4974c: ; {metadata(method data for {method} {0x00007f7890faf9e0} 'composeImmediateRuntimeError' '(Ljava/lang/String;)V' in 'androidx/compose/runtime/ComposerKt')} - 0x00007f78c5a4974c: 48ba c092 | 0b92 787f | 0000 488d | 92f8 0000 | 0048 85c0 | 750a f602 | 0175 05f0 | 4883 0a01 - 0x00007f78c5a4976c: 488b 9424 | 1802 0000 - - 0x00007f78c5a49774: ; {metadata(method data for {method} {0x00007f7890faf9e0} 'composeImmediateRuntimeError' '(Ljava/lang/String;)V' in 'androidx/compose/runtime/ComposerKt')} - 0x00007f78c5a49774: 48be c092 | 0b92 787f | 0000 4883 | 8608 0100 | 0001 488b | d048 8bb4 | 2418 0200 | 0066 0f1f - 0x00007f78c5a49794: ; {optimized virtual_call} - 0x00007f78c5a49794: 4400 00e8 - - 0x00007f78c5a49798: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [528]=Oop [536]=Oop [600]=Oop } - ;*invokespecial {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.ComposerKt::composeImmediateRuntimeError@10 (line 1429) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1964 (line 2076) - 0x00007f78c5a49798: 4443 a606 - - 0x00007f78c5a4979c: ; {post_call_nop} - 0x00007f78c5a4979c: 0f1f 8400 | 9476 005f | 488b 8424 - - 0x00007f78c5a497a8: ; ImmutableOopMap {rax=Oop [216]=Oop [232]=Oop [240]=Oop [528]=Oop [600]=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.ComposerKt::composeImmediateRuntimeError@13 (line 1429) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1964 (line 2076) - 0x00007f78c5a497a8: 1802 0000 - - 0x00007f78c5a497ac: ; {section_word} - 0x00007f78c5a497ac: 48ba ac97 | a4c5 787f - - 0x00007f78c5a497b4: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a497b4: 0000 e8a5 | 8fb3 0690 - - 0x00007f78c5a497bc: ; {metadata(method data for {method} {0x00007f789051cc70} 'sanitizeStackTrace' '(Ljava/lang/Throwable;)Ljava/lang/Throwable;' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a497bc: 48be 68e8 | ca90 787f | 0000 4883 | 8608 0100 | 0001 488b | f048 8bd7 - - 0x00007f78c5a497d4: ; {static_call} - 0x00007f78c5a497d4: 6666 90e8 - - 0x00007f78c5a497d8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokestatic sanitizeStackTrace {reexecute=0 rethrow=0 return_oop=0} - ; - kotlin.jvm.internal.Intrinsics::sanitizeStackTrace@6 (line 253) - ; - kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@11 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a497d8: 044d a606 - - 0x00007f78c5a497dc: ; {post_call_nop} - 0x00007f78c5a497dc: 0f1f 8400 | d476 0061 | 448b 8c24 | 7401 0000 | 448b ac24 | 6001 0000 | 448b b424 | 7001 0000 - 0x00007f78c5a497fc: 4c8b 8424 | 6801 0000 | 488b 9424 | 9002 0000 | 4c8b 9c24 | f800 0000 | 488b 8c24 | f000 0000 - 0x00007f78c5a4981c: ; {metadata(method data for {method} {0x00007f789051ba30} 'throwParameterIsNullNPE' '(Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a4981c: 4885 c048 | bb78 e2ca | 9078 7f00 | 0075 0c80 | 8b01 0100 | 0001 e995 | 0000 008b | 7808 483b - 0x00007f78c5a4983c: bb10 0100 | 0075 0d48 | 8383 1801 | 0000 01e9 | 6000 0000 | 483b bb20 | 0100 0075 | 0d48 8383 - 0x00007f78c5a4985c: 2801 0000 | 01e9 4a00 | 0000 4883 | bb10 0100 | 0000 7517 | 4889 bb10 | 0100 0048 | c783 1801 - 0x00007f78c5a4987c: 0000 0100 | 0000 e929 | 0000 0048 | 83bb 2001 | 0000 0075 | 1748 89bb | 2001 0000 | 48c7 8328 - 0x00007f78c5a4989c: 0100 0001 | 0000 00e9 | 0800 0000 | 4883 8308 | 0100 0001 - - 0x00007f78c5a498b0: ; {metadata('java/lang/NullPointerException')} - 0x00007f78c5a498b0: 48bf 98dc | 0121 0000 | 0000 8b58 | 0848 3b7b | 500f 8594 | 1600 00e9 | 0000 0000 - - 0x00007f78c5a498cc: ; implicit exception: dispatches to 0x00007f78c5a4af64 - 0x00007f78c5a498cc: 483b 0048 - - 0x00007f78c5a498d0: ; ImmutableOopMap {rcx=Oop r8=Oop r11=Oop rsi=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@17 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - ; {section_word} - 0x00007f78c5a498d0: 8bf2 48ba | d298 a4c5 | 787f 0000 - - 0x00007f78c5a498dc: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a498dc: e87f 8eb3 | 0690 498b | 87e0 0500 | 004d 33d2 | 4d89 97e0 | 0500 004d | 33d2 4d89 | 97e8 0500 - 0x00007f78c5a498fc: 0048 8bd0 | 488d 8424 | 5002 0000 | 488b 7808 | 418b b7d0 | 0700 0049 | 3b7c 37f8 | 0f85 4b16 - 0x00007f78c5a4991c: 0000 4183 | afd0 0700 | 0008 493b | 7c37 f00f | 842a 0000 | 0048 8b07 | a802 0f85 | 1200 0000 - 0x00007f78c5a4993c: 488b f048 | 83ce 01f0 | 480f b137 | 0f84 0d00 | 0000 4183 | 87d0 0700 | 0008 e90e | 1600 0048 - 0x00007f78c5a4995c: ; ImmutableOopMap {rax=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@775 (line 1965) - ; {section_word} - 0x00007f78c5a4995c: 8bc2 48ba | 5e99 a4c5 | 787f 0000 - - 0x00007f78c5a49968: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a49968: e8f3 8db3 | 0690 498b | 87e0 0500 | 004d 33d2 | 4d89 97e0 | 0500 004d | 33d2 4d89 | 97e8 0500 - 0x00007f78c5a49988: 0048 8bd0 | 488d 8424 | 5002 0000 | 488b 7808 | 418b b7d0 | 0700 0049 | 3b7c 37f8 | 0f85 d515 - 0x00007f78c5a499a8: 0000 4183 | afd0 0700 | 0008 493b | 7c37 f00f | 842a 0000 | 0048 8b07 | a802 0f85 | 1200 0000 - 0x00007f78c5a499c8: 488b f048 | 83ce 01f0 | 480f b137 | 0f84 0d00 | 0000 4183 | 87d0 0700 | 0008 e998 | 1500 0048 - 0x00007f78c5a499e8: ; ImmutableOopMap {rax=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1989 (line 2074) - ; {section_word} - 0x00007f78c5a499e8: 8bc2 48ba | ea99 a4c5 | 787f 0000 - - 0x00007f78c5a499f4: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a499f4: e867 8db3 | 0690 498b | 87e0 0500 | 004d 33d2 | 4d89 97e0 | 0500 004d | 33d2 4d89 | 97e8 0500 - 0x00007f78c5a49a14: 0048 8984 | 2420 0200 | 004c 8b84 | 24e0 0000 | 0049 3b00 - - 0x00007f78c5a49a28: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49a28: 498b f048 | bf48 99d2 | 9278 7f00 | 008b 7608 | 483b b740 | 0800 0075 | 0d48 8387 | 4808 0000 - 0x00007f78c5a49a48: 01e9 6000 | 0000 483b | b750 0800 | 0075 0d48 | 8387 5808 | 0000 01e9 | 4a00 0000 | 4883 bf40 - 0x00007f78c5a49a68: 0800 0000 | 7517 4889 | b740 0800 | 0048 c787 | 4808 0000 | 0100 0000 | e929 0000 | 0048 83bf - 0x00007f78c5a49a88: 5008 0000 | 0075 1748 | 89b7 5008 | 0000 48c7 | 8758 0800 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a49aa8: 8738 0800 | 0001 498b | f00f 1f40 | 0048 b848 | 58de 6978 - - 0x00007f78c5a49abc: ; {virtual_call} - 0x00007f78c5a49abc: 7f00 00e8 - - 0x00007f78c5a49ac0: ; ImmutableOopMap {[544]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@448 (line 665) - 0x00007f78c5a49ac0: 1c45 a606 - - 0x00007f78c5a49ac4: ; {post_call_nop} - 0x00007f78c5a49ac4: 0f1f 8400 | bc79 0065 | 488b 8424 - - 0x00007f78c5a49ad0: ; ImmutableOopMap {rax=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@455 (line 665) - 0x00007f78c5a49ad0: 2002 0000 - - 0x00007f78c5a49ad4: ; {section_word} - 0x00007f78c5a49ad4: 48ba d49a | a4c5 787f - - 0x00007f78c5a49adc: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a49adc: 0000 e87d | 8cb3 0690 | 498b 87e0 | 0500 004d | 33d2 4d89 | 97e0 0500 | 004d 33d2 | 4d89 97e8 - 0x00007f78c5a49afc: 0500 0048 | 8984 2428 | 0200 0048 | 8bbc 2480 | 0200 0048 | 3b07 488b - - 0x00007f78c5a49b14: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49b14: f748 bb48 | 99d2 9278 | 7f00 008b | 7608 483b | b3f8 1500 | 0075 0d48 | 8383 0016 | 0000 01e9 - 0x00007f78c5a49b34: 6000 0000 | 483b b308 | 1600 0075 | 0d48 8383 | 1016 0000 | 01e9 4a00 | 0000 4883 | bbf8 1500 - 0x00007f78c5a49b54: 0000 7517 | 4889 b3f8 | 1500 0048 | c783 0016 | 0000 0100 | 0000 e929 | 0000 0048 | 83bb 0816 - 0x00007f78c5a49b74: 0000 0075 | 1748 89b3 | 0816 0000 | 48c7 8310 | 1600 0001 | 0000 00e9 | 0800 0000 | 4883 83f0 - 0x00007f78c5a49b94: 1500 0001 | 488b f766 | 9048 b870 | 58de 6978 - - 0x00007f78c5a49ba4: ; {virtual_call} - 0x00007f78c5a49ba4: 7f00 00e8 - - 0x00007f78c5a49ba8: ; ImmutableOopMap {[416]=Oop [424]=Oop [552]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1220 (line 740) - 0x00007f78c5a49ba8: 3444 a606 - - 0x00007f78c5a49bac: ; {post_call_nop} - 0x00007f78c5a49bac: 0f1f 8400 | a47a 0067 | 488b 8424 - - 0x00007f78c5a49bb8: ; ImmutableOopMap {rax=Oop [416]=Oop [424]=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1227 (line 740) - 0x00007f78c5a49bb8: 2802 0000 - - 0x00007f78c5a49bbc: ; {section_word} - 0x00007f78c5a49bbc: 48ba bc9b | a4c5 787f - - 0x00007f78c5a49bc4: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a49bc4: 0000 e895 | 8bb3 0690 | 498b 87e0 | 0500 004d | 33d2 4d89 | 97e0 0500 | 004d 33d2 | 4d89 97e8 - 0x00007f78c5a49be4: 0500 0048 | 8984 2430 | 0200 0048 | 8bbc 2400 | 0100 0048 - - 0x00007f78c5a49bf8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49bf8: 8bf7 48bb | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb3 b819 | 0000 750d | 4883 83c0 | 1900 0001 - 0x00007f78c5a49c18: e960 0000 | 0048 3bb3 | c819 0000 | 750d 4883 | 83d0 1900 | 0001 e94a | 0000 0048 | 83bb b819 - 0x00007f78c5a49c38: 0000 0075 | 1748 89b3 | b819 0000 | 48c7 83c0 | 1900 0001 | 0000 00e9 | 2900 0000 | 4883 bbc8 - 0x00007f78c5a49c58: 1900 0000 | 7517 4889 | b3c8 1900 | 0048 c783 | d019 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a49c78: b019 0000 | 0148 8bf7 | 0f1f 8000 - - 0x00007f78c5a49c84: ; {optimized virtual_call} - 0x00007f78c5a49c84: 0000 00e8 - - 0x00007f78c5a49c88: ; ImmutableOopMap {[416]=Oop [424]=Oop [560]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1546 (line 755) - 0x00007f78c5a49c88: 543e a606 - - 0x00007f78c5a49c8c: ; {post_call_nop} - 0x00007f78c5a49c8c: 0f1f 8400 | 847b 0069 | 488b 8424 - - 0x00007f78c5a49c98: ; ImmutableOopMap {rax=Oop [416]=Oop [424]=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1551 (line 755) - 0x00007f78c5a49c98: 3002 0000 - - 0x00007f78c5a49c9c: ; {section_word} - 0x00007f78c5a49c9c: 48ba 9c9c | a4c5 787f - - 0x00007f78c5a49ca4: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a49ca4: 0000 e8b5 | 8ab3 0690 | 498b 87e0 | 0500 004d | 33d2 4d89 | 97e0 0500 | 004d 33d2 | 4d89 97e8 - 0x00007f78c5a49cc4: 0500 0048 | 8984 2438 | 0200 0048 | 8bbc 2488 | 0200 0048 - - 0x00007f78c5a49cd8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49cd8: 8bf7 48bb | 4899 d292 | 787f 0000 | 8b76 0848 | 3bb3 181d | 0000 750d | 4883 8320 | 1d00 0001 - 0x00007f78c5a49cf8: e960 0000 | 0048 3bb3 | 281d 0000 | 750d 4883 | 8330 1d00 | 0001 e94a | 0000 0048 | 83bb 181d - 0x00007f78c5a49d18: 0000 0075 | 1748 89b3 | 181d 0000 | 48c7 8320 | 1d00 0001 | 0000 00e9 | 2900 0000 | 4883 bb28 - 0x00007f78c5a49d38: 1d00 0000 | 7517 4889 | b328 1d00 | 0048 c783 | 301d 0000 | 0100 0000 | e908 0000 | 0048 8383 - 0x00007f78c5a49d58: 101d 0000 | 0148 8bf7 | 0f1f 8000 - - 0x00007f78c5a49d64: ; {optimized virtual_call} - 0x00007f78c5a49d64: 0000 00e8 - - 0x00007f78c5a49d68: ; ImmutableOopMap {[416]=Oop [424]=Oop [568]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1860 (line 769) - 0x00007f78c5a49d68: 743d a606 - - 0x00007f78c5a49d6c: ; {post_call_nop} - 0x00007f78c5a49d6c: 0f1f 8400 | 647c 006b | 488b 8424 - - 0x00007f78c5a49d78: ; ImmutableOopMap {rax=Oop [416]=Oop [424]=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1865 (line 769) - 0x00007f78c5a49d78: 3802 0000 - - 0x00007f78c5a49d7c: ; {section_word} - 0x00007f78c5a49d7c: 48ba 7c9d | a4c5 787f - - 0x00007f78c5a49d84: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a49d84: 0000 e8d5 | 89b3 0690 | 498b 87e0 | 0500 004d | 33d2 4d89 | 97e0 0500 | 004d 33d2 | 4d89 97e8 - 0x00007f78c5a49da4: 0500 0048 | 8984 2440 | 0200 0048 | 8bbc 24a8 | 0100 0048 | 8b94 24a0 - - 0x00007f78c5a49dbc: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49dbc: 0100 0048 | be48 99d2 | 9278 7f00 | 008b 5208 | 483b 9690 | 1d00 0075 | 0d48 8386 | 981d 0000 - 0x00007f78c5a49ddc: 01e9 6000 | 0000 483b | 96a0 1d00 | 0075 0d48 | 8386 a81d | 0000 01e9 | 4a00 0000 | 4883 be90 - 0x00007f78c5a49dfc: 1d00 0000 | 7517 4889 | 9690 1d00 | 0048 c786 | 981d 0000 | 0100 0000 | e929 0000 | 0048 83be - 0x00007f78c5a49e1c: a01d 0000 | 0075 1748 | 8996 a01d | 0000 48c7 | 86a8 1d00 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a49e3c: 8688 1d00 - - 0x00007f78c5a49e40: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a49e40: 0001 48ba | 40bf 8f92 | 787f 0000 | 8bb2 9000 | 0000 83c6 | 0289 b290 | 0000 0081 | e6fe ff1f - 0x00007f78c5a49e60: 0085 f60f | 8436 1100 - - 0x00007f78c5a49e68: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a49e68: 0048 ba40 | bf8f 9278 | 7f00 0048 | 8382 d800 - - 0x00007f78c5a49e78: ; {metadata(method data for {method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a49e78: 0000 0148 | bab8 936d | 9278 7f00 | 008b b290 | 0000 0083 | c602 89b2 | 9000 0000 | 81e6 feff - 0x00007f78c5a49e98: 1f00 85f6 | 0f84 1e11 - - 0x00007f78c5a49ea0: ; {oop(a 'androidx/compose/runtime/internal/SnapshotThreadLocal'{0x00000004214f8a00})} - 0x00007f78c5a49ea0: 0000 48be | 008a 4f21 | 0400 0000 - - 0x00007f78c5a49eac: ; {metadata(method data for {method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a49eac: 488b d648 | bb40 bf8f | 9278 7f00 | 008b 5208 | 483b 93f0 | 0000 0075 | 0d48 8383 | f800 0000 - 0x00007f78c5a49ecc: 01e9 6000 | 0000 483b | 9300 0100 | 0075 0d48 | 8383 0801 | 0000 01e9 | 4a00 0000 | 4883 bbf0 - 0x00007f78c5a49eec: 0000 0000 | 7517 4889 | 93f0 0000 | 0048 c783 | f800 0000 | 0100 0000 | e929 0000 | 0048 83bb - 0x00007f78c5a49f0c: 0001 0000 | 0075 1748 | 8993 0001 | 0000 48c7 | 8308 0100 | 0001 0000 | 00e9 0800 | 0000 4883 - 0x00007f78c5a49f2c: 83e8 0000 | 0001 488b - - 0x00007f78c5a49f34: ; {optimized virtual_call} - 0x00007f78c5a49f34: d766 90e8 - - 0x00007f78c5a49f38: ; ImmutableOopMap {[416]=Oop [576]=Oop } - ;*invokevirtual set {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@4 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1890 (line 2020) - 0x00007f78c5a49f38: a43b a606 - - 0x00007f78c5a49f3c: ; {post_call_nop} - 0x00007f78c5a49f3c: 0f1f 8400 | 347e 006d | 488b 8424 - - 0x00007f78c5a49f48: ; ImmutableOopMap {rax=Oop [416]=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1895 (line 2020) - 0x00007f78c5a49f48: 4002 0000 - - 0x00007f78c5a49f4c: ; {section_word} - 0x00007f78c5a49f4c: 48ba 4c9f | a4c5 787f - - 0x00007f78c5a49f54: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a49f54: 0000 e805 | 88b3 0690 | 498b 87e0 | 0500 004d | 33d2 4d89 | 97e0 0500 | 004d 33d2 | 4d89 97e8 - 0x00007f78c5a49f74: 0500 0048 | 8984 2448 | 0200 0048 | 8b84 24a0 | 0100 0048 | 3b00 488b - - 0x00007f78c5a49f8c: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a49f8c: f048 bb48 | 99d2 9278 | 7f00 008b | 7608 483b | b308 1e00 | 0075 0d48 | 8383 101e | 0000 01e9 - 0x00007f78c5a49fac: 6000 0000 | 483b b318 | 1e00 0075 | 0d48 8383 | 201e 0000 | 01e9 4a00 | 0000 4883 | bb08 1e00 - 0x00007f78c5a49fcc: 0000 7517 | 4889 b308 | 1e00 0048 | c783 101e | 0000 0100 | 0000 e929 | 0000 0048 | 83bb 181e - 0x00007f78c5a49fec: 0000 0075 | 1748 89b3 | 181e 0000 | 48c7 8320 | 1e00 0001 | 0000 00e9 | 0800 0000 | 4883 8300 - 0x00007f78c5a4a00c: 1e00 0001 | 488b f066 | 9048 b898 | 58de 6978 - - 0x00007f78c5a4a01c: ; {virtual_call} - 0x00007f78c5a4a01c: 7f00 00e8 - - 0x00007f78c5a4a020: ; ImmutableOopMap {[584]=Oop } - ;*invokevirtual dispose {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1909 (line 2021) - 0x00007f78c5a4a020: bc3f a606 - - 0x00007f78c5a4a024: ; {post_call_nop} - 0x00007f78c5a4a024: 0f1f 8400 | 1c7f 006f | 488b 8424 - - 0x00007f78c5a4a030: ; ImmutableOopMap {rax=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1914 (line 2021) - 0x00007f78c5a4a030: 4802 0000 - - 0x00007f78c5a4a034: ; {section_word} - 0x00007f78c5a4a034: 48ba 34a0 | a4c5 787f - - 0x00007f78c5a4a03c: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a4a03c: 0000 e81d | 87b3 0690 | 498b 87e0 | 0500 004d | 33d2 4d89 | 97e0 0500 | 004d 33d2 | 4d89 97e8 - 0x00007f78c5a4a05c: 0500 0048 | 8bd0 488d | 8424 5002 | 0000 488b | 7808 418b | b7d0 0700 | 0049 3b7c | 37f8 0f85 - 0x00007f78c5a4a07c: 660f 0000 | 4183 afd0 | 0700 0008 | 493b 7c37 | f00f 842a | 0000 0048 | 8b07 a802 | 0f85 1200 - 0x00007f78c5a4a09c: 0000 488b | f048 83ce | 01f0 480f | b137 0f84 | 0d00 0000 | 4183 87d0 | 0700 0008 | e929 0f00 - 0x00007f78c5a4a0bc: ; ImmutableOopMap {rax=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@222 (line 1949) - 0x00007f78c5a4a0bc: 0048 8bc2 - - 0x00007f78c5a4a0c0: ; {section_word} - 0x00007f78c5a4a0c0: 48ba c0a0 | a4c5 787f - - 0x00007f78c5a4a0c8: ; {runtime_call C1 Runtime handle_exception_nofpu_blob} - 0x00007f78c5a4a0c8: 0000 e891 | 86b3 0690 | 498b 87e0 | 0500 004d | 33d2 4d89 | 97e0 0500 | 004d 33d2 | 4d89 97e8 - 0x00007f78c5a4a0e8: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a4a0e8: 0500 0048 | be88 da4f | 2104 0000 - - 0x00007f78c5a4a0f4: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a0f4: 0048 bf48 | 99d2 9278 | 7f00 008b | 7608 483b | b7c0 1f00 | 0075 0d48 | 8387 c81f | 0000 01e9 - 0x00007f78c5a4a114: 6000 0000 | 483b b7d0 | 1f00 0075 | 0d48 8387 | d81f 0000 | 01e9 4a00 | 0000 4883 | bfc0 1f00 - 0x00007f78c5a4a134: 0000 7517 | 4889 b7c0 | 1f00 0048 | c787 c81f | 0000 0100 | 0000 e929 | 0000 0048 | 83bf d01f - 0x00007f78c5a4a154: 0000 0075 | 1748 89b7 | d01f 0000 | 48c7 87d8 | 1f00 0001 | 0000 00e9 | 0800 0000 | 4883 87b8 - 0x00007f78c5a4a174: 1f00 0001 - - 0x00007f78c5a4a178: ; {metadata(method data for {method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4a178: 48be b0dd | ae92 787f | 0000 8bbe | 9000 0000 | 83c7 0289 | be90 0000 | 0081 e7fe | ff1f 0085 - 0x00007f78c5a4a198: ff0f 845d | 0e00 00e9 | c40e 0000 | 498b 87e0 | 0500 004d | 33d2 4d89 | 97e0 0500 | 004d 33d2 - 0x00007f78c5a4a1b8: 4d89 97e8 - - 0x00007f78c5a4a1bc: ; {oop(a 'androidx/compose/runtime/internal/Trace'{0x00000004214fda88})} - 0x00007f78c5a4a1bc: 0500 0048 | be88 da4f | 2104 0000 - - 0x00007f78c5a4a1c8: ; {metadata(method data for {method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a1c8: 0048 bf48 | 99d2 9278 | 7f00 008b | 7608 483b | b7f8 0100 | 0075 0d48 | 8387 0002 | 0000 01e9 - 0x00007f78c5a4a1e8: 6000 0000 | 483b b708 | 0200 0075 | 0d48 8387 | 1002 0000 | 01e9 4a00 | 0000 4883 | bff8 0100 - 0x00007f78c5a4a208: 0000 7517 | 4889 b7f8 | 0100 0048 | c787 0002 | 0000 0100 | 0000 e929 | 0000 0048 | 83bf 0802 - 0x00007f78c5a4a228: 0000 0075 | 1748 89b7 | 0802 0000 | 48c7 8710 | 0200 0001 | 0000 00e9 | 0800 0000 | 4883 87f0 - 0x00007f78c5a4a248: 0100 0001 - - 0x00007f78c5a4a24c: ; {metadata(method data for {method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4a24c: 48be b0dd | ae92 787f | 0000 8bbe | 9000 0000 | 83c7 0289 | be90 0000 | 0081 e7fe | ff1f 0085 - 0x00007f78c5a4a26c: ff0f 84aa | 0d00 00e9 | f00d 0000 - - 0x00007f78c5a4a278: ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a278: 49ba 4868 | f790 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4a28c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a28c: ffff ffe8 - - 0x00007f78c5a4a290: ; ImmutableOopMap {rsi=Oop rdx=Oop rcx=Oop r8=Oop r9=Oop rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@-1 (line 629) - 0x00007f78c5a4a290: ccc8 b306 | e9dc 7fff - - 0x00007f78c5a4a298: ; {metadata({method} {0x00007f7890e27d88} 'access$getHasBroadcastFrameClockAwaiters' '(Landroidx/compose/runtime/Recomposer;)Z' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4a298: ff49 ba88 | 7de2 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4a2b0: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a2b0: e8ab c8b3 - - 0x00007f78c5a4a2b4: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::access$getHasBroadcastFrameClockAwaiters@-1 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1 (line 629) - 0x00007f78c5a4a2b4: 06e9 f37f - - 0x00007f78c5a4a2b8: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a2b8: ffff e821 - - 0x00007f78c5a4a2bc: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokespecial getHasBroadcastFrameClockAwaiters {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer::access$getHasBroadcastFrameClockAwaiters@1 (line 191) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1 (line 629) - ; {metadata({method} {0x00007f7891032318} 'beginSection' '(Ljava/lang/String;)Ljava/lang/Object;' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4a2bc: 69b3 0649 | ba18 2303 | 9178 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4a2d4: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a2d4: ffff e885 - - 0x00007f78c5a4a2d8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::beginSection@-1 (line 22) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@20 (line 1941) - 0x00007f78c5a4a2d8: c8b3 06e9 | dd80 ffff - - 0x00007f78c5a4a2e0: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a2e0: e8fb 68b3 - - 0x00007f78c5a4a2e4: ; ImmutableOopMap {rsi=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual sendFrame {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - ; {metadata({method} {0x00007f7890e5a2f0} 'sendFrame' '(J)V' in 'androidx/compose/runtime/BroadcastFrameClock')} - 0x00007f78c5a4a2e4: 0649 baf0 | a2e5 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4a2fc: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a2fc: e85f c8b3 - - 0x00007f78c5a4a300: ; ImmutableOopMap {rsi=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@-1 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - 0x00007f78c5a4a300: 06e9 8781 - - 0x00007f78c5a4a304: ; {metadata({method} {0x00007f7890558a48} 'linkToTargetMethod' '(JLjava/lang/Object;)Ljava/lang/Object;' in 'java/lang/invoke/Invokers$Holder')} - 0x00007f78c5a4a304: ffff 49ba | 488a 5590 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4a31c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a31c: ffe8 3ec8 - - 0x00007f78c5a4a320: ; ImmutableOopMap {rsi=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - java.lang.invoke.Invokers$Holder::linkToTargetMethod@-1 - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@5 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - 0x00007f78c5a4a320: b306 e9a5 - - 0x00007f78c5a4a324: ; {metadata({method} {0x00007f7890490820} 'newInvokeSpecial' '(Ljava/lang/Object;J)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle$Holder')} - 0x00007f78c5a4a324: 81ff ff49 | ba20 0849 | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4a33c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a33c: ffff e81d - - 0x00007f78c5a4a340: ; ImmutableOopMap {rsi=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - java.lang.invoke.DirectMethodHandle$Holder::newInvokeSpecial@-1 - ; - java.lang.invoke.Invokers$Holder::linkToTargetMethod@5 - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@5 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - 0x00007f78c5a4a340: c8b3 06e9 | bc81 ffff - - 0x00007f78c5a4a348: ; {metadata({method} {0x00007f78900834b0} 'allocateInstance' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle')} - 0x00007f78c5a4a348: 49ba b034 | 0890 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4a35c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a35c: ffff ffe8 - - 0x00007f78c5a4a360: ; ImmutableOopMap {rsi=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - java.lang.invoke.DirectMethodHandle::allocateInstance@-1 - ; - java.lang.invoke.DirectMethodHandle$Holder::newInvokeSpecial@1 - ; - java.lang.invoke.Invokers$Holder::linkToTargetMethod@5 - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@5 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - 0x00007f78c5a4a360: fcc7 b306 | e9d3 81ff - - 0x00007f78c5a4a368: ; {metadata({method} {0x00007f78900833f0} 'constructorMethod' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'java/lang/invoke/DirectMethodHandle')} - 0x00007f78c5a4a368: ff49 baf0 | 3308 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4a380: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a380: e8db c7b3 - - 0x00007f78c5a4a384: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [264]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - java.lang.invoke.DirectMethodHandle::constructorMethod@-1 - ; - java.lang.invoke.DirectMethodHandle$Holder::newInvokeSpecial@6 - ; - java.lang.invoke.Invokers$Holder::linkToTargetMethod@5 - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@5 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - 0x00007f78c5a4a384: 06e9 e182 - - 0x00007f78c5a4a388: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a388: ffff e851 - - 0x00007f78c5a4a38c: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [264]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokestatic linkToSpecial {reexecute=0 rethrow=0 return_oop=0} - ; - java.lang.invoke.DirectMethodHandle$Holder::newInvokeSpecial@18 - ; - java.lang.invoke.Invokers$Holder::linkToTargetMethod@5 - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@5 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - ; {metadata({method} {0x00007f78922256c0} '' '(J)V' in 'androidx/compose/runtime/BroadcastFrameClock$$Lambda+0x000000002142f288')} - 0x00007f78c5a4a38c: 68b3 0649 | bac0 5622 | 9278 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4a3a4: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a3a4: ffff e8b5 - - 0x00007f78c5a4a3a8: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [264]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.BroadcastFrameClock$$Lambda/0x000000002142f288::@-1 - ; - java.lang.invoke.DirectMethodHandle$Holder::newInvokeSpecial@18 - ; - java.lang.invoke.Invokers$Holder::linkToTargetMethod@5 - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@5 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - 0x00007f78c5a4a3a8: c7b3 06e9 | fa82 ffff - - 0x00007f78c5a4a3b0: ; {metadata({method} {0x00007f78900005d0} '' '()V' in 'java/lang/Object')} - 0x00007f78c5a4a3b0: 49ba d005 | 0090 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4a3c4: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a3c4: ffff ffe8 - - 0x00007f78c5a4a3c8: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [264]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - java.lang.Object::@-1 - ; - androidx.compose.runtime.BroadcastFrameClock$$Lambda/0x000000002142f288::@1 - ; - java.lang.invoke.DirectMethodHandle$Holder::newInvokeSpecial@18 - ; - java.lang.invoke.Invokers$Holder::linkToTargetMethod@5 - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@5 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - 0x00007f78c5a4a3c8: 94c7 b306 | e915 83ff - - 0x00007f78c5a4a3d0: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a3d0: ffe8 0a68 - - 0x00007f78c5a4a3d4: ; ImmutableOopMap {rax=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual flushAndDispatchAwaiters {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.BroadcastFrameClock::sendFrame@10 (line 71) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@35 (line 633) - ; {metadata({method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4a3d4: b306 49ba | e023 0391 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4a3ec: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a3ec: ffe8 6ec7 - - 0x00007f78c5a4a3f0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::endSection@-1 (line 27) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@55 (line 1945) - 0x00007f78c5a4a3f0: b306 e971 - - 0x00007f78c5a4a3f4: ; {metadata({method} {0x00007f7891032318} 'beginSection' '(Ljava/lang/String;)Ljava/lang/Object;' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4a3f4: 85ff ff49 | ba18 2303 | 9178 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4a40c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a40c: ffff e84d - - 0x00007f78c5a4a410: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::beginSection@-1 (line 22) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@90 (line 1946) - 0x00007f78c5a4a410: c7b3 06e9 | 1486 ffff - - 0x00007f78c5a4a418: ; {metadata({method} {0x00007f7890e279b8} 'access$recordComposerModifications' '(Landroidx/compose/runtime/Recomposer;)Z' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4a418: 49ba b879 | e290 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4a42c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a42c: ffff ffe8 - - 0x00007f78c5a4a430: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::access$recordComposerModifications@-1 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@100 (line 643) - 0x00007f78c5a4a430: 2cc7 b306 | e934 86ff - - 0x00007f78c5a4a438: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a438: ffe8 a267 - - 0x00007f78c5a4a43c: ; ImmutableOopMap {rsi=Oop rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [288]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*monitorenter {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@119 (line 1949) - 0x00007f78c5a4a43c: b306 4889 | 7c24 0848 - - 0x00007f78c5a4a444: ; {runtime_call C1 Runtime monitorenter_nofpu_blob} - 0x00007f78c5a4a444: 8914 24e8 - - 0x00007f78c5a4a448: ; ImmutableOopMap {rsi=Oop rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*monitorenter {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@119 (line 1949) - 0x00007f78c5a4a448: 149f b306 | e9d4 86ff - - 0x00007f78c5a4a450: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a450: ffe8 8a67 - - 0x00007f78c5a4a454: ; ImmutableOopMap {rsi=Oop rdi=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*getfield content {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@138 (line 1951) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a454: b306 e885 - - 0x00007f78c5a4a458: ; ImmutableOopMap {rbx=Oop r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [280]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@161 (line 1954) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a458: 67b3 06e8 - - 0x00007f78c5a4a45c: ; ImmutableOopMap {rbx=Oop r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [280]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@161 (line 1954) - 0x00007f78c5a4a45c: 8067 b306 | 4889 0c24 | 4889 5c24 - - 0x00007f78c5a4a468: ; {runtime_call C1 Runtime throw_range_check_failed_blob} - 0x00007f78c5a4a468: 08e8 725e - - 0x00007f78c5a4a46c: ; ImmutableOopMap {rbx=Oop r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [280]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@161 (line 1954) - 0x00007f78c5a4a46c: b306 4889 - - 0x00007f78c5a4a470: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a470: 1424 e869 - - 0x00007f78c5a4a474: ; ImmutableOopMap {rbx=Oop r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [280]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@162 (line 1954) - 0x00007f78c5a4a474: 8fb3 064c - - 0x00007f78c5a4a478: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a478: 8904 24e8 - - 0x00007f78c5a4a47c: ; ImmutableOopMap {rbx=Oop r8=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [280]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@171 (line 645) - 0x00007f78c5a4a47c: 608f b306 - - 0x00007f78c5a4a480: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a480: e85b 67b3 - - 0x00007f78c5a4a484: ; ImmutableOopMap {rbx=Oop r8=Oop rdx=Oop r9=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [280]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@176 (line 645) - ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a484: 0649 ba48 | 68f7 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | be00 0000 - - 0x00007f78c5a4a49c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a49c: e8bf c6b3 - - 0x00007f78c5a4a4a0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [280]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@190 (line 1955) - 0x00007f78c5a4a4a0: 06e9 de89 - - 0x00007f78c5a4a4a4: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a4a4: ffff e835 - - 0x00007f78c5a4a4a8: ; ImmutableOopMap {rsi=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [288]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@198 (line 646) - 0x00007f78c5a4a4a8: 67b3 0648 | 8d84 2450 | 0200 0048 - - 0x00007f78c5a4a4b4: ; {runtime_call C1 Runtime monitorexit_nofpu_blob} - 0x00007f78c5a4a4b4: 8904 24e8 | 24a5 b306 | e936 8bff - - 0x00007f78c5a4a4c0: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a4c0: ffe8 1a67 - - 0x00007f78c5a4a4c4: ; ImmutableOopMap {rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@226 (line 650) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a4c4: b306 e815 - - 0x00007f78c5a4a4c8: ; ImmutableOopMap {rcx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@230 (line 651) - 0x00007f78c5a4a4c8: 67b3 064c - - 0x00007f78c5a4a4cc: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a4cc: 8904 24e8 - - 0x00007f78c5a4a4d0: ; ImmutableOopMap {r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@234 (line 652) - 0x00007f78c5a4a4d0: 0c8f b306 - - 0x00007f78c5a4a4d4: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a4d4: e807 67b3 - - 0x00007f78c5a4a4d8: ; ImmutableOopMap {r8=Oop r11=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface isEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@237 (line 652) - 0x00007f78c5a4a4d8: 064c 890c - - 0x00007f78c5a4a4dc: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a4dc: 24e8 fe8e - - 0x00007f78c5a4a4e0: ; ImmutableOopMap {r9=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@255 (line 652) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a4e0: b306 e8f9 - - 0x00007f78c5a4a4e4: ; ImmutableOopMap {r9=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface isEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@258 (line 652) - 0x00007f78c5a4a4e4: 66b3 064c - - 0x00007f78c5a4a4e8: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a4e8: 8904 24e8 - - 0x00007f78c5a4a4ec: ; ImmutableOopMap {r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@286 (line 1958) - 0x00007f78c5a4a4ec: f08e b306 - - 0x00007f78c5a4a4f0: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a4f0: e8eb 66b3 - - 0x00007f78c5a4a4f4: ; ImmutableOopMap {r8=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface size {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@289 (line 1958) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a4f4: 06e8 e666 - - 0x00007f78c5a4a4f8: ; ImmutableOopMap {r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@307 (line 1959) - 0x00007f78c5a4a4f8: b306 4889 - - 0x00007f78c5a4a4fc: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a4fc: 0424 e8dd - - 0x00007f78c5a4a500: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@316 (line 1960) - ; {metadata({method} {0x00007f7890e28000} 'access$performRecompose' '(Landroidx/compose/runtime/Recomposer;Landroidx/compose/runtime/ControlledComposition;Landroidx/collection/MutableScatterSet;)Landroidx/compose/runtime/ControlledComposition;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4a500: 8eb3 0649 | ba00 80e2 | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4a518: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a518: ffff e841 - - 0x00007f78c5a4a51c: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::access$performRecompose@-1 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@328 (line 655) - 0x00007f78c5a4a51c: c6b3 06e9 | 5e93 ffff | 4889 3c24 - - 0x00007f78c5a4a528: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a528: e8b3 8eb3 - - 0x00007f78c5a4a52c: ; ImmutableOopMap {rax=Oop rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [304]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@342 (line 656) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a52c: 06e8 ae66 - - 0x00007f78c5a4a530: ; ImmutableOopMap {rax=Oop rdi=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [304]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@347 (line 656) - ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a530: b306 49ba | 4868 f790 | 787f 0000 | 4c89 5424 | 0848 c704 | 247b 0100 - - 0x00007f78c5a4a548: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a548: 00e8 12c6 - - 0x00007f78c5a4a54c: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@379 (line 1958) - 0x00007f78c5a4a54c: b306 e921 - - 0x00007f78c5a4a550: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a550: 96ff ffe8 - - 0x00007f78c5a4a554: ; ImmutableOopMap {r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@384 (line 665) - 0x00007f78c5a4a554: 8866 b306 - - 0x00007f78c5a4a558: ; {metadata({method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a4a558: 49ba e881 | e790 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4a56c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a56c: ffff ffe8 - - 0x00007f78c5a4a570: ; ImmutableOopMap {rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.collection.ScatterSet::isNotEmpty@-1 (line 155) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@457 (line 673) - 0x00007f78c5a4a570: ecc5 b306 | e9b8 97ff - - 0x00007f78c5a4a578: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a578: ffe8 6266 - - 0x00007f78c5a4a57c: ; ImmutableOopMap {rdx=Oop rsi=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual getSize {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@474 (line 1964) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a57c: b306 e85d - - 0x00007f78c5a4a580: ; ImmutableOopMap {rdx=Oop rsi=Oop rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*monitorenter {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@503 (line 1965) - 0x00007f78c5a4a580: 66b3 0648 | 897c 2408 | 4889 1c24 - - 0x00007f78c5a4a58c: ; {runtime_call C1 Runtime monitorenter_nofpu_blob} - 0x00007f78c5a4a58c: e8cf 9db3 - - 0x00007f78c5a4a590: ; ImmutableOopMap {rdx=Oop rsi=Oop rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*monitorenter {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@503 (line 1965) - 0x00007f78c5a4a590: 06e9 b599 - - 0x00007f78c5a4a594: ; {metadata({method} {0x00007f7890e280c0} 'access$knownCompositionsLocked' '(Landroidx/compose/runtime/Recomposer;)Ljava/util/List;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4a594: ffff 49ba | c080 e290 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4a5ac: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a5ac: ffe8 aec5 - - 0x00007f78c5a4a5b0: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::access$knownCompositionsLocked@-1 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@509 (line 676) - 0x00007f78c5a4a5b0: b306 e9cc | 99ff ff48 - - 0x00007f78c5a4a5b8: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a5b8: 8904 24e8 - - 0x00007f78c5a4a5bc: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@522 (line 1966) - 0x00007f78c5a4a5bc: 208e b306 - - 0x00007f78c5a4a5c0: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a5c0: e81b 66b3 - - 0x00007f78c5a4a5c4: ; ImmutableOopMap {rax=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface size {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@525 (line 1966) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a5c4: 06e8 1666 - - 0x00007f78c5a4a5c8: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@543 (line 1967) - 0x00007f78c5a4a5c8: b306 4889 - - 0x00007f78c5a4a5cc: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a5cc: 0424 e80d - - 0x00007f78c5a4a5d0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@552 (line 1968) - ; {runtime_call C1 Runtime throw_incompatible_class_change_error_blob} - 0x00007f78c5a4a5d0: 8eb3 06e8 - - 0x00007f78c5a4a5d4: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [336]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface observesAnyOf {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@573 (line 679) - 0x00007f78c5a4a5d4: 0891 b306 - - 0x00007f78c5a4a5d8: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a5d8: e803 66b3 - - 0x00007f78c5a4a5dc: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [336]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface observesAnyOf {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@573 (line 679) - 0x00007f78c5a4a5dc: 064c 8904 - - 0x00007f78c5a4a5e0: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a5e0: 24e8 fa8d - - 0x00007f78c5a4a5e4: ; ImmutableOopMap {rdi=Oop r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [336]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@582 (line 681) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a5e4: b306 e8f5 - - 0x00007f78c5a4a5e8: ; ImmutableOopMap {rdi=Oop r8=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [336]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@587 (line 681) - ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a5e8: 65b3 0649 | ba48 68f7 | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 5602 - - 0x00007f78c5a4a600: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a600: 0000 e859 - - 0x00007f78c5a4a604: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [320]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@598 (line 1966) - 0x00007f78c5a4a604: c5b3 06e9 | 00a0 ffff - - 0x00007f78c5a4a60c: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a60c: e8cf 65b3 - - 0x00007f78c5a4a610: ; ImmutableOopMap {rsi=Oop rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual getSize {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@616 (line 1972) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a610: 06e8 ca65 - - 0x00007f78c5a4a614: ; ImmutableOopMap {rdi=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@638 (line 1974) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a614: b306 e8c5 - - 0x00007f78c5a4a618: ; ImmutableOopMap {rdi=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@638 (line 1974) - 0x00007f78c5a4a618: 65b3 0648 | 8904 2448 | 8954 2408 - - 0x00007f78c5a4a624: ; {runtime_call C1 Runtime throw_range_check_failed_blob} - 0x00007f78c5a4a624: e8b7 5cb3 - - 0x00007f78c5a4a628: ; ImmutableOopMap {rdi=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@638 (line 1974) - 0x00007f78c5a4a628: 0648 8914 - - 0x00007f78c5a4a62c: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a62c: 24e8 ae8d - - 0x00007f78c5a4a630: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@639 (line 1974) - 0x00007f78c5a4a630: b306 4c89 - - 0x00007f78c5a4a634: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a634: 0424 e8a5 - - 0x00007f78c5a4a638: ; ImmutableOopMap {r9=Oop r8=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@668 (line 691) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a638: 8db3 06e8 - - 0x00007f78c5a4a63c: ; ImmutableOopMap {r9=Oop r8=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@673 (line 691) - 0x00007f78c5a4a63c: a065 b306 - - 0x00007f78c5a4a640: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a640: e89b 65b3 - - 0x00007f78c5a4a644: ; ImmutableOopMap {rdi=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@719 (line 1980) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a644: 06e8 9665 - - 0x00007f78c5a4a648: ; ImmutableOopMap {rdi=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@719 (line 1980) - 0x00007f78c5a4a648: b306 4889 | 0424 4889 - - 0x00007f78c5a4a650: ; {runtime_call C1 Runtime throw_range_check_failed_blob} - 0x00007f78c5a4a650: 7424 08e8 - - 0x00007f78c5a4a654: ; ImmutableOopMap {rdi=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@719 (line 1980) - 0x00007f78c5a4a654: 885c b306 | 4c89 0424 | 4889 7424 - - 0x00007f78c5a4a660: ; {runtime_call C1 Runtime throw_range_check_failed_blob} - 0x00007f78c5a4a660: 08e8 7a5c - - 0x00007f78c5a4a664: ; ImmutableOopMap {rdi=Oop rsi=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aastore {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@720 (line 1980) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a664: b306 e875 - - 0x00007f78c5a4a668: ; ImmutableOopMap {rdi=Oop rsi=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aastore {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@720 (line 1980) - 0x00007f78c5a4a668: 65b3 0648 - - 0x00007f78c5a4a66c: ; {runtime_call C1 Runtime throw_array_store_exception_blob} - 0x00007f78c5a4a66c: 8914 24e8 - - 0x00007f78c5a4a670: ; ImmutableOopMap {rdi=Oop rsi=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*aastore {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@720 (line 1980) - 0x00007f78c5a4a670: 6c8a b306 | 448b 0649 | c1e0 0349 | 83f8 000f | 84a4 a6ff | ff4c 8904 - - 0x00007f78c5a4a688: ; {runtime_call g1_pre_barrier_slow} - 0x00007f78c5a4a688: 24e8 d2cb | b306 e996 | a6ff ff48 | 83fa 000f | 84aa a6ff | ff48 8934 - - 0x00007f78c5a4a6a0: ; {runtime_call g1_post_barrier_slow} - 0x00007f78c5a4a6a0: 24e8 3acf | b306 e99c - - 0x00007f78c5a4a6a8: ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a6a8: a6ff ff49 | ba48 68f7 | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 d402 - - 0x00007f78c5a4a6c0: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a6c0: 0000 e899 - - 0x00007f78c5a4a6c4: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@724 (line 1973) - 0x00007f78c5a4a6c4: c4b3 06e9 | a4a6 ffff - - 0x00007f78c5a4a6cc: ; {metadata({method} {0x00007f7890f34620} 'fill' '([Ljava/lang/Object;Ljava/lang/Object;II)V' in 'kotlin/collections/ArraysKt___ArraysJvmKt')} - 0x00007f78c5a4a6cc: 49ba 2046 | f390 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4a6e0: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a6e0: ffff ffe8 - - 0x00007f78c5a4a6e4: ; ImmutableOopMap {rdi=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@-1 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a4a6e4: 78c4 b306 | e90c a7ff - - 0x00007f78c5a4a6ec: ; {metadata({method} {0x00007f789051b880} 'checkNotNullParameter' '(Ljava/lang/Object;Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a4a6ec: ff49 ba80 | b851 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4a704: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a704: e857 c4b3 - - 0x00007f78c5a4a708: ; ImmutableOopMap {rdi=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@-1 (line 130) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a4a708: 06e9 24a7 | ffff 488d | 8424 5002 | 0000 4889 - - 0x00007f78c5a4a718: ; {runtime_call C1 Runtime monitorexit_nofpu_blob} - 0x00007f78c5a4a718: 0424 e8c1 | a2b3 06e9 | 6ba8 ffff - - 0x00007f78c5a4a724: ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a724: 49ba 4868 | f790 787f | 0000 4c89 | 5424 0848 | c704 2410 - - 0x00007f78c5a4a738: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a738: 0300 00e8 - - 0x00007f78c5a4a73c: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*ifeq {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@784 (line 700) - 0x00007f78c5a4a73c: 20c4 b306 | e944 a9ff - - 0x00007f78c5a4a744: ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a744: ff49 ba48 | 68f7 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | 2c03 0000 - - 0x00007f78c5a4a75c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a75c: e8ff c3b3 - - 0x00007f78c5a4a760: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*ifeq {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@812 (line 703) - 0x00007f78c5a4a760: 06e9 eea9 - - 0x00007f78c5a4a764: ; {metadata({method} {0x00007f7890e281a0} 'access$performInsertValues' '(Landroidx/compose/runtime/Recomposer;Ljava/util/List;Landroidx/collection/MutableScatterSet;)Ljava/util/List;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4a764: ffff 49ba | a081 e290 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4a77c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a77c: ffe8 dec3 - - 0x00007f78c5a4a780: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::access$performInsertValues@-1 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@821 (line 704) - 0x00007f78c5a4a780: b306 e937 | aaff ff48 - - 0x00007f78c5a4a788: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a788: 8904 24e8 - - 0x00007f78c5a4a78c: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@824 (line 704) - 0x00007f78c5a4a78c: 508c b306 - - 0x00007f78c5a4a790: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a790: e84b 64b3 - - 0x00007f78c5a4a794: ; ImmutableOopMap {rdx=Oop rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokevirtual plusAssign {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@827 (line 704) - ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a794: 0649 ba48 | 68f7 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | 4403 0000 - - 0x00007f78c5a4a7ac: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a7ac: e8af c3b3 - - 0x00007f78c5a4a7b0: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@836 (line 705) - 0x00007f78c5a4a7b0: 06e9 15ac | ffff 4c89 - - 0x00007f78c5a4a7b8: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a7b8: 0c24 e821 - - 0x00007f78c5a4a7bc: ; ImmutableOopMap {r9=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@796 (line 703) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a7bc: 8cb3 06e8 - - 0x00007f78c5a4a7c0: ; ImmutableOopMap {r9=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*invokeinterface isEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@799 (line 703) - 0x00007f78c5a4a7c0: 1c64 b306 - - 0x00007f78c5a4a7c4: ; {metadata({method} {0x00007f789051ba30} 'throwParameterIsNullNPE' '(Ljava/lang/String;)V' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a4a7c4: 49ba 30ba | 5190 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4a7d8: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a7d8: ffff ffe8 - - 0x00007f78c5a4a7dc: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@-1 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a4a7dc: 80c3 b306 | e9e7 adff - - 0x00007f78c5a4a7e4: ; {metadata(nullptr)} - 0x00007f78c5a4a7e4: ff48 ba00 | 0000 0000 | 0000 00b8 | 000f 050a - - 0x00007f78c5a4a7f4: ; {runtime_call C1 Runtime load_klass_patching_blob} - 0x00007f78c5a4a7f4: e867 aeb3 - - 0x00007f78c5a4a7f8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*new {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@0 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a4a7f8: 06e9 d2ad | ffff 488b - - 0x00007f78c5a4a800: ; {runtime_call C1 Runtime fast_new_instance_blob} - 0x00007f78c5a4a800: d2e8 da6d - - 0x00007f78c5a4a804: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*new {reexecute=0 rethrow=0 return_oop=0} - ; - kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@0 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a4a804: b306 e914 - - 0x00007f78c5a4a808: ; {metadata({method} {0x00007f789051cc70} 'sanitizeStackTrace' '(Ljava/lang/Throwable;)Ljava/lang/Throwable;' in 'kotlin/jvm/internal/Intrinsics')} - 0x00007f78c5a4a808: aeff ff49 | ba70 cc51 | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4a820: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a820: ffff e839 - - 0x00007f78c5a4a824: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [376]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - kotlin.jvm.internal.Intrinsics::sanitizeStackTrace@-1 (line 253) - ; - kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@11 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a4a824: c3b3 06e9 | 99ae ffff - - 0x00007f78c5a4a82c: ; {metadata({method} {0x00007f78900180d8} 'getName' '()Ljava/lang/String;' in 'java/lang/Class')} - 0x00007f78c5a4a82c: 49ba d880 | 0190 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4a840: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a840: ffff ffe8 - - 0x00007f78c5a4a844: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [376]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*synchronization entry - ; - java.lang.Class::getName@-1 - ; - kotlin.jvm.internal.Intrinsics::sanitizeStackTrace@3 (line 253) - ; - kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@11 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a4a844: 18c3 b306 | e92f afff - - 0x00007f78c5a4a84c: ; {metadata({method} {0x00007f7890cec178} 'getCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot$Companion')} - 0x00007f78c5a4a84c: ff49 ba78 | c1ce 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4a864: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a864: e8f7 c2b3 - - 0x00007f78c5a4a868: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::getCurrent@-1 (line 294) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@891 (line 1986) - 0x00007f78c5a4a868: 06e9 4ab0 - - 0x00007f78c5a4a86c: ; {metadata({method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a4a86c: ffff 49ba | 5020 cf90 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4a884: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a884: ffe8 d6c2 - - 0x00007f78c5a4a888: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.SnapshotKt::currentSnapshot@-1 (line 1284) - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::getCurrent@0 (line 294) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@891 (line 1986) - 0x00007f78c5a4a888: b306 e962 | b0ff ff48 - - 0x00007f78c5a4a890: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a890: 8904 24e8 - - 0x00007f78c5a4a894: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.SnapshotKt::currentSnapshot@6 (line 1284) - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::getCurrent@0 (line 294) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@891 (line 1986) - 0x00007f78c5a4a894: 488b b306 - - 0x00007f78c5a4a898: ; {runtime_call C1 Runtime fast_new_instance_blob} - 0x00007f78c5a4a898: 488b d2e8 - - 0x00007f78c5a4a89c: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*new {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@904 (line 1990) - 0x00007f78c5a4a89c: 406d b306 | e911 b3ff | ff48 891c - - 0x00007f78c5a4a8a8: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a8a8: 24e8 328b - - 0x00007f78c5a4a8ac: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@910 (line 1991) - ; {metadata(nullptr)} - 0x00007f78c5a4a8ac: b306 48ba | 0000 0000 | 0000 0000 | b800 0f05 - - 0x00007f78c5a4a8bc: ; {runtime_call C1 Runtime load_klass_patching_blob} - 0x00007f78c5a4a8bc: 0ae8 9ead - - 0x00007f78c5a4a8c0: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*new {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@926 (line 1998) - 0x00007f78c5a4a8c0: b306 e919 - - 0x00007f78c5a4a8c4: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a8c4: b4ff ffe8 - - 0x00007f78c5a4a8c8: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*new {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@926 (line 1998) - 0x00007f78c5a4a8c8: 1463 b306 - - 0x00007f78c5a4a8cc: ; {runtime_call C1 Runtime fast_new_instance_init_check_blob} - 0x00007f78c5a4a8cc: 488b d2e8 - - 0x00007f78c5a4a8d0: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*new {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@926 (line 1998) - 0x00007f78c5a4a8d0: 8c70 b306 | e962 b4ff - - 0x00007f78c5a4a8d8: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a8d8: ffe8 0263 - - 0x00007f78c5a4a8dc: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual makeCurrent {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@953 (line 2007) - ; {metadata({method} {0x00007f7890ce9ae0} 'makeCurrent' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a4a8dc: b306 49ba | e09a ce90 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4a8f4: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a8f4: ffe8 66c2 - - 0x00007f78c5a4a8f8: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.Snapshot::makeCurrent@-1 (line 160) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@953 (line 2007) - 0x00007f78c5a4a8f8: b306 e93d - - 0x00007f78c5a4a8fc: ; {metadata({method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a4a8fc: b5ff ff49 | bab0 70cf | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4a914: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a914: ffff e845 - - 0x00007f78c5a4a918: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.SnapshotKt::access$getThreadSnapshot$p@-1 (line 1) - ; - androidx.compose.runtime.snapshots.Snapshot::makeCurrent@0 (line 160) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@953 (line 2007) - 0x00007f78c5a4a918: c2b3 06e9 | 55b5 ffff | 4889 0424 - - 0x00007f78c5a4a924: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a924: e8b7 8ab3 - - 0x00007f78c5a4a928: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [408]=Oop [416]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot::makeCurrent@6 (line 160) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@953 (line 2007) - ; {metadata({method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a4a928: 0649 bab0 | 70cf 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4a940: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a940: e81b c2b3 - - 0x00007f78c5a4a944: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [408]=Oop [416]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.SnapshotKt::access$getThreadSnapshot$p@-1 (line 1) - ; - androidx.compose.runtime.snapshots.Snapshot::makeCurrent@10 (line 161) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@953 (line 2007) - 0x00007f78c5a4a944: 06e9 ceb6 | ffff 4889 - - 0x00007f78c5a4a94c: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a94c: 3c24 e88d - - 0x00007f78c5a4a950: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@964 (line 720) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a950: 8ab3 06e8 - - 0x00007f78c5a4a954: ; ImmutableOopMap {rdi=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface isEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@967 (line 720) - 0x00007f78c5a4a954: 8862 b306 | 4889 3c24 - - 0x00007f78c5a4a95c: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a95c: e87f 8ab3 - - 0x00007f78c5a4a960: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1010 (line 2010) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a960: 06e8 7a62 - - 0x00007f78c5a4a964: ; ImmutableOopMap {rdi=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface size {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1013 (line 2010) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a964: b306 e875 - - 0x00007f78c5a4a968: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1031 (line 2011) - 0x00007f78c5a4a968: 62b3 0648 - - 0x00007f78c5a4a96c: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a96c: 8904 24e8 - - 0x00007f78c5a4a970: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1040 (line 2012) - 0x00007f78c5a4a970: 6c8a b306 - - 0x00007f78c5a4a974: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a974: e867 62b3 - - 0x00007f78c5a4a978: ; ImmutableOopMap {rdx=Oop rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual add {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1052 (line 730) - ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a978: 0649 ba48 | 68f7 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | 2504 0000 - - 0x00007f78c5a4a990: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a990: e8cb c1b3 - - 0x00007f78c5a4a994: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1061 (line 2010) - 0x00007f78c5a4a994: 06e9 b2bd | ffff 4889 - - 0x00007f78c5a4a99c: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a99c: 3c24 e83d - - 0x00007f78c5a4a9a0: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1077 (line 2015) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a9a0: 8ab3 06e8 - - 0x00007f78c5a4a9a4: ; ImmutableOopMap {rdi=Oop rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface size {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1080 (line 2015) - 0x00007f78c5a4a9a4: 3862 b306 - - 0x00007f78c5a4a9a8: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a9a8: e833 62b3 - - 0x00007f78c5a4a9ac: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface get {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1098 (line 2016) - 0x00007f78c5a4a9ac: 0648 8904 - - 0x00007f78c5a4a9b0: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4a9b0: 24e8 2a8a - - 0x00007f78c5a4a9b4: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1107 (line 2017) - ; {runtime_call C1 Runtime throw_incompatible_class_change_error_blob} - 0x00007f78c5a4a9b4: b306 e825 - - 0x00007f78c5a4a9b8: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface applyChanges {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1117 (line 733) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a9b8: 8db3 06e8 - - 0x00007f78c5a4a9bc: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface applyChanges {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1117 (line 733) - 0x00007f78c5a4a9bc: 2062 b306 - - 0x00007f78c5a4a9c0: ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4a9c0: 49ba 4868 | f790 787f | 0000 4c89 | 5424 0848 | c704 2467 - - 0x00007f78c5a4a9d4: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4a9d4: 0400 00e8 - - 0x00007f78c5a4a9d8: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1127 (line 2015) - 0x00007f78c5a4a9d8: 84c1 b306 | e953 c1ff - - 0x00007f78c5a4a9e0: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a9e0: ffe8 fa61 - - 0x00007f78c5a4a9e4: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1133 (line 740) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4a9e4: b306 e8f5 - - 0x00007f78c5a4a9e8: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual isNotEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1230 (line 744) - ; {metadata({method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a4a9e8: 61b3 0649 | bae8 81e7 | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4aa00: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4aa00: ffff e859 - - 0x00007f78c5a4aa04: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.collection.ScatterSet::isNotEmpty@-1 (line 155) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1230 (line 744) - 0x00007f78c5a4aa04: c1b3 06e9 | e8c2 ffff - - 0x00007f78c5a4aa0c: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aa0c: e8cf 61b3 - - 0x00007f78c5a4aa10: ; ImmutableOopMap {rdi=Oop rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual plusAssign {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1244 (line 746) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aa10: 06e8 ca61 - - 0x00007f78c5a4aa14: ; ImmutableOopMap {rdi=Oop rbx=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*arraylength {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1282 (line 2027) - 0x00007f78c5a4aa14: b306 4889 | 1424 4889 - - 0x00007f78c5a4aa1c: ; {runtime_call C1 Runtime throw_range_check_failed_blob} - 0x00007f78c5a4aa1c: 4424 08e8 - - 0x00007f78c5a4aa20: ; ImmutableOopMap {rbx=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*laload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1301 (line 2030) - 0x00007f78c5a4aa20: bc58 b306 - - 0x00007f78c5a4aa24: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aa24: e8b7 61b3 - - 0x00007f78c5a4aa28: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1407 (line 2024) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aa28: 06e8 b261 - - 0x00007f78c5a4aa2c: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1407 (line 2024) - 0x00007f78c5a4aa2c: b306 4889 | 3424 4889 - - 0x00007f78c5a4aa34: ; {runtime_call C1 Runtime throw_range_check_failed_blob} - 0x00007f78c5a4aa34: 5c24 08e8 - - 0x00007f78c5a4aa38: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1407 (line 2024) - 0x00007f78c5a4aa38: a458 b306 | 4889 3424 - - 0x00007f78c5a4aa40: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4aa40: e89b 89b3 - - 0x00007f78c5a4aa44: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1408 (line 2024) - ; {runtime_call C1 Runtime throw_incompatible_class_change_error_blob} - 0x00007f78c5a4aa44: 06e8 968c - - 0x00007f78c5a4aa48: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface applyLateChanges {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1418 (line 748) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aa48: b306 e891 - - 0x00007f78c5a4aa4c: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface applyLateChanges {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1418 (line 748) - ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4aa4c: 61b3 0649 | ba48 68f7 | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 9c05 - - 0x00007f78c5a4aa64: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4aa64: 0000 e8f5 - - 0x00007f78c5a4aa68: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1436 (line 2034) - 0x00007f78c5a4aa68: c0b3 06e9 | 0ec7 ffff - - 0x00007f78c5a4aa70: ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4aa70: 49ba 4868 | f790 787f | 0000 4c89 | 5424 0848 | c704 24b0 - - 0x00007f78c5a4aa84: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4aa84: 0500 00e8 - - 0x00007f78c5a4aa88: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [456]=Oop [480]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1456 (line 2029) - 0x00007f78c5a4aa88: d4c0 b306 | e9b0 c7ff - - 0x00007f78c5a4aa90: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aa90: ffe8 4a61 - - 0x00007f78c5a4aa94: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*invokevirtual isNotEmpty {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1554 (line 759) - ; {metadata({method} {0x00007f7890e781e8} 'isNotEmpty' '()Z' in 'androidx/collection/ScatterSet')} - 0x00007f78c5a4aa94: b306 49ba | e881 e790 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4aaac: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4aaac: ffe8 aec0 - - 0x00007f78c5a4aab0: ; ImmutableOopMap {rdi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.collection.ScatterSet::isNotEmpty@-1 (line 155) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1554 (line 759) - 0x00007f78c5a4aab0: b306 e925 - - 0x00007f78c5a4aab4: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aab4: c9ff ffe8 - - 0x00007f78c5a4aab8: ; ImmutableOopMap {rdi=Oop rbx=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*arraylength {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1596 (line 2052) - 0x00007f78c5a4aab8: 2461 b306 | 4889 1424 | 4889 4424 - - 0x00007f78c5a4aac4: ; {runtime_call C1 Runtime throw_range_check_failed_blob} - 0x00007f78c5a4aac4: 08e8 1658 - - 0x00007f78c5a4aac8: ; ImmutableOopMap {rbx=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*laload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1615 (line 2055) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aac8: b306 e811 - - 0x00007f78c5a4aacc: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1721 (line 2049) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aacc: 61b3 06e8 - - 0x00007f78c5a4aad0: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1721 (line 2049) - 0x00007f78c5a4aad0: 0c61 b306 | 4889 3424 | 4889 5c24 - - 0x00007f78c5a4aadc: ; {runtime_call C1 Runtime throw_range_check_failed_blob} - 0x00007f78c5a4aadc: 08e8 fe57 - - 0x00007f78c5a4aae0: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*aaload {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1721 (line 2049) - 0x00007f78c5a4aae0: b306 4889 - - 0x00007f78c5a4aae4: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4aae4: 3424 e8f5 - - 0x00007f78c5a4aae8: ; ImmutableOopMap {rbx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1722 (line 2049) - ; {runtime_call C1 Runtime throw_incompatible_class_change_error_blob} - 0x00007f78c5a4aae8: 88b3 06e8 - - 0x00007f78c5a4aaec: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface changesApplied {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1732 (line 762) - 0x00007f78c5a4aaec: f08b b306 - - 0x00007f78c5a4aaf0: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4aaf0: e8eb 60b3 - - 0x00007f78c5a4aaf4: ; ImmutableOopMap {rsi=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*invokeinterface changesApplied {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1732 (line 762) - ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4aaf4: 0649 ba48 | 68f7 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | d606 0000 - - 0x00007f78c5a4ab0c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ab0c: e84f c0b3 - - 0x00007f78c5a4ab10: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1750 (line 2059) - 0x00007f78c5a4ab10: 06e9 a0cc - - 0x00007f78c5a4ab14: ; {metadata({method} {0x00007f7890f76848} 'invokeSuspend$lambda$2' '(Landroidx/compose/runtime/Recomposer;Landroidx/collection/MutableScatterSet;Landroidx/collection/MutableScatterSet;Ljava/util/List;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/List;Landroidx/collection/MutableScatterSet;Ljava/util/Set;J)Lkotlin/Unit;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2')} - 0x00007f78c5a4ab14: ffff 49ba | 4868 f790 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ea 0600 - - 0x00007f78c5a4ab2c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ab2c: 00e8 2ec0 - - 0x00007f78c5a4ab30: ; ImmutableOopMap {[216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [496]=Oop [520]=Oop [640]=Oop [648]=Oop } - ;*goto {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1770 (line 2054) - 0x00007f78c5a4ab30: b306 e942 - - 0x00007f78c5a4ab34: ; {metadata({method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a4ab34: cdff ff49 | bab0 9bce | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4ab4c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ab4c: ffff e80d - - 0x00007f78c5a4ab50: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [416]=Oop [424]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@-1 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1876 (line 2020) - 0x00007f78c5a4ab50: c0b3 06e9 | b6ce ffff - - 0x00007f78c5a4ab58: ; {metadata({method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a4ab58: 49ba b070 | cf90 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4ab6c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ab6c: ffff ffe8 - - 0x00007f78c5a4ab70: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [416]=Oop [424]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.SnapshotKt::access$getThreadSnapshot$p@-1 (line 1) - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@0 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1876 (line 2020) - 0x00007f78c5a4ab70: ecbf b306 | e9ce ceff - - 0x00007f78c5a4ab78: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4ab78: ffe8 6260 - - 0x00007f78c5a4ab7c: ; ImmutableOopMap {rsi=Oop rdi=Oop [216]=Oop [232]=Oop [240]=Oop [528]=Oop } - ;*monitorenter {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1931 (line 2074) - 0x00007f78c5a4ab7c: b306 4889 | 7c24 0848 - - 0x00007f78c5a4ab84: ; {runtime_call C1 Runtime monitorenter_nofpu_blob} - 0x00007f78c5a4ab84: 891c 24e8 - - 0x00007f78c5a4ab88: ; ImmutableOopMap {rsi=Oop rdi=Oop [216]=Oop [232]=Oop [240]=Oop [528]=Oop [600]=Oop } - ;*monitorenter {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1931 (line 2074) - 0x00007f78c5a4ab88: d497 b306 | e9b4 d0ff - - 0x00007f78c5a4ab90: ; {metadata({method} {0x00007f7890e28330} 'access$deriveStateLocked' '(Landroidx/compose/runtime/Recomposer;)Lkotlinx/coroutines/CancellableContinuation;' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4ab90: ff49 ba30 | 83e2 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4aba8: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4aba8: e8b3 bfb3 - - 0x00007f78c5a4abac: ; ImmutableOopMap {rsi=Oop [216]=Oop [232]=Oop [240]=Oop [528]=Oop [600]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::access$deriveStateLocked@-1 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1937 (line 775) - 0x00007f78c5a4abac: 06e9 cbd0 | ffff 488d | 8424 5002 | 0000 4889 - - 0x00007f78c5a4abbc: ; {runtime_call C1 Runtime monitorexit_nofpu_blob} - 0x00007f78c5a4abbc: 0424 e81d | 9eb3 06e9 | b3d1 ffff - - 0x00007f78c5a4abc8: ; {metadata({method} {0x00007f7890cee578} 'notifyObjectsInitialized' '()V' in 'androidx/compose/runtime/snapshots/Snapshot$Companion')} - 0x00007f78c5a4abc8: 49ba 78e5 | ce90 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4abdc: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4abdc: ffff ffe8 - - 0x00007f78c5a4abe0: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::notifyObjectsInitialized@-1 (line 676) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1995 (line 785) - 0x00007f78c5a4abe0: 7cbf b306 | e956 d2ff - - 0x00007f78c5a4abe8: ; {metadata({method} {0x00007f7890cf2050} 'currentSnapshot' '()Landroidx/compose/runtime/snapshots/Snapshot;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a4abe8: ff49 ba50 | 20cf 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4ac00: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ac00: e85b bfb3 - - 0x00007f78c5a4ac04: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.SnapshotKt::currentSnapshot@-1 (line 1284) - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::notifyObjectsInitialized@0 (line 676) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1995 (line 785) - 0x00007f78c5a4ac04: 06e9 6ed2 | ffff 4889 - - 0x00007f78c5a4ac0c: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4ac0c: 0424 e8cd - - 0x00007f78c5a4ac10: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.SnapshotKt::currentSnapshot@6 (line 1284) - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::notifyObjectsInitialized@0 (line 676) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1995 (line 785) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4ac10: 87b3 06e8 - - 0x00007f78c5a4ac14: ; ImmutableOopMap {rsi=Oop [216]=Oop [232]=Oop [240]=Oop } - ;*invokevirtual notifyObjectsInitialized$runtime {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.snapshots.Snapshot$Companion::notifyObjectsInitialized@3 (line 676) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1995 (line 785) - 0x00007f78c5a4ac14: c85f b306 | 8b46 5c48 | c1e0 0348 | 83f8 000f | 84e2 d5ff | ff48 8904 - - 0x00007f78c5a4ac2c: ; {runtime_call g1_pre_barrier_slow} - 0x00007f78c5a4ac2c: 24e8 2ec6 | b306 e9d4 - - 0x00007f78c5a4ac34: ; {metadata({method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4ac34: d5ff ff49 | bab0 e9e1 | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4ac4c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ac4c: ffff e80d - - 0x00007f78c5a4ac50: ; ImmutableOopMap {rsi=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@-1 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1793 (line 765) - 0x00007f78c5a4ac50: bfb3 06e9 | 1bd6 ffff - - 0x00007f78c5a4ac58: ; {metadata({method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a4ac58: 49ba b09b | ce90 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4ac6c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ac6c: ffff ffe8 - - 0x00007f78c5a4ac70: ; ImmutableOopMap {[416]=Oop [424]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@-1 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1829 (line 2020) - 0x00007f78c5a4ac70: ecbe b306 | e91d d8ff - - 0x00007f78c5a4ac78: ; {metadata({method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a4ac78: ff49 bab0 | 70cf 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4ac90: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ac90: e8cb beb3 - - 0x00007f78c5a4ac94: ; ImmutableOopMap {[416]=Oop [424]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.SnapshotKt::access$getThreadSnapshot$p@-1 (line 1) - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@0 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1829 (line 2020) - 0x00007f78c5a4ac94: 06e9 35d8 - - 0x00007f78c5a4ac98: ; {metadata({method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4ac98: ffff 49ba | b0e9 e190 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4acb0: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4acb0: ffe8 aabe - - 0x00007f78c5a4acb4: ; ImmutableOopMap {r11=Oop rbx=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@-1 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1479 (line 751) - 0x00007f78c5a4acb4: b306 e922 - - 0x00007f78c5a4acb8: ; {metadata({method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a4acb8: d9ff ff49 | bab0 9bce | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4acd0: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4acd0: ffff e889 - - 0x00007f78c5a4acd4: ; ImmutableOopMap {[416]=Oop [424]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@-1 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1515 (line 2020) - 0x00007f78c5a4acd4: beb3 06e9 | 2adb ffff - - 0x00007f78c5a4acdc: ; {metadata({method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a4acdc: 49ba b070 | cf90 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4acf0: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4acf0: ffff ffe8 - - 0x00007f78c5a4acf4: ; ImmutableOopMap {[416]=Oop [424]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.SnapshotKt::access$getThreadSnapshot$p@-1 (line 1) - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@0 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1515 (line 2020) - 0x00007f78c5a4acf4: 68be b306 | e942 dbff - - 0x00007f78c5a4acfc: ; {metadata({method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4acfc: ff49 bab0 | e9e1 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4ad14: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ad14: e847 beb3 - - 0x00007f78c5a4ad18: ; ImmutableOopMap {r11=Oop rbx=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [416]=Oop [424]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@-1 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1151 (line 736) - 0x00007f78c5a4ad18: 06e9 2fdc - - 0x00007f78c5a4ad1c: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4ad1c: ffff e8bd - - 0x00007f78c5a4ad20: ; ImmutableOopMap {rdi=Oop [416]=Oop [424]=Oop [640]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1176 (line 740) - ; {metadata({method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a4ad20: 5eb3 0649 | bab0 9bce | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4ad38: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ad38: ffff e821 - - 0x00007f78c5a4ad3c: ; ImmutableOopMap {[416]=Oop [424]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@-1 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1189 (line 2020) - 0x00007f78c5a4ad3c: beb3 06e9 | 42de ffff - - 0x00007f78c5a4ad44: ; {metadata({method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a4ad44: 49ba b070 | cf90 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4ad58: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ad58: ffff ffe8 - - 0x00007f78c5a4ad5c: ; ImmutableOopMap {[416]=Oop [424]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.SnapshotKt::access$getThreadSnapshot$p@-1 (line 1) - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@0 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1189 (line 2020) - 0x00007f78c5a4ad5c: 00be b306 | e95a deff - - 0x00007f78c5a4ad64: ; {metadata({method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4ad64: ff49 bab0 | e9e1 9078 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4ad7c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ad7c: e8df bdb3 - - 0x00007f78c5a4ad80: ; ImmutableOopMap {rdi=Oop r11=Oop rbx=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@-1 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@848 (line 708) - 0x00007f78c5a4ad80: 06e9 4fdf - - 0x00007f78c5a4ad84: ; {metadata({method} {0x00007f7890e1e9b0} 'processCompositionError$default' '(Landroidx/compose/runtime/Recomposer;Ljava/lang/Throwable;Landroidx/compose/runtime/ControlledComposition;ZILjava/lang/Object;)V' in 'androidx/compose/runtime/Recomposer')} - 0x00007f78c5a4ad84: ffff 49ba | b0e9 e190 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4ad9c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ad9c: ffe8 bebd - - 0x00007f78c5a4ada0: ; ImmutableOopMap {rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [640]=Oop [648]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer::processCompositionError$default@-1 (line 797) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@401 (line 661) - 0x00007f78c5a4ada0: b306 e906 - - 0x00007f78c5a4ada4: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4ada4: e0ff ffe8 - - 0x00007f78c5a4ada8: ; ImmutableOopMap {r8=Oop [224]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@425 (line 665) - 0x00007f78c5a4ada8: 345e b306 - - 0x00007f78c5a4adac: ; {metadata({method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4adac: 49ba e023 | 0391 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4adc0: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4adc0: ffff ffe8 - - 0x00007f78c5a4adc4: ; ImmutableOopMap {} - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::endSection@-1 (line 27) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@439 (line 1963) - 0x00007f78c5a4adc4: 98bd b306 | e91b e2ff - - 0x00007f78c5a4adcc: ; {internal_word} - 0x00007f78c5a4adcc: ff49 bafa | 8fa4 c578 | 7f00 004d | 8997 3805 - - 0x00007f78c5a4addc: ; {runtime_call SafepointBlob} - 0x00007f78c5a4addc: 0000 e97d - - 0x00007f78c5a4ade0: ; {metadata({method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4ade0: 5da6 0649 | bae0 2303 | 9178 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4adf8: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4adf8: ffff e861 - - 0x00007f78c5a4adfc: ; ImmutableOopMap {} - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::endSection@-1 (line 27) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@876 (line 1963) - 0x00007f78c5a4adfc: bdb3 06e9 | 0ce3 ffff - - 0x00007f78c5a4ae04: ; {internal_word} - 0x00007f78c5a4ae04: 49ba 2291 | a4c5 787f | 0000 4d89 | 9738 0500 - - 0x00007f78c5a4ae14: ; {runtime_call SafepointBlob} - 0x00007f78c5a4ae14: 00e9 465d - - 0x00007f78c5a4ae18: ; {metadata({method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4ae18: a606 49ba | e023 0391 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4ae30: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ae30: ffe8 2abd - - 0x00007f78c5a4ae34: ; ImmutableOopMap {} - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::endSection@-1 (line 27) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1210 (line 1963) - 0x00007f78c5a4ae34: b306 e955 - - 0x00007f78c5a4ae38: ; {internal_word} - 0x00007f78c5a4ae38: e4ff ff49 | baa2 92a4 | c578 7f00 | 004d 8997 | 3805 0000 - - 0x00007f78c5a4ae4c: ; {runtime_call SafepointBlob} - 0x00007f78c5a4ae4c: e90f 5da6 - - 0x00007f78c5a4ae50: ; {metadata({method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4ae50: 0649 bae0 | 2303 9178 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4ae68: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ae68: e8f3 bcb3 - - 0x00007f78c5a4ae6c: ; ImmutableOopMap {} - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::endSection@-1 (line 27) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1536 (line 1963) - 0x00007f78c5a4ae6c: 06e9 9ee5 - - 0x00007f78c5a4ae70: ; {internal_word} - 0x00007f78c5a4ae70: ffff 49ba | 2294 a4c5 | 787f 0000 | 4d89 9738 - - 0x00007f78c5a4ae80: ; {runtime_call SafepointBlob} - 0x00007f78c5a4ae80: 0500 00e9 | d85c a606 - - 0x00007f78c5a4ae88: ; {metadata({method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4ae88: 49ba e023 | 0391 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4ae9c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4ae9c: ffff ffe8 - - 0x00007f78c5a4aea0: ; ImmutableOopMap {} - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::endSection@-1 (line 27) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1850 (line 1963) - 0x00007f78c5a4aea0: bcbc b306 | e9e7 e6ff - - 0x00007f78c5a4aea8: ; {internal_word} - 0x00007f78c5a4aea8: ff49 baa2 | 95a4 c578 | 7f00 004d | 8997 3805 - - 0x00007f78c5a4aeb8: ; {runtime_call SafepointBlob} - 0x00007f78c5a4aeb8: 0000 e9a1 - - 0x00007f78c5a4aebc: ; {metadata({method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4aebc: 5ca6 0649 | bae0 2303 | 9178 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4aed4: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4aed4: ffff e885 - - 0x00007f78c5a4aed8: ; ImmutableOopMap {} - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::endSection@-1 (line 27) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@2022 (line 1963) - 0x00007f78c5a4aed8: bcb3 06e9 | 81e7 ffff - - 0x00007f78c5a4aee0: ; {internal_word} - 0x00007f78c5a4aee0: 49ba 8396 | a4c5 787f | 0000 4d89 | 9738 0500 - - 0x00007f78c5a4aef0: ; {runtime_call SafepointBlob} - 0x00007f78c5a4aef0: 00e9 6a5c - - 0x00007f78c5a4aef4: ; {metadata({method} {0x00007f7890faf9e0} 'composeImmediateRuntimeError' '(Ljava/lang/String;)V' in 'androidx/compose/runtime/ComposerKt')} - 0x00007f78c5a4aef4: a606 49ba | e0f9 fa90 | 787f 0000 | 4c89 5424 | 0848 c704 | 24ff ffff - - 0x00007f78c5a4af0c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4af0c: ffe8 4ebc - - 0x00007f78c5a4af10: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [528]=Oop [600]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.ComposerKt::composeImmediateRuntimeError@-1 (line 1429) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1964 (line 2076) - 0x00007f78c5a4af10: b306 e9af - - 0x00007f78c5a4af14: ; {metadata(nullptr)} - 0x00007f78c5a4af14: e7ff ff48 | ba00 0000 | 0000 0000 | 00b8 000f - - 0x00007f78c5a4af24: ; {runtime_call C1 Runtime load_klass_patching_blob} - 0x00007f78c5a4af24: 050a e835 - - 0x00007f78c5a4af28: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [528]=Oop [600]=Oop } - ;*new {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.ComposerKt::composeImmediateRuntimeError@0 (line 1429) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1964 (line 2076) - 0x00007f78c5a4af28: a7b3 06e9 | 98e7 ffff - - 0x00007f78c5a4af30: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4af30: e8ab 5cb3 - - 0x00007f78c5a4af34: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [528]=Oop [600]=Oop } - ;*new {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.ComposerKt::composeImmediateRuntimeError@0 (line 1429) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1964 (line 2076) - 0x00007f78c5a4af34: 0648 8bd2 - - 0x00007f78c5a4af38: ; {runtime_call C1 Runtime fast_new_instance_init_check_blob} - 0x00007f78c5a4af38: e823 6ab3 - - 0x00007f78c5a4af3c: ; ImmutableOopMap {[216]=Oop [232]=Oop [240]=Oop [528]=Oop [600]=Oop } - ;*new {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.ComposerKt::composeImmediateRuntimeError@0 (line 1429) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1964 (line 2076) - 0x00007f78c5a4af3c: 06e9 dee7 | ffff 48ba | 0000 0000 | 0000 0000 | b800 0f05 - - 0x00007f78c5a4af50: ; {runtime_call C1 Runtime load_appendix_patching_blob} - 0x00007f78c5a4af50: 0ae8 0ab3 - - 0x00007f78c5a4af54: ; ImmutableOopMap {rax=Oop [216]=Oop [232]=Oop [240]=Oop [528]=Oop [600]=Oop } - ;*invokedynamic {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) androidx.compose.runtime.ComposerKt::composeImmediateRuntimeError@5 (line 1431) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1964 (line 2076) - 0x00007f78c5a4af54: b306 e9c5 | e7ff ff48 - - 0x00007f78c5a4af5c: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a4af5c: 8904 24e8 - - 0x00007f78c5a4af60: ; ImmutableOopMap {rcx=Oop r8=Oop r11=Oop rdx=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@14 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a4af60: 7c84 b306 - - 0x00007f78c5a4af64: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4af64: e877 5cb3 - - 0x00007f78c5a4af68: ; ImmutableOopMap {rcx=Oop r8=Oop r11=Oop rdx=Oop rax=Oop [216]=Oop [224]=Oop [232]=Oop [240]=Oop [248]=Oop [256]=Oop [312]=Oop [360]=Oop [384]=Oop [600]=Oop [640]=Oop [648]=Oop [656]=Oop } - ;*athrow {reexecute=1 rethrow=0 return_oop=0} - ; - (reexecute) kotlin.jvm.internal.Intrinsics::throwParameterIsNullNPE@17 (line 140) - ; - kotlin.jvm.internal.Intrinsics::checkNotNullParameter@5 (line 131) - ; - kotlin.collections.ArraysKt___ArraysJvmKt::fill@3 - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@740 (line 1983) - 0x00007f78c5a4af68: 0648 8d84 | 2450 0200 | 0048 8904 - - 0x00007f78c5a4af74: ; {runtime_call C1 Runtime monitorexit_nofpu_blob} - 0x00007f78c5a4af74: 24e8 669a | b306 e9dc | e9ff ff48 | 8d84 2450 | 0200 0048 - - 0x00007f78c5a4af88: ; {runtime_call C1 Runtime monitorexit_nofpu_blob} - 0x00007f78c5a4af88: 8904 24e8 | 509a b306 | e952 eaff - - 0x00007f78c5a4af94: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4af94: ffe8 465c - - 0x00007f78c5a4af98: ; ImmutableOopMap {r8=Oop [224]=Oop [544]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@448 (line 665) - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4af98: b306 e841 - - 0x00007f78c5a4af9c: ; ImmutableOopMap {rdi=Oop [416]=Oop [424]=Oop [552]=Oop [640]=Oop } - ;*invokeinterface clear {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1220 (line 740) - ; {metadata({method} {0x00007f7890ce9bb0} 'restoreCurrent' '(Landroidx/compose/runtime/snapshots/Snapshot;)V' in 'androidx/compose/runtime/snapshots/Snapshot')} - 0x00007f78c5a4af9c: 5cb3 0649 | bab0 9bce | 9078 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a4afb4: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4afb4: ffff e8a5 - - 0x00007f78c5a4afb8: ; ImmutableOopMap {rdi=Oop [416]=Oop [576]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@-1 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1890 (line 2020) - 0x00007f78c5a4afb8: bbb3 06e9 | a9ee ffff - - 0x00007f78c5a4afc0: ; {metadata({method} {0x00007f7890cf70b0} 'access$getThreadSnapshot$p' '()Landroidx/compose/runtime/internal/SnapshotThreadLocal;' in 'androidx/compose/runtime/snapshots/SnapshotKt')} - 0x00007f78c5a4afc0: 49ba b070 | cf90 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4afd4: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4afd4: ffff ffe8 - - 0x00007f78c5a4afd8: ; ImmutableOopMap {rdi=Oop [416]=Oop [576]=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.snapshots.SnapshotKt::access$getThreadSnapshot$p@-1 (line 1) - ; - androidx.compose.runtime.snapshots.Snapshot::restoreCurrent@0 (line 167) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1890 (line 2020) - 0x00007f78c5a4afd8: 84bb b306 | e9c1 eeff - - 0x00007f78c5a4afe0: ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a4afe0: ffe8 fa5b - - 0x00007f78c5a4afe4: ; ImmutableOopMap {rax=Oop [584]=Oop } - ;*invokevirtual dispose {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@1909 (line 2021) - 0x00007f78c5a4afe4: b306 488d | 8424 5002 | 0000 4889 - - 0x00007f78c5a4aff0: ; {runtime_call C1 Runtime monitorexit_nofpu_blob} - 0x00007f78c5a4aff0: 0424 e8e9 | 99b3 06e9 | c1f0 ffff - - 0x00007f78c5a4affc: ; {metadata({method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4affc: 49ba e023 | 0391 787f | 0000 4c89 | 5424 0848 | c704 24ff - - 0x00007f78c5a4b010: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4b010: ffff ffe8 - - 0x00007f78c5a4b014: ; ImmutableOopMap {rax=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::endSection@-1 (line 27) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@2037 (line 1963) - 0x00007f78c5a4b014: 48bb b306 | e982 f1ff - - 0x00007f78c5a4b01c: ; {metadata({method} {0x00007f78910323e0} 'endSection' '(Ljava/lang/Object;)V' in 'androidx/compose/runtime/internal/Trace')} - 0x00007f78c5a4b01c: ff49 bae0 | 2303 9178 | 7f00 004c | 8954 2408 | 48c7 0424 | ffff ffff - - 0x00007f78c5a4b034: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a4b034: e827 bbb3 - - 0x00007f78c5a4b038: ; ImmutableOopMap {rax=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.internal.Trace::endSection@-1 (line 27) - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2::invokeSuspend$lambda$2@70 (line 1945) - 0x00007f78c5a4b038: 06e9 35f2 | ffff 4889 | 8424 a001 | 0000 e911 | efff ff49 | 8b87 e005 | 0000 49c7 | 87e0 0500 - 0x00007f78c5a4b058: 0000 0000 | 0049 c787 | e805 0000 | 0000 0000 | 4881 c470 | 0200 005d - - 0x00007f78c5a4b070: ; {runtime_call C1 Runtime unwind_exception_blob} - 0x00007f78c5a4b070: e96b 4cb3 | 06f4 f4f4 -[Stub Code] - 0x00007f78c5a4b078: ; {other} - 0x00007f78c5a4b078: 0f1f 4400 - - 0x00007f78c5a4b07c: ; {static_stub} - 0x00007f78c5a4b07c: 0048 bb00 | 0000 0000 - - 0x00007f78c5a4b084: ; {runtime_call nmethod} - 0x00007f78c5a4b084: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b08c: ; {static_stub} - 0x00007f78c5a4b08c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b094: ; {runtime_call nmethod} - 0x00007f78c5a4b094: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b09c: ; {static_stub} - 0x00007f78c5a4b09c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b0a4: ; {runtime_call nmethod} - 0x00007f78c5a4b0a4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b0ac: ; {static_stub} - 0x00007f78c5a4b0ac: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b0b4: ; {runtime_call nmethod} - 0x00007f78c5a4b0b4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b0bc: ; {static_stub} - 0x00007f78c5a4b0bc: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b0c4: ; {runtime_call nmethod} - 0x00007f78c5a4b0c4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b0cc: ; {static_stub} - 0x00007f78c5a4b0cc: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b0d4: ; {runtime_call nmethod} - 0x00007f78c5a4b0d4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b0dc: ; {static_stub} - 0x00007f78c5a4b0dc: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b0e4: ; {runtime_call nmethod} - 0x00007f78c5a4b0e4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b0ec: ; {static_stub} - 0x00007f78c5a4b0ec: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b0f4: ; {runtime_call nmethod} - 0x00007f78c5a4b0f4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b0fc: ; {static_stub} - 0x00007f78c5a4b0fc: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b104: ; {runtime_call nmethod} - 0x00007f78c5a4b104: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b10c: ; {static_stub} - 0x00007f78c5a4b10c: 9048 bb70 | 05f9 9078 - - 0x00007f78c5a4b114: ; {runtime_call I2C/C2I adapters} - 0x00007f78c5a4b114: 7f00 00e9 | 9ab4 a606 - - 0x00007f78c5a4b11c: ; {static_stub} - 0x00007f78c5a4b11c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b124: ; {runtime_call nmethod} - 0x00007f78c5a4b124: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b12c: ; {static_stub} - 0x00007f78c5a4b12c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b134: ; {runtime_call nmethod} - 0x00007f78c5a4b134: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b13c: ; {static_stub} - 0x00007f78c5a4b13c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b144: ; {runtime_call nmethod} - 0x00007f78c5a4b144: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b14c: ; {static_stub} - 0x00007f78c5a4b14c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b154: ; {runtime_call nmethod} - 0x00007f78c5a4b154: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b15c: ; {static_stub} - 0x00007f78c5a4b15c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b164: ; {runtime_call nmethod} - 0x00007f78c5a4b164: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b16c: ; {static_stub} - 0x00007f78c5a4b16c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b174: ; {runtime_call nmethod} - 0x00007f78c5a4b174: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b17c: ; {static_stub} - 0x00007f78c5a4b17c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b184: ; {runtime_call nmethod} - 0x00007f78c5a4b184: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b18c: ; {static_stub} - 0x00007f78c5a4b18c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b194: ; {runtime_call nmethod} - 0x00007f78c5a4b194: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b19c: ; {static_stub} - 0x00007f78c5a4b19c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b1a4: ; {runtime_call nmethod} - 0x00007f78c5a4b1a4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b1ac: ; {static_stub} - 0x00007f78c5a4b1ac: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b1b4: ; {runtime_call nmethod} - 0x00007f78c5a4b1b4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b1bc: ; {static_stub} - 0x00007f78c5a4b1bc: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b1c4: ; {runtime_call nmethod} - 0x00007f78c5a4b1c4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b1cc: ; {static_stub} - 0x00007f78c5a4b1cc: 9048 bb18 | 6ef9 9078 - - 0x00007f78c5a4b1d4: ; {runtime_call I2C/C2I adapters} - 0x00007f78c5a4b1d4: 7f00 00e9 | d663 a606 - - 0x00007f78c5a4b1dc: ; {static_stub} - 0x00007f78c5a4b1dc: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b1e4: ; {runtime_call nmethod} - 0x00007f78c5a4b1e4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b1ec: ; {static_stub} - 0x00007f78c5a4b1ec: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b1f4: ; {runtime_call nmethod} - 0x00007f78c5a4b1f4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b1fc: ; {static_stub} - 0x00007f78c5a4b1fc: 9048 bb30 | 77f9 9078 - - 0x00007f78c5a4b204: ; {runtime_call I2C/C2I adapters} - 0x00007f78c5a4b204: 7f00 00e9 | a663 a606 - - 0x00007f78c5a4b20c: ; {static_stub} - 0x00007f78c5a4b20c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b214: ; {runtime_call nmethod} - 0x00007f78c5a4b214: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b21c: ; {static_stub} - 0x00007f78c5a4b21c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b224: ; {runtime_call nmethod} - 0x00007f78c5a4b224: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b22c: ; {static_stub} - 0x00007f78c5a4b22c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b234: ; {runtime_call nmethod} - 0x00007f78c5a4b234: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b23c: ; {static_stub} - 0x00007f78c5a4b23c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b244: ; {runtime_call nmethod} - 0x00007f78c5a4b244: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b24c: ; {static_stub} - 0x00007f78c5a4b24c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b254: ; {runtime_call nmethod} - 0x00007f78c5a4b254: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b25c: ; {static_stub} - 0x00007f78c5a4b25c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b264: ; {runtime_call nmethod} - 0x00007f78c5a4b264: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b26c: ; {static_stub} - 0x00007f78c5a4b26c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b274: ; {runtime_call nmethod} - 0x00007f78c5a4b274: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b27c: ; {static_stub} - 0x00007f78c5a4b27c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b284: ; {runtime_call nmethod} - 0x00007f78c5a4b284: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b28c: ; {static_stub} - 0x00007f78c5a4b28c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b294: ; {runtime_call nmethod} - 0x00007f78c5a4b294: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b29c: ; {static_stub} - 0x00007f78c5a4b29c: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b2a4: ; {runtime_call nmethod} - 0x00007f78c5a4b2a4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b2ac: ; {static_stub} - 0x00007f78c5a4b2ac: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b2b4: ; {runtime_call nmethod} - 0x00007f78c5a4b2b4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b2bc: ; {static_stub} - 0x00007f78c5a4b2bc: 9048 bb00 | 0000 0000 - - 0x00007f78c5a4b2c4: ; {runtime_call nmethod} - 0x00007f78c5a4b2c4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b2cc: ; {static_stub} - 0x00007f78c5a4b2cc: 48bb 0000 | 0000 0000 - - 0x00007f78c5a4b2d4: ; {runtime_call nmethod} - 0x00007f78c5a4b2d4: 0000 e9fb - - 0x00007f78c5a4b2d8: ; {static_stub} - 0x00007f78c5a4b2d8: ffff ff48 | bb00 0000 | 0000 0000 - - 0x00007f78c5a4b2e4: ; {runtime_call nmethod} - 0x00007f78c5a4b2e4: 00e9 fbff - - 0x00007f78c5a4b2e8: ; {static_stub} - 0x00007f78c5a4b2e8: ffff 48bb | 0000 0000 | 0000 0000 - - 0x00007f78c5a4b2f4: ; {runtime_call nmethod} - 0x00007f78c5a4b2f4: e9fb ffff - - 0x00007f78c5a4b2f8: ; {static_stub} - 0x00007f78c5a4b2f8: ff48 bb00 | 0000 0000 - - 0x00007f78c5a4b300: ; {runtime_call nmethod} - 0x00007f78c5a4b300: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b308: ; {static_stub} - 0x00007f78c5a4b308: 48bb 0000 | 0000 0000 - - 0x00007f78c5a4b310: ; {runtime_call nmethod} - 0x00007f78c5a4b310: 0000 e9fb - - 0x00007f78c5a4b314: ; {static_stub} - 0x00007f78c5a4b314: ffff ff48 | bb00 0000 | 0000 0000 - - 0x00007f78c5a4b320: ; {runtime_call nmethod} - 0x00007f78c5a4b320: 00e9 fbff - - 0x00007f78c5a4b324: ; {static_stub} - 0x00007f78c5a4b324: ffff 48bb | 0000 0000 | 0000 0000 - - 0x00007f78c5a4b330: ; {runtime_call nmethod} - 0x00007f78c5a4b330: e9fb ffff - - 0x00007f78c5a4b334: ; {static_stub} - 0x00007f78c5a4b334: ff48 bb00 | 0000 0000 - - 0x00007f78c5a4b33c: ; {runtime_call nmethod} - 0x00007f78c5a4b33c: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b344: ; {static_stub} - 0x00007f78c5a4b344: 48bb 0000 | 0000 0000 - - 0x00007f78c5a4b34c: ; {runtime_call nmethod} - 0x00007f78c5a4b34c: 0000 e9fb - - 0x00007f78c5a4b350: ; {static_stub} - 0x00007f78c5a4b350: ffff ff48 | bb00 0000 | 0000 0000 - - 0x00007f78c5a4b35c: ; {runtime_call nmethod} - 0x00007f78c5a4b35c: 00e9 fbff - - 0x00007f78c5a4b360: ; {static_stub} - 0x00007f78c5a4b360: ffff 48bb | 0000 0000 | 0000 0000 - - 0x00007f78c5a4b36c: ; {runtime_call nmethod} - 0x00007f78c5a4b36c: e9fb ffff - - 0x00007f78c5a4b370: ; {static_stub} - 0x00007f78c5a4b370: ff48 bb20 | 51f7 9078 - - 0x00007f78c5a4b378: ; {runtime_call I2C/C2I adapters} - 0x00007f78c5a4b378: 7f00 00e9 | 36b2 a606 - - 0x00007f78c5a4b380: ; {static_stub} - 0x00007f78c5a4b380: 48bb 0000 | 0000 0000 - - 0x00007f78c5a4b388: ; {runtime_call nmethod} - 0x00007f78c5a4b388: 0000 e9fb - - 0x00007f78c5a4b38c: ; {static_stub} - 0x00007f78c5a4b38c: ffff ff48 | bb00 0000 | 0000 0000 - - 0x00007f78c5a4b398: ; {runtime_call nmethod} - 0x00007f78c5a4b398: 00e9 fbff - - 0x00007f78c5a4b39c: ; {static_stub} - 0x00007f78c5a4b39c: ffff 48bb | 0000 0000 | 0000 0000 - - 0x00007f78c5a4b3a8: ; {runtime_call nmethod} - 0x00007f78c5a4b3a8: e9fb ffff - - 0x00007f78c5a4b3ac: ; {static_stub} - 0x00007f78c5a4b3ac: ff48 bb00 | 0000 0000 - - 0x00007f78c5a4b3b4: ; {runtime_call nmethod} - 0x00007f78c5a4b3b4: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b3bc: ; {static_stub} - 0x00007f78c5a4b3bc: 48bb d01f | e290 787f - - 0x00007f78c5a4b3c4: ; {runtime_call I2C/C2I adapters} - 0x00007f78c5a4b3c4: 0000 e96f - - 0x00007f78c5a4b3c8: ; {static_stub} - 0x00007f78c5a4b3c8: 00a6 0648 | bb00 0000 | 0000 0000 - - 0x00007f78c5a4b3d4: ; {runtime_call nmethod} - 0x00007f78c5a4b3d4: 00e9 fbff - - 0x00007f78c5a4b3d8: ; {static_stub} - 0x00007f78c5a4b3d8: ffff 48bb | 0000 0000 | 0000 0000 - - 0x00007f78c5a4b3e4: ; {runtime_call nmethod} - 0x00007f78c5a4b3e4: e9fb ffff - - 0x00007f78c5a4b3e8: ; {static_stub} - 0x00007f78c5a4b3e8: ff48 bb00 | 0000 0000 - - 0x00007f78c5a4b3f0: ; {runtime_call nmethod} - 0x00007f78c5a4b3f0: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b3f8: ; {static_stub} - 0x00007f78c5a4b3f8: 48bb 0000 | 0000 0000 - - 0x00007f78c5a4b400: ; {runtime_call nmethod} - 0x00007f78c5a4b400: 0000 e9fb - - 0x00007f78c5a4b404: ; {static_stub} - 0x00007f78c5a4b404: ffff ff48 | bb00 0000 | 0000 0000 - - 0x00007f78c5a4b410: ; {runtime_call nmethod} - 0x00007f78c5a4b410: 00e9 fbff - - 0x00007f78c5a4b414: ; {static_stub} - 0x00007f78c5a4b414: ffff 48bb | 0000 0000 | 0000 0000 - - 0x00007f78c5a4b420: ; {runtime_call nmethod} - 0x00007f78c5a4b420: e9fb ffff - - 0x00007f78c5a4b424: ; {static_stub} - 0x00007f78c5a4b424: ff48 bb00 | 0000 0000 - - 0x00007f78c5a4b42c: ; {runtime_call nmethod} - 0x00007f78c5a4b42c: 0000 00e9 | fbff ffff - - 0x00007f78c5a4b434: ; {static_stub} - 0x00007f78c5a4b434: 48bb 0000 | 0000 0000 - - 0x00007f78c5a4b43c: ; {runtime_call nmethod} - 0x00007f78c5a4b43c: 0000 e9fb - - 0x00007f78c5a4b440: ; {static_stub} - 0x00007f78c5a4b440: ffff ff48 | bb00 0000 | 0000 0000 - - 0x00007f78c5a4b44c: ; {runtime_call nmethod} - 0x00007f78c5a4b44c: 00e9 fbff - - 0x00007f78c5a4b450: ; {runtime_call C1 Runtime handle_exception_from_callee_blob} - 0x00007f78c5a4b450: ffff e889 - - 0x00007f78c5a4b454: ; {external_word} - 0x00007f78c5a4b454: 79b3 0648 | bf99 ef4d | dc78 7f00 | 0048 83e4 - - 0x00007f78c5a4b464: ; {runtime_call MacroAssembler::debug64(char*, long, long*)} - 0x00007f78c5a4b464: f0e8 a6b1 - - 0x00007f78c5a4b468: ; {section_word} - 0x00007f78c5a4b468: 6e17 f449 | ba6b b4a4 | c578 7f00 - - 0x00007f78c5a4b474: ; {runtime_call DeoptimizationBlob} - 0x00007f78c5a4b474: 0041 52e9 | 643e a606 | f4f4 f4f4 -[/MachCode] - - -Compiled method (c1) 2047337 7324 3 androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$$Lambda/0x000000002159aaf0::invoke (47 bytes) - total in heap [0x00007f78c5a40b08,0x00007f78c5a40ec0] = 952 - main code [0x00007f78c5a40c00,0x00007f78c5a40e78] = 632 - stub code [0x00007f78c5a40e78,0x00007f78c5a40eb8] = 64 - oops [0x00007f78c5a40eb8,0x00007f78c5a40ec0] = 8 - mutable data [0x00007f7878042e70,0x00007f7878042ec8] = 88 - relocation [0x00007f7878042e70,0x00007f7878042eb8] = 72 - metadata [0x00007f7878042eb8,0x00007f7878042ec8] = 16 - immutable data [0x00007f7878042dc0,0x00007f7878042e60] = 160 - dependencies [0x00007f7878042dc0,0x00007f7878042dc8] = 8 - nul chk table [0x00007f7878042dc8,0x00007f7878042dd8] = 16 - scopes pcs [0x00007f7878042dd8,0x00007f7878042e38] = 96 - scopes data [0x00007f7878042e38,0x00007f7878042e60] = 40 - -[Constant Pool (empty)] - -[MachCode] -[Instructions begin] - 0x00007f78c5a40c00: 660f 1f84 | 0000 0000 | 0066 0f1f | 8400 0000 | 0000 448b | 5608 443b - - 0x00007f78c5a40c18: ; {runtime_call Shared Runtime ic_miss_blob} - 0x00007f78c5a40c18: 5008 0f85 | c0c9 a606 -[Verified Entry Point] - 0x00007f78c5a40c20: 8984 2400 | c0fe ff55 | 4881 ec90 | 0000 0090 | 4181 7f20 | 0500 0000 - - 0x00007f78c5a40c38: ; {runtime_call Stub::method_entry_barrier} - 0x00007f78c5a40c38: 7405 e861 - - 0x00007f78c5a40c3c: ; {metadata(method data for {method} {0x00007f7892a07030} 'invoke' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$$Lambda+0x000000002159aaf0')} - 0x00007f78c5a40c3c: b7a5 0648 | b978 96d2 | 9278 7f00 | 0044 8b81 | 9000 0000 | 4183 c002 | 4489 8190 | 0000 0041 - 0x00007f78c5a40c5c: 81e0 fe07 | 0000 4585 | c00f 849c | 0100 008b | 4e0c 48c1 | e103 448b | 4610 49c1 | e003 448b - 0x00007f78c5a40c7c: 4e14 49c1 | e103 8b7e | 1848 c1e7 | 038b 5e1c | 48c1 e303 | 8b46 2048 | c1e0 0344 | 8b5e 2449 - 0x00007f78c5a40c9c: c1e3 0344 | 8b6e 2849 | c1e5 0344 | 8b76 2c49 | c1e6 034c | 89ac 2480 | 0000 004c | 8974 2478 - 0x00007f78c5a40cbc: ; {metadata(method data for {method} {0x00007f7892a07030} 'invoke' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$$Lambda+0x000000002159aaf0')} - 0x00007f78c5a40cbc: 4885 d249 | bd78 96d2 | 9278 7f00 | 0075 0d41 | 808d d100 | 0000 01e9 | 9500 0000 | 448b 7208 - 0x00007f78c5a40cdc: 4d3b b5e0 | 0000 0075 | 0d49 8385 | e800 0000 | 01e9 6000 | 0000 4d3b | b5f0 0000 | 0075 0d49 - 0x00007f78c5a40cfc: 8385 f800 | 0000 01e9 | 4a00 0000 | 4983 bde0 | 0000 0000 | 7517 4d89 | b5e0 0000 | 0049 c785 - 0x00007f78c5a40d1c: e800 0000 | 0100 0000 | e929 0000 | 0049 83bd | f000 0000 | 0075 174d | 89b5 f000 | 0000 49c7 - 0x00007f78c5a40d3c: 85f8 0000 | 0001 0000 | 00e9 0800 | 0000 4983 | 85d8 0000 - - 0x00007f78c5a40d50: ; {metadata('java/lang/Long')} - 0x00007f78c5a40d50: 0001 49be | f0a7 0121 | 0000 0000 | 8b72 084c | 3bf6 0f85 | c000 0000 | e900 0000 | 0048 8bf2 - 0x00007f78c5a40d70: ; implicit exception: dispatches to 0x00007f78c5a40e31 - 0x00007f78c5a40d70: 483b 0648 - - 0x00007f78c5a40d74: ; {metadata(method data for {method} {0x00007f7892a07030} 'invoke' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$$Lambda+0x000000002159aaf0')} - 0x00007f78c5a40d74: 8bd6 49bd | 7896 d292 | 787f 0000 - - 0x00007f78c5a40d80: ; {metadata('java/lang/Long')} - 0x00007f78c5a40d80: 49ba f0a7 | 0121 0000 | 0000 4d89 | 9510 0100 | 0049 8385 | 1801 0000 | 014c 8b6e - - 0x00007f78c5a40d9c: ; {metadata(method data for {method} {0x00007f7892a07030} 'invoke' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$$Lambda+0x000000002159aaf0')} - 0x00007f78c5a40d9c: 1048 be78 | 96d2 9278 | 7f00 0048 | 8386 3801 | 0000 0148 | 8bf1 498b | d049 8bc9 | 4c8b c74c - 0x00007f78c5a40dbc: 8bcb 488b | f84c 891c | 2448 8b9c | 2480 0000 | 0048 895c | 2408 4c8b | 7424 784c | 8974 2410 - 0x00007f78c5a40ddc: 4c89 6c24 | 1866 0f1f - - 0x00007f78c5a40de4: ; {static_call} - 0x00007f78c5a40de4: 4400 00e8 - - 0x00007f78c5a40de8: ; ImmutableOopMap {} - ;*invokestatic invokeSuspend$lambda$2 {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$$Lambda/0x000000002159aaf0::invoke@43 - 0x00007f78c5a40de8: 1414 0000 - - 0x00007f78c5a40dec: ; {post_call_nop} - 0x00007f78c5a40dec: 0f1f 8400 | e402 0000 | 4881 c490 | 0000 005d - - 0x00007f78c5a40dfc: ; {poll_return} - 0x00007f78c5a40dfc: 493b 6728 | 0f87 3000 - - 0x00007f78c5a40e04: ; {metadata({method} {0x00007f7892a07030} 'invoke' '(Ljava/lang/Object;)Ljava/lang/Object;' in 'androidx/compose/runtime/Recomposer$runRecomposeAndApplyChanges$2$$Lambda+0x000000002159aaf0')} - 0x00007f78c5a40e04: 0000 c349 | ba30 70a0 | 9278 7f00 | 004c 8954 | 2408 48c7 | 0424 ffff - - 0x00007f78c5a40e1c: ; {runtime_call C1 Runtime counter_overflow_blob} - 0x00007f78c5a40e1c: ffff e83d - - 0x00007f78c5a40e20: ; ImmutableOopMap {rsi=Oop rdx=Oop } - ;*synchronization entry - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$$Lambda/0x000000002159aaf0::invoke@-1 - 0x00007f78c5a40e20: 5db4 06e9 | 43fe ffff | 4889 1424 - - 0x00007f78c5a40e2c: ; {runtime_call C1 Runtime throw_class_cast_exception_blob} - 0x00007f78c5a40e2c: e8af 25b4 - - 0x00007f78c5a40e30: ; ImmutableOopMap {rcx=Oop r8=Oop r9=Oop rdi=Oop rbx=Oop rax=Oop r11=Oop [120]=Oop [128]=Oop } - ;*checkcast {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$$Lambda/0x000000002159aaf0::invoke@37 - ; {runtime_call C1 Runtime throw_null_pointer_exception_blob} - 0x00007f78c5a40e30: 06e8 aafd - - 0x00007f78c5a40e34: ; ImmutableOopMap {rcx=Oop r8=Oop r9=Oop rdi=Oop rbx=Oop rax=Oop r11=Oop rsi=Oop [120]=Oop [128]=Oop } - ;*invokevirtual longValue {reexecute=0 rethrow=0 return_oop=0} - ; - androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$$Lambda/0x000000002159aaf0::invoke@40 - ; {internal_word} - 0x00007f78c5a40e34: b306 49ba | fc0d a4c5 | 787f 0000 | 4d89 9738 - - 0x00007f78c5a40e44: ; {runtime_call SafepointBlob} - 0x00007f78c5a40e44: 0500 00e9 | 14fd a606 | 498b 87e0 | 0500 0049 | c787 e005 | 0000 0000 | 0000 49c7 | 87e8 0500 - 0x00007f78c5a40e64: 0000 0000 | 0048 81c4 | 9000 0000 - - 0x00007f78c5a40e70: ; {runtime_call C1 Runtime unwind_exception_blob} - 0x00007f78c5a40e70: 5de9 6aee | b306 f4f4 -[Stub Code] - 0x00007f78c5a40e78: ; {static_stub} - 0x00007f78c5a40e78: 48bb 4868 | f790 787f - - 0x00007f78c5a40e80: ; {runtime_call I2C/C2I adapters} - 0x00007f78c5a40e80: 0000 e9e7 - - 0x00007f78c5a40e84: ; {runtime_call C1 Runtime handle_exception_from_callee_blob} - 0x00007f78c5a40e84: f6d8 06e8 | 541f b406 - - 0x00007f78c5a40e8c: ; {external_word} - 0x00007f78c5a40e8c: 48bf 99ef | 4ddc 787f | 0000 4883 - - 0x00007f78c5a40e98: ; {runtime_call MacroAssembler::debug64(char*, long, long*)} - 0x00007f78c5a40e98: e4f0 e871 | 576f 17f4 -[Deopt Handler Code] - 0x00007f78c5a40ea0: ; {section_word} - 0x00007f78c5a40ea0: 49ba a00e | a4c5 787f | 0000 4152 - - 0x00007f78c5a40eac: ; {runtime_call DeoptimizationBlob} - 0x00007f78c5a40eac: e92f e4a6 | 06f4 f4f4 | f4f4 f4f4 -[/MachCode] - - ---------------- P R O C E S S --------------- - -Threads class SMR info: -_java_thread_list=0x00007f78743b14f0, length=28, elements={ -0x00007f78d402f350, 0x00007f78d416ac80, 0x00007f78d4187890, 0x00007f78d4188f30, -0x00007f78d418a960, 0x00007f78d418c1c0, 0x00007f78d418d970, 0x00007f78d418f3f0, -0x00007f78d4190d10, 0x00007f78d421fad0, 0x00007f78d473fad0, 0x00007f78d4740c60, -0x00007f78d48b2810, 0x00007f78d48b3280, 0x00007f785004b8b0, 0x00007f78d48bd520, -0x00007f785003d9d0, 0x00007f78d48e74e0, 0x00007f78d48e9190, 0x00007f78682e0440, -0x00007f78683b4a40, 0x00007f785c1666d0, 0x00007f785c1687d0, 0x00007f78a4008c90, -0x00007f78a4009fa0, 0x00007f78699c8f70, 0x000055ebc500c030, 0x00007f78743b0610 -} - -Java Threads: ( => current thread ) - 0x00007f78d402f350 JavaThread "main" [_thread_blocked, id=1041995, stack(0x00007f78dbfc9000,0x00007f78dc0c9000) (1024K)] - 0x00007f78d416ac80 JavaThread "Common-Cleaner" daemon [_thread_blocked, id=1042003, stack(0x00007f78a135b000,0x00007f78a145b000) (1024K)] - 0x00007f78d4187890 JavaThread "Reference Handler" daemon [_thread_blocked, id=1042004, stack(0x00007f78a0f00000,0x00007f78a1000000) (1024K)] - 0x00007f78d4188f30 JavaThread "Finalizer" daemon [_thread_blocked, id=1042005, stack(0x00007f78a0dff000,0x00007f78a0eff000) (1024K)] - 0x00007f78d418a960 JavaThread "Signal Dispatcher" daemon [_thread_blocked, id=1042006, stack(0x00007f78a0cfe000,0x00007f78a0dfe000) (1024K)] - 0x00007f78d418c1c0 JavaThread "Service Thread" daemon [_thread_blocked, id=1042007, stack(0x00007f78a0bfd000,0x00007f78a0cfd000) (1024K)] - 0x00007f78d418d970 JavaThread "Monitor Deflation Thread" daemon [_thread_blocked, id=1042008, stack(0x00007f78a0afc000,0x00007f78a0bfc000) (1024K)] - 0x00007f78d418f3f0 JavaThread "C2 CompilerThread0" daemon [_thread_blocked, id=1042009, stack(0x00007f78a09fb000,0x00007f78a0afb000) (1024K)] - 0x00007f78d4190d10 JavaThread "C1 CompilerThread0" daemon [_thread_blocked, id=1042010, stack(0x00007f78a08fa000,0x00007f78a09fa000) (1024K)] - 0x00007f78d421fad0 JavaThread "Notification Thread" daemon [_thread_blocked, id=1042012, stack(0x00007f78a06f8000,0x00007f78a07f8000) (1024K)] - 0x00007f78d473fad0 JavaThread "Java2D Disposer" daemon [_thread_blocked, id=1042023, stack(0x00007f7856500000,0x00007f7856600000) (1024K)] - 0x00007f78d4740c60 JavaThread "AWT-XAWT" daemon [_thread_in_native, id=1042024, stack(0x00007f7855808000,0x00007f7855908000) (1024K)] - 0x00007f78d48b2810 JavaThread "DefaultDispatcher-worker-1" daemon [_thread_blocked, id=1042027, stack(0x00007f784d6f0000,0x00007f784d7f0000) (1024K)] - 0x00007f78d48b3280 JavaThread "DefaultDispatcher-worker-2" daemon [_thread_blocked, id=1042028, stack(0x00007f784d5ef000,0x00007f784d6ef000) (1024K)] - 0x00007f785004b8b0 JavaThread "DefaultDispatcher-worker-3" daemon [_thread_blocked, id=1042029, stack(0x00007f784d4ee000,0x00007f784d5ee000) (1024K)] - 0x00007f78d48bd520 JavaThread "Reference Cleaner" daemon [_thread_blocked, id=1042030, stack(0x00007f784d3ed000,0x00007f784d4ed000) (1024K)] - 0x00007f785003d9d0 JavaThread "kotlinx.coroutines.DefaultExecutor" daemon [_thread_blocked, id=1042031, stack(0x00007f784d2ec000,0x00007f784d3ec000) (1024K)] - 0x00007f78d48e74e0 JavaThread "AWT-Shutdown" [_thread_blocked, id=1042032, stack(0x00007f784d1eb000,0x00007f784d2eb000) (1024K)] -=>0x00007f78d48e9190 JavaThread "AWT-EventQueue-0" [_thread_in_native, id=1042033, stack(0x00007f784d0ea000,0x00007f784d1ea000) (1024K)] - 0x00007f78682e0440 JavaThread "TimerQueue" daemon [_thread_blocked, id=1042034, stack(0x00007f784cfe9000,0x00007f784d0e9000) (1024K)] - 0x00007f78683b4a40 JavaThread "JNA Cleaner" daemon [_thread_blocked, id=1042038, stack(0x00007f784cb00000,0x00007f784cc00000) (1024K)] - 0x00007f785c1666d0 JavaThread "DefaultDispatcher-worker-4" daemon [_thread_blocked, id=1042078, stack(0x00007f784cee8000,0x00007f784cfe8000) (1024K)] - 0x00007f785c1687d0 JavaThread "DefaultDispatcher-worker-5" daemon [_thread_blocked, id=1042079, stack(0x00007f7832e02000,0x00007f7832f02000) (1024K)] - 0x00007f78a4008c90 JavaThread "DefaultDispatcher-worker-6" daemon [_thread_blocked, id=1042080, stack(0x00007f7832d01000,0x00007f7832e01000) (1024K)] - 0x00007f78a4009fa0 JavaThread "DefaultDispatcher-worker-7" daemon [_thread_blocked, id=1042081, stack(0x00007f7832c00000,0x00007f7832d00000) (1024K)] - 0x00007f78699c8f70 JavaThread "skiko-dispatcher-to-block-on" daemon [_thread_blocked, id=1049262, stack(0x00007f78a04f6000,0x00007f78a05f6000) (1024K)] - 0x000055ebc500c030 JavaThread "skiko-dispatcher-to-block-on" daemon [_thread_blocked, id=1049263, stack(0x00007f78a03f5000,0x00007f78a04f5000) (1024K)] - 0x00007f78743b0610 JavaThread "C2 CompilerThread1" daemon [_thread_blocked, id=1049266, stack(0x00007f783ceff000,0x00007f783cfff000) (1024K)] -Total: 28 - -Other Threads: - 0x00007f78d414e760 VMThread "VM Thread" [id=1042002, stack(0x00007f78a145c000,0x00007f78a155c000) (1024K)] - 0x00007f78d410cc20 WatcherThread "VM Periodic Task Thread" [id=1042001, stack(0x00007f78a155d000,0x00007f78a165d000) (1024K)] - 0x00007f78d405e6d0 WorkerThread "GC Thread#0" [id=1041996, stack(0x00007f78b0e4a000,0x00007f78b0f4a000) (1024K)] - 0x00007f788800a4b0 WorkerThread "GC Thread#1" [id=1042039, stack(0x00007f784c9ff000,0x00007f784caff000) (1024K)] - 0x00007f788800b270 WorkerThread "GC Thread#2" [id=1042040, stack(0x00007f784c8fe000,0x00007f784c9fe000) (1024K)] - 0x00007f788800c030 WorkerThread "GC Thread#3" [id=1042041, stack(0x00007f784c7fd000,0x00007f784c8fd000) (1024K)] - 0x00007f788800cdf0 WorkerThread "GC Thread#4" [id=1042042, stack(0x00007f784c6fc000,0x00007f784c7fc000) (1024K)] - 0x00007f788800e000 WorkerThread "GC Thread#5" [id=1042043, stack(0x00007f784c5fb000,0x00007f784c6fb000) (1024K)] - 0x00007f788800edc0 WorkerThread "GC Thread#6" [id=1042044, stack(0x00007f784c4fa000,0x00007f784c5fa000) (1024K)] - 0x00007f788800fb80 WorkerThread "GC Thread#7" [id=1042045, stack(0x00007f784c3f9000,0x00007f784c4f9000) (1024K)] - 0x00007f7888010940 WorkerThread "GC Thread#8" [id=1042046, stack(0x00007f784c2f8000,0x00007f784c3f8000) (1024K)] - 0x00007f7888011740 WorkerThread "GC Thread#9" [id=1042047, stack(0x00007f784c1f7000,0x00007f784c2f7000) (1024K)] - 0x00007f7888012540 WorkerThread "GC Thread#10" [id=1042048, stack(0x00007f784c0f6000,0x00007f784c1f6000) (1024K)] - 0x00007f7888013340 WorkerThread "GC Thread#11" [id=1042049, stack(0x00007f7847f00000,0x00007f7848000000) (1024K)] - 0x00007f7888014140 WorkerThread "GC Thread#12" [id=1042050, stack(0x00007f7847dff000,0x00007f7847eff000) (1024K)] - 0x00007f7888014f40 WorkerThread "GC Thread#13" [id=1042051, stack(0x00007f7847cfe000,0x00007f7847dfe000) (1024K)] - 0x00007f7888015d40 WorkerThread "GC Thread#14" [id=1042052, stack(0x00007f7847bfd000,0x00007f7847cfd000) (1024K)] - 0x00007f7888016b40 WorkerThread "GC Thread#15" [id=1042053, stack(0x00007f7847afc000,0x00007f7847bfc000) (1024K)] - 0x00007f7888017940 WorkerThread "GC Thread#16" [id=1042054, stack(0x00007f78479fb000,0x00007f7847afb000) (1024K)] - 0x00007f7888018740 WorkerThread "GC Thread#17" [id=1042055, stack(0x00007f78478fa000,0x00007f78479fa000) (1024K)] - 0x00007f78d4077360 ConcurrentGCThread "G1 Main Marker" [id=1041997, stack(0x00007f78b0d49000,0x00007f78b0e49000) (1024K)] - 0x00007f78d4078560 WorkerThread "G1 Conc#0" [id=1041998, stack(0x00007f78b0c48000,0x00007f78b0d48000) (1024K)] - 0x00007f78a4003210 WorkerThread "G1 Conc#1" [id=1042056, stack(0x00007f78476de000,0x00007f78477de000) (1024K)] - 0x00007f78a4003b60 WorkerThread "G1 Conc#2" [id=1042057, stack(0x00007f78475dd000,0x00007f78476dd000) (1024K)] - 0x00007f78a4004940 WorkerThread "G1 Conc#3" [id=1042058, stack(0x00007f78474dc000,0x00007f78475dc000) (1024K)] - 0x00007f78a4005750 WorkerThread "G1 Conc#4" [id=1042059, stack(0x00007f78473db000,0x00007f78474db000) (1024K)] - 0x00007f78d40fe060 ConcurrentGCThread "G1 Refine#0" [id=1041999, stack(0x00007f78a18fa000,0x00007f78a19fa000) (1024K)] - 0x00007f78d40ff250 ConcurrentGCThread "G1 Service" [id=1042000, stack(0x00007f78a17f9000,0x00007f78a18f9000) (1024K)] -Total: 28 - -Threads with active compile tasks: -Total: 0 - -VM state: not at safepoint (normal execution) - -VM Mutex/Monitor currently owned by a thread: None - -Heap address: 0x0000000421000000, size: 15856 MB, Compressed Oops mode: Zero based, Oop shift amount: 3 - -CDS archive(s) not mapped -Compressed class space mapped at: 0x0000000021000000-0x0000000061000000, reserved size: 1073741824 -UseCompressedClassPointers 1, UseCompactObjectHeaders 0 -Narrow klass pointer bits 32, Max shift 3 -Narrow klass base: 0x0000000000000000, Narrow klass shift: 0 -Encoding Range: [0x0000000000000000 - 0x0000000100000000), (4294967296 bytes) -Klass Range: [0x0000000021000000 - 0x0000000061000000), (1073741824 bytes) -Klass ID Range: [553648128 - 1627389945) (1073741817) -No protection zone. - -GC Precious Log: - CardTable entry size: 512 - Card Set container configuration: InlinePtr #cards 4 size 8 Array Of Cards #cards 64 size 144 Howl #buckets 8 coarsen threshold 14745 Howl Bitmap #cards 2048 size 272 coarsen threshold 1843 Card regions per heap region 1 cards per card region 16384 - CPUs: 24 total, 24 available - Memory: 63403M - Large Page Support: Disabled - NUMA Support: Disabled - Compressed Oops: Enabled (Zero based) - Heap Region Size: 8M - Heap Min Capacity: 8M - Heap Initial Capacity: 992M - Heap Max Capacity: 15856M - Pre-touch: Disabled - Parallel Workers: 18 - Concurrent Workers: 5 - Concurrent Refinement Workers: 18 - Periodic GC: Disabled - -Heap: - garbage-first heap total reserved 16236544K, committed 65536K, used 22699K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 3 young (24576K), 1 survivors (8192K) - -Heap Regions: E=young(eden), S=young(survivor), O=old, HS=humongous(starts), HC=humongous(continues), CS=collection set, F=free, TAMS=top-at-mark-start, PB=parsable bottom -| 0|0x0000000421000000, 0x000000042160cd48, 0x0000000421800000| 75%| O| |TAMS 0x0000000421000000| PB 0x0000000421000000| Untracked | 0 -| 1|0x0000000421800000, 0x0000000421c120f0, 0x0000000422000000| 50%| O| |TAMS 0x0000000421800000| PB 0x0000000421800000| Untracked | 0 -| 2|0x0000000422000000, 0x0000000422000000, 0x0000000422800000| 0%| F| |TAMS 0x0000000422000000| PB 0x0000000422000000| Untracked | 0 -| 3|0x0000000422800000, 0x0000000422c0be40, 0x0000000423000000| 50%| S|CS|TAMS 0x0000000422800000| PB 0x0000000422800000| Complete | 0 -| 4|0x0000000423000000, 0x0000000423000000, 0x0000000423800000| 0%| F| |TAMS 0x0000000423000000| PB 0x0000000423000000| Untracked | 0 -| 42|0x0000000436000000, 0x0000000436000000, 0x0000000436800000| 0%| F| |TAMS 0x0000000436000000| PB 0x0000000436000000| Untracked | 0 -| 122|0x000000045e000000, 0x000000045e661fb0, 0x000000045e800000| 79%| E| |TAMS 0x000000045e000000| PB 0x000000045e000000| Complete | 0 -| 123|0x000000045e800000, 0x000000045f000000, 0x000000045f000000|100%| E|CS|TAMS 0x000000045e800000| PB 0x000000045e800000| Complete | 0 - -Card table byte_map: [0x00007f78c0806000,0x00007f78c26fe000] _byte_map_base: 0x00007f78be6fe000 - -Marking Bits: (CMBitMap*) 0x00007f78d405f420 - Bits: [0x00007f78b1046000, 0x00007f78c0806000) - -Polling page: 0x00007f78ddad7000 - -Metaspace: -Metaspace used 52962K, committed 53440K, reserved 1114112K - class space used 5762K, committed 5952K, reserved 1048576K - -Usage: - Non-class: 46.09 MB used. - Class: 5.63 MB used. - Both: 51.72 MB used. - -Virtual space: - Non-class space: 64.00 MB reserved, 46.38 MB ( 72%) committed, 1 nodes. - Class space: 1.00 GB reserved, 5.81 MB ( <1%) committed, 1 nodes. - Both: 1.06 GB reserved, 52.19 MB ( 5%) committed. - -Chunk freelists: - Non-Class: 880.00 KB - Class: 10.12 MB - Both: 10.97 MB - -MaxMetaspaceSize: unlimited -CompressedClassSpaceSize: 1.00 GB -Initial GC threshold: 21.00 MB -Current GC threshold: 59.19 MB -CDS: off - - commit_granule_bytes: 65536. - - commit_granule_words: 8192. - - virtual_space_node_default_size: 8388608. - - enlarge_chunks_in_place: 1. -UseCompressedClassPointers 1, UseCompactObjectHeaders 0 -Narrow klass pointer bits 32, Max shift 3 -Narrow klass base: 0x0000000000000000, Narrow klass shift: 0 -Encoding Range: [0x0000000000000000 - 0x0000000100000000), (4294967296 bytes) -Klass Range: [0x0000000021000000 - 0x0000000061000000), (1073741824 bytes) -Klass ID Range: [553648128 - 1627389945) (1073741817) -No protection zone. - - -Internal statistics: - -num_allocs_failed_limit: 9. -num_arena_births: 574. -num_arena_deaths: 0. -num_vsnodes_births: 2. -num_vsnodes_deaths: 0. -num_space_committed: 835. -num_space_uncommitted: 0. -num_chunks_returned_to_freelist: 9. -num_chunks_taken_from_freelist: 1934. -num_chunk_merges: 9. -num_chunk_splits: 1372. -num_chunks_enlarged: 1104. -num_inconsistent_stats: 0. - -CodeHeap 'non-profiled nmethods': size=119172Kb used=3602Kb max_used=3602Kb free=115569Kb - bounds [0x00007f78ccb9f000, 0x00007f78ccf2f000, 0x00007f78d4000000] -CodeHeap 'profiled nmethods': size=119164Kb used=11911Kb max_used=11911Kb free=107252Kb - bounds [0x00007f78c5000000, 0x00007f78c5bb0000, 0x00007f78cc45f000] -CodeHeap 'non-nmethods': size=7424Kb used=3206Kb max_used=5005Kb free=4217Kb - bounds [0x00007f78cc45f000, 0x00007f78cca2f000, 0x00007f78ccb9f000] -CodeCache: size=245760Kb, used=18719Kb, max_used=20518Kb, free=227038Kb - total_blobs=9821, nmethods=8162, adapters=1563, full_count=0 -Compilation: enabled, stopped_count=0, restarted_count=0 - -Compilation events (20 events): -Event: 2047.123 Thread 0x00007f78d4190d10 8374 3 java.util.TreeSet::remove (22 bytes) -Event: 2047.123 Thread 0x00007f78d4190d10 nmethod 8374 0x00007f78c5b9d108 code [0x00007f78c5b9d200, 0x00007f78c5b9d418] -Event: 2047.123 Thread 0x00007f78d4190d10 8375 3 androidx.compose.ui.graphics.layer.GraphicsLayer::discardContentIfReleasedAndHaveNoParentLayerUsages (310 bytes) -Event: 2047.124 Thread 0x00007f78d4190d10 nmethod 8375 0x00007f78c5b9d488 code [0x00007f78c5b9d580, 0x00007f78c5b9df50] -Event: 2047.124 Thread 0x00007f78d4190d10 8377 3 java.util.TreeMap::remove (24 bytes) -Event: 2047.124 Thread 0x00007f78d4190d10 nmethod 8377 0x00007f78c5b9df88 code [0x00007f78c5b9e080, 0x00007f78c5b9e2e0] -Event: 2047.124 Thread 0x00007f78d4190d10 8381 3 androidx.compose.ui.node.DepthSortedSetsForDifferentPasses::remove (43 bytes) -Event: 2047.124 Thread 0x00007f78d4190d10 nmethod 8381 0x00007f78c5b9e308 code [0x00007f78c5b9e400, 0x00007f78c5b9e7e0] -Event: 2047.124 Thread 0x00007f78d4190d10 8379 3 kotlinx.coroutines.ChildHandleNode::childCancelled (9 bytes) -Event: 2047.124 Thread 0x00007f78d4190d10 nmethod 8379 0x00007f78c5b9e808 code [0x00007f78c5b9e900, 0x00007f78c5b9f738] -Event: 2047.124 Thread 0x00007f78d4190d10 8384 3 androidx.compose.runtime.collection.MutableVector::remove (20 bytes) -Event: 2047.124 Thread 0x00007f78d4190d10 nmethod 8384 0x00007f78c5b9f788 code [0x00007f78c5b9f880, 0x00007f78c5b9fb38] -Event: 2047.124 Thread 0x00007f78d4190d10 8380 3 kotlinx.coroutines.JobSupport::childCancelled (30 bytes) -Event: 2047.124 Thread 0x00007f78d4190d10 nmethod 8380 0x00007f78c5b9fb88 code [0x00007f78c5b9fc80, 0x00007f78c5ba0070] -Event: 2047.124 Thread 0x00007f78d4190d10 8388 3 androidx.compose.runtime.composer.gapbuffer.SlotWriter::childGroupAtIndex (101 bytes) -Event: 2047.124 Thread 0x00007f78d418f3f0 nmethod 8378 0x00007f78ccf21c88 code [0x00007f78ccf21d80, 0x00007f78ccf22170] -Event: 2047.124 Thread 0x00007f78d4190d10 nmethod 8388 0x00007f78c5ba0088 code [0x00007f78c5ba0180, 0x00007f78c5ba0950] -Event: 2047.124 Thread 0x00007f78d4190d10 8389 3 androidx.collection.MutableIntSet::add (32 bytes) -Event: 2047.124 Thread 0x00007f78d418f3f0 8387 4 androidx.compose.ui.node.LayoutNode::getMeasurePending$ui (8 bytes) -Event: 2047.124 Thread 0x00007f78743b0610 8376 4 kotlinx.coroutines.internal.LockFreeLinkedListNode::getNext (8 bytes) - -GC Heap Usage History (20 events): -Event: 0.674 {heap Before GC invocations=4 (full 0): - garbage-first heap total reserved 16236544K, committed 40960K, used 23809K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 3 young (24576K), 1 survivors (8192K) -} -Event: 0.677 {heap After GC invocations=5 (full 0): - garbage-first heap total reserved 16236544K, committed 40960K, used 7820K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} -Event: 0.759 {heap Before GC invocations=5 (full 0): - garbage-first heap total reserved 16236544K, committed 40960K, used 24204K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 3 young (24576K), 1 survivors (8192K) -} -Event: 0.760 {heap After GC invocations=6 (full 0): - garbage-first heap total reserved 16236544K, committed 40960K, used 9241K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} -Event: 0.789 {heap Before GC invocations=6 (full 0): - garbage-first heap total reserved 16236544K, committed 40960K, used 17433K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 3 young (24576K), 1 survivors (8192K) -} -Event: 0.791 {heap After GC invocations=7 (full 0): - garbage-first heap total reserved 16236544K, committed 532480K, used 17747K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} -Event: 0.910 {heap Before GC invocations=7 (full 0): - garbage-first heap total reserved 16236544K, committed 532480K, used 173395K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 22 young (180224K), 1 survivors (8192K) -} -Event: 0.912 {heap After GC invocations=8 (full 0): - garbage-first heap total reserved 16236544K, committed 532480K, used 10655K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} -Event: 0.941 {heap Before GC invocations=9 (full 0): - garbage-first heap total reserved 16236544K, committed 57344K, used 43423K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 6 young (49152K), 1 survivors (8192K) -} -Event: 0.943 {heap After GC invocations=10 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 10730K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} -Event: 1.161 {heap Before GC invocations=10 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 35306K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 4 young (32768K), 1 survivors (8192K) -} -Event: 1.164 {heap After GC invocations=11 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 10925K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} -Event: 1.419 {heap Before GC invocations=11 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 27309K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 4 young (32768K), 1 survivors (8192K) -} -Event: 1.421 {heap After GC invocations=12 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 12415K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} -Event: 2.495 {heap Before GC invocations=12 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 45183K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 4 young (32768K), 1 survivors (8192K) -} -Event: 2.498 {heap After GC invocations=13 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 14088K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} -Event: 9.638 {heap Before GC invocations=13 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 30472K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 4 young (32768K), 1 survivors (8192K) -} -Event: 9.641 {heap After GC invocations=14 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 14649K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} -Event: 92.609 {heap Before GC invocations=14 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 47417K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 4 young (32768K), 1 survivors (8192K) -} -Event: 92.612 {heap After GC invocations=15 (full 0): - garbage-first heap total reserved 16236544K, committed 65536K, used 14507K [0x0000000421000000, 0x0000000800000000) - region size 8192K, 1 young (8192K), 1 survivors (8192K) -} - -Metaspace Usage History (20 events): -Event: 0.674 {metaspace Before GC invocations=4 (full 0): - Metaspace used 24200K, committed 24448K, reserved 1114112K - class space used 2568K, committed 2688K, reserved 1048576K -} -Event: 0.677 {metaspace After GC invocations=5 (full 0): - Metaspace used 24200K, committed 24448K, reserved 1114112K - class space used 2568K, committed 2688K, reserved 1048576K -} -Event: 0.759 {metaspace Before GC invocations=5 (full 0): - Metaspace used 30049K, committed 30208K, reserved 1114112K - class space used 3274K, committed 3328K, reserved 1048576K -} -Event: 0.760 {metaspace After GC invocations=6 (full 0): - Metaspace used 30049K, committed 30208K, reserved 1114112K - class space used 3274K, committed 3328K, reserved 1048576K -} -Event: 0.789 {metaspace Before GC invocations=6 (full 0): - Metaspace used 31576K, committed 31872K, reserved 1114112K - class space used 3433K, committed 3584K, reserved 1048576K -} -Event: 0.791 {metaspace After GC invocations=7 (full 0): - Metaspace used 31576K, committed 31872K, reserved 1114112K - class space used 3433K, committed 3584K, reserved 1048576K -} -Event: 0.910 {metaspace Before GC invocations=7 (full 0): - Metaspace used 35740K, committed 35968K, reserved 1114112K - class space used 3858K, committed 3968K, reserved 1048576K -} -Event: 0.912 {metaspace After GC invocations=8 (full 0): - Metaspace used 35740K, committed 35968K, reserved 1114112K - class space used 3858K, committed 3968K, reserved 1048576K -} -Event: 0.941 {metaspace Before GC invocations=9 (full 0): - Metaspace used 36671K, committed 36928K, reserved 1114112K - class space used 3963K, committed 4032K, reserved 1048576K -} -Event: 0.943 {metaspace After GC invocations=10 (full 0): - Metaspace used 36671K, committed 36928K, reserved 1114112K - class space used 3963K, committed 4032K, reserved 1048576K -} -Event: 1.161 {metaspace Before GC invocations=10 (full 0): - Metaspace used 39741K, committed 40064K, reserved 1114112K - class space used 4339K, committed 4480K, reserved 1048576K -} -Event: 1.164 {metaspace After GC invocations=11 (full 0): - Metaspace used 39741K, committed 40064K, reserved 1114112K - class space used 4339K, committed 4480K, reserved 1048576K -} -Event: 1.419 {metaspace Before GC invocations=11 (full 0): - Metaspace used 45199K, committed 45568K, reserved 1114112K - class space used 5024K, committed 5184K, reserved 1048576K -} -Event: 1.421 {metaspace After GC invocations=12 (full 0): - Metaspace used 45199K, committed 45568K, reserved 1114112K - class space used 5024K, committed 5184K, reserved 1048576K -} -Event: 2.495 {metaspace Before GC invocations=12 (full 0): - Metaspace used 49277K, committed 49792K, reserved 1114112K - class space used 5533K, committed 5760K, reserved 1048576K -} -Event: 2.498 {metaspace After GC invocations=13 (full 0): - Metaspace used 49277K, committed 49792K, reserved 1114112K - class space used 5533K, committed 5760K, reserved 1048576K -} -Event: 9.638 {metaspace Before GC invocations=13 (full 0): - Metaspace used 51627K, committed 52160K, reserved 1114112K - class space used 5612K, committed 5824K, reserved 1048576K -} -Event: 9.641 {metaspace After GC invocations=14 (full 0): - Metaspace used 51627K, committed 52160K, reserved 1114112K - class space used 5612K, committed 5824K, reserved 1048576K -} -Event: 92.609 {metaspace Before GC invocations=14 (full 0): - Metaspace used 52545K, committed 52992K, reserved 1114112K - class space used 5717K, committed 5888K, reserved 1048576K -} -Event: 92.612 {metaspace After GC invocations=15 (full 0): - Metaspace used 52545K, committed 52992K, reserved 1114112K - class space used 5717K, committed 5888K, reserved 1048576K -} - -Dll operation events (14 events): -Event: 0.001 Loaded shared library /opt/android-studio/jbr/lib/libjava.so -Event: 0.006 Loaded shared library /opt/android-studio/jbr/lib/libzip.so -Event: 0.049 Loaded shared library /opt/android-studio/jbr/lib/libnio.so -Event: 0.053 Loaded shared library /opt/android-studio/jbr/lib/libzip.so -Event: 0.116 Loaded shared library /opt/android-studio/jbr/lib/libjimage.so -Event: 0.124 Loaded shared library /opt/android-studio/jbr/lib/libnet.so -Event: 0.132 Loaded shared library /home/mey/.skiko/skiko-linux-x64-434f7633c843870c0839a1137b128f6c3db3c5d58dcad3545e90827e3e341afb/libskiko-linux-x64.so -Event: 0.144 Loaded shared library /opt/android-studio/jbr/lib/libawt.so -Event: 0.145 Loaded shared library /opt/android-studio/jbr/lib/libawt_xawt.so -Event: 0.171 Loaded shared library /opt/android-studio/jbr/lib/libfontmanager.so -Event: 0.171 Loaded shared library /opt/android-studio/jbr/lib/libfontmanager_xawt.so -Event: 0.438 Loaded shared library /home/mey/.cache/JNA/temp/jna12348363668190973279.tmp -Event: 0.639 Loaded shared library /opt/android-studio/jbr/lib/libjaas.so -Event: 2047.115 Loaded shared library /opt/android-studio/jbr/lib/libextnet.so - -Deoptimization events (20 events): -Event: 2047.119 Thread 0x00007f78d48e9190 DEOPT PACKING pc=0x00007f78cce693ac sp=0x00007f784d1e7070 -Event: 2047.119 Thread 0x00007f78d48e9190 DEOPT UNPACKING pc=0x00007f78cc5882f9 sp=0x00007f784d1e6fa0 mode 2 -Event: 2047.122 Thread 0x00007f78d48e9190 Uncommon trap: trap_request=0xffffff45 fr.pc=0x00007f78cceadd24 relative=0x00000000000000a4 -Event: 2047.122 Thread 0x00007f78d48e9190 Uncommon trap: reason=unstable_if action=reinterpret pc=0x00007f78cceadd24 method=java.util.IdentityHashMap.isEmpty()Z @ 4 c2 -Event: 2047.122 Thread 0x00007f78d48e9190 DEOPT PACKING pc=0x00007f78cceadd24 sp=0x00007f784d1e7300 -Event: 2047.122 Thread 0x00007f78d48e9190 DEOPT UNPACKING pc=0x00007f78cc5882f9 sp=0x00007f784d1e7260 mode 2 -Event: 2047.122 Thread 0x00007f78d48e74e0 Uncommon trap: trap_request=0xffffff45 fr.pc=0x00007f78ccead9d4 relative=0x0000000000000054 -Event: 2047.122 Thread 0x00007f78d48e74e0 Uncommon trap: reason=unstable_if action=reinterpret pc=0x00007f78ccead9d4 method=java.util.IdentityHashMap.isEmpty()Z @ 4 c2 -Event: 2047.122 Thread 0x00007f78d48e74e0 DEOPT PACKING pc=0x00007f78ccead9d4 sp=0x00007f784d2e9820 -Event: 2047.122 Thread 0x00007f78d48e74e0 DEOPT UNPACKING pc=0x00007f78cc5882f9 sp=0x00007f784d2e97d0 mode 2 -Event: 2047.122 Thread 0x00007f78d48e9190 Uncommon trap: trap_request=0xffffff45 fr.pc=0x00007f78cce39c7c relative=0x000000000000017c -Event: 2047.122 Thread 0x00007f78d48e9190 Uncommon trap: reason=unstable_if action=reinterpret pc=0x00007f78cce39c7c method=androidx.compose.ui.scene.BaseComposeScene.updateInvalidations()V @ 47 c2 -Event: 2047.122 Thread 0x00007f78d48e9190 DEOPT PACKING pc=0x00007f78cce39c7c sp=0x00007f784d1e7450 -Event: 2047.122 Thread 0x00007f78d48e9190 DEOPT UNPACKING pc=0x00007f78cc5882f9 sp=0x00007f784d1e7348 mode 2 -Event: 2047.123 Thread 0x00007f78d48e9190 Uncommon trap: trap_request=0xffffff45 fr.pc=0x00007f78cce1db58 relative=0x0000000000000158 -Event: 2047.123 Thread 0x00007f78d48e9190 Uncommon trap: reason=unstable_if action=reinterpret pc=0x00007f78cce1db58 method=androidx.compose.ui.scene.BaseComposeScene.updateInvalidations()V @ 47 c2 -Event: 2047.123 Thread 0x00007f78d48e9190 DEOPT PACKING pc=0x00007f78cce1db58 sp=0x00007f784d1e7290 -Event: 2047.123 Thread 0x00007f78d48e9190 DEOPT UNPACKING pc=0x00007f78cc5882f9 sp=0x00007f784d1e7248 mode 2 -Event: 2047.124 Thread 0x00007f78d48e9190 DEOPT PACKING pc=0x00007f78c54d05b4 sp=0x00007f784d1e70d0 -Event: 2047.124 Thread 0x00007f78d48e9190 DEOPT UNPACKING pc=0x00007f78cc4afb8f sp=0x00007f784d1e6598 mode 0 - -Classes loaded (20 events): -Event: 2047.115 Loading class jdk/net/UnixDomainPrincipal -Event: 2047.115 Loading class jdk/net/UnixDomainPrincipal done -Event: 2047.115 Loading class jdk/net/ExtendedSocketOptions$PlatformSocketOptions -Event: 2047.115 Loading class jdk/net/ExtendedSocketOptions$PlatformSocketOptions done -Event: 2047.115 Loading class jdk/net/ExtendedSocketOptions$2 -Event: 2047.115 Loading class jdk/net/ExtendedSocketOptions$2 done -Event: 2047.115 Loading class jdk/net/LinuxSocketOptions -Event: 2047.115 Loading class jdk/net/LinuxSocketOptions done -Event: 2047.115 Loading class jdk/net/ExtendedSocketOptions$1 -Event: 2047.115 Loading class jdk/net/ExtendedSocketOptions$1 done -Event: 2047.116 Loading class java/nio/channels/ClosedByInterruptException -Event: 2047.116 Loading class java/nio/channels/AsynchronousCloseException -Event: 2047.116 Loading class java/nio/channels/AsynchronousCloseException done -Event: 2047.116 Loading class java/nio/channels/ClosedByInterruptException done -Event: 2047.118 Loading class java/awt/Window$1DisposeAction -Event: 2047.118 Loading class java/awt/Window$1DisposeAction done -Event: 2047.118 Loading class sun/awt/X11/XUnmapEvent -Event: 2047.118 Loading class sun/awt/X11/XUnmapEvent done -Event: 2047.121 Loading class java/awt/Component$FlipBufferStrategy -Event: 2047.122 Loading class java/awt/Component$FlipBufferStrategy done - -Classes unloaded (0 events): -No events - -Classes redefined (0 events): -No events - -Internal exceptions (20 events): -Event: 1.325 Thread 0x00007f78d48e9190 Exception (0x0000000436184108) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.331 Thread 0x00007f78d48e9190 Exception (0x00000004362371c0) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.331 Thread 0x00007f78d48e9190 Exception (0x0000000436240d48) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.351 Thread 0x00007f78d48e9190 Exception (0x00000004363316f8) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.351 Thread 0x00007f78d48e9190 Exception (0x0000000436342da8) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.358 Thread 0x00007f78d48e9190 Exception (0x00000004363a7210) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.375 Thread 0x00007f78d48e9190 Exception (0x000000043644ec98) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.377 Thread 0x00007f78d48e9190 Exception (0x00000004364a2648) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.453 Thread 0x00007f78d48e9190 Exception (0x000000045ec5def8) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.476 Thread 0x00007f78d48e9190 Exception (0x0000000436170dc8) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.476 Thread 0x00007f78d48e9190 Exception (0x00000004361812e0) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.478 Thread 0x00007f78d48e9190 Exception (0x00000004361b4d88) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.556 Thread 0x00007f78d4740c60 Exception (0x0000000436450878) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 1.556 Thread 0x00007f78d4740c60 Exception (0x00000004366558c8) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 2.495 Thread 0x00007f78d48e9190 Exception (0x00000004367fe430) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 6.469 Thread 0x00007f78d48e9190 Exception (0x000000045efb6808) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 91.755 Thread 0x00007f78d48e9190 Exception (0x000000045e33f820) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 91.869 Thread 0x00007f78d48e9190 Exception (0x000000045e456900) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 91.876 Thread 0x00007f78d48e9190 Exception (0x000000045e4ba500) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 815] -Event: 2047.116 Thread 0x00007f7868602f40 Exception (0x000000045e5ed720) -thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 882] - -VM Operations (20 events): -Event: 1800.461 Executing non-safepoint VM operation: RendezvousGCThreads -Event: 1800.461 Executing non-safepoint VM operation: RendezvousGCThreads done -Event: 1860.475 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) -Event: 1860.475 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) done -Event: 1860.475 Executing non-safepoint VM operation: RendezvousGCThreads -Event: 1860.475 Executing non-safepoint VM operation: RendezvousGCThreads done -Event: 1920.489 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) -Event: 1920.489 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) done -Event: 1920.489 Executing non-safepoint VM operation: RendezvousGCThreads -Event: 1920.489 Executing non-safepoint VM operation: RendezvousGCThreads done -Event: 1980.503 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) -Event: 1980.503 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) done -Event: 1980.503 Executing non-safepoint VM operation: RendezvousGCThreads -Event: 1980.503 Executing non-safepoint VM operation: RendezvousGCThreads done -Event: 2040.517 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) -Event: 2040.517 Executing non-safepoint VM operation: HandshakeAllThreads (HandshakeForDeflation) done -Event: 2040.517 Executing non-safepoint VM operation: RendezvousGCThreads -Event: 2040.517 Executing non-safepoint VM operation: RendezvousGCThreads done -Event: 2047.114 Executing non-safepoint VM operation: HandshakeAllThreads (Deoptimize) -Event: 2047.114 Executing non-safepoint VM operation: HandshakeAllThreads (Deoptimize) done - -Memory protections (20 events): -Event: 4.438 Protecting memory [0x00007f784db00000,0x00007f784db04000] with protection modes 0 -Event: 4.532 Protecting memory [0x00007f78a05f7000,0x00007f78a05fb000] with protection modes 0 -Event: 4.599 Protecting memory [0x00007f78a04f6000,0x00007f78a04fa000] with protection modes 0 -Event: 6.501 Protecting memory [0x00007f78a07f9000,0x00007f78a07fd000] with protection modes 0 -Event: 7.043 Protecting memory [0x00007f784db00000,0x00007f784db04000] with protection modes 0 -Event: 7.043 Protecting memory [0x00007f784d9ff000,0x00007f784da03000] with protection modes 0 -Event: 8.667 Protecting memory [0x00007f78a05f7000,0x00007f78a05fb000] with protection modes 0 -Event: 89.808 Protecting memory [0x00007f78a03f5000,0x00007f78a03f9000] with protection modes 0 -Event: 89.808 Protecting memory [0x00007f78a04f6000,0x00007f78a04fa000] with protection modes 0 -Event: 90.946 Protecting memory [0x00007f782e453000,0x00007f782e457000] with protection modes 0 -Event: 91.173 Protecting memory [0x00007f78a07f9000,0x00007f78a07fd000] with protection modes 0 -Event: 91.173 Protecting memory [0x00007f784db00000,0x00007f784db04000] with protection modes 0 -Event: 91.173 Protecting memory [0x00007f784d9ff000,0x00007f784da03000] with protection modes 0 -Event: 91.762 Protecting memory [0x00007f782e453000,0x00007f782e457000] with protection modes 0 -Event: 93.671 Protecting memory [0x00007f78a07f9000,0x00007f78a07fd000] with protection modes 0 -Event: 174.477 Protecting memory [0x00007f78a04f6000,0x00007f78a04fa000] with protection modes 0 -Event: 174.477 Protecting memory [0x00007f78a03f5000,0x00007f78a03f9000] with protection modes 0 -Event: 2046.028 Protecting memory [0x00007f78a04f6000,0x00007f78a04fa000] with protection modes 0 -Event: 2046.028 Protecting memory [0x00007f78a03f5000,0x00007f78a03f9000] with protection modes 0 -Event: 2047.124 Protecting memory [0x00007f783ceff000,0x00007f783cf03000] with protection modes 0 - -Nmethod flushes (20 events): -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78ccbfa008 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78ccbfaa08 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78ccbfba08 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5025e08 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5026f08 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5027588 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5027e08 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5073808 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5188108 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c518af08 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c518ca88 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c518d608 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c518f608 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5190308 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5190808 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5029588 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c5078b88 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c507be08 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c516e588 -Event: 0.919 Thread 0x00007f78d414e760 flushing nmethod 0x00007f78c51f8508 - -Events (20 events): -Event: 93.192 Thread 0x00007f786991e5e0 Thread exited: 0x00007f786991e5e0 -Event: 93.254 Thread 0x00007f7869673800 Thread exited: 0x00007f7869673800 -Event: 93.542 Thread 0x00007f786800b8e0 Thread exited: 0x00007f786800b8e0 -Event: 93.671 Thread 0x00007f78d418f3f0 Thread added: 0x00007f78748739a0 -Event: 94.853 Thread 0x00007f78748739a0 Thread exited: 0x00007f78748739a0 -Event: 155.251 Thread 0x00007f78699c8f70 Thread exited: 0x00007f78699c8f70 -Event: 155.257 Thread 0x00007f788001eb20 Thread exited: 0x00007f788001eb20 -Event: 174.477 Thread 0x00007f78d48e9190 Thread added: 0x00007f78699c8f70 -Event: 174.477 Thread 0x00007f78699c8f70 Thread added: 0x00007f78ac012d10 -Event: 234.667 Thread 0x00007f78ac012d10 Thread exited: 0x00007f78ac012d10 -Event: 234.673 Thread 0x00007f78699c8f70 Thread exited: 0x00007f78699c8f70 -Event: 2046.028 Thread 0x00007f78d48e9190 Thread added: 0x00007f78699c8f70 -Event: 2046.028 Thread 0x00007f78699c8f70 Thread added: 0x000055ebc500c030 -Event: 2047.116 Thread 0x00007f78800142d0 Thread exited: 0x00007f78800142d0 -Event: 2047.116 Thread 0x00007f7880008ef0 Thread exited: 0x00007f7880008ef0 -Event: 2047.116 Thread 0x00007f78800157c0 Thread exited: 0x00007f78800157c0 -Event: 2047.116 Thread 0x00007f7880013080 Thread exited: 0x00007f7880013080 -Event: 2047.116 Thread 0x00007f78686100d0 Thread exited: 0x00007f78686100d0 -Event: 2047.116 Thread 0x00007f7868602f40 Thread exited: 0x00007f7868602f40 -Event: 2047.124 Thread 0x00007f78d418f3f0 Thread added: 0x00007f78743b0610 - - -Dynamic libraries: -21000000-21080000 rw-p 00000000 00:00 0 -21080000-210c0000 rw-p 00000000 00:00 0 -210c0000-21140000 rw-p 00000000 00:00 0 -21140000-21200000 rw-p 00000000 00:00 0 -21200000-21240000 rw-p 00000000 00:00 0 -21240000-21280000 rw-p 00000000 00:00 0 -21280000-21340000 rw-p 00000000 00:00 0 -21340000-21380000 rw-p 00000000 00:00 0 -21380000-21440000 rw-p 00000000 00:00 0 -21440000-21460000 rw-p 00000000 00:00 0 -21460000-21480000 ---p 00000000 00:00 0 -21480000-215f0000 rw-p 00000000 00:00 0 -215f0000-61000000 ---p 00000000 00:00 0 -61000000-610af000 rw-p 00000000 00:00 0 -610af000-610b1000 r-xs 00000000 00:01 796506 /memfd:/.glXXXXXX (deleted) -421000000-423800000 rw-p 00000000 00:00 0 -423800000-436000000 ---p 00000000 00:00 0 -436000000-436800000 rw-p 00000000 00:00 0 -436800000-45e000000 ---p 00000000 00:00 0 -45e000000-45f000000 rw-p 00000000 00:00 0 -45f000000-800000000 ---p 00000000 00:00 0 -55eb8a0cf000-55eb8a0d0000 r--p 00000000 103:05 53876768 /opt/android-studio/jbr/bin/java -55eb8a0d0000-55eb8a0d1000 r-xp 00000000 103:05 53876768 /opt/android-studio/jbr/bin/java -55eb8a0d1000-55eb8a0d3000 r--p 00000000 103:05 53876768 /opt/android-studio/jbr/bin/java -55eb8a0d3000-55eb8a0d4000 rw-p 00001000 103:05 53876768 /opt/android-studio/jbr/bin/java -55ebc4ec1000-55ebc508f000 rw-p 00000000 00:00 0 [heap] -7f782e452000-7f782e453000 rw-p 00000000 00:00 0 -7f782e453000-7f782e457000 ---p 00000000 00:00 0 -7f782e457000-7f782e553000 rw-p 00000000 00:00 0 -7f782e553000-7f782e753000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f782fb29000-7f7831d7f000 rw-p 00000000 00:00 0 -7f7831d7f000-7f783217f000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f783217f000-7f783257f000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f783257f000-7f783277f000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f783277f000-7f783297f000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f783297f000-7f7832b7f000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f7832bff000-7f7832c00000 rw-p 00000000 00:00 0 -7f7832c00000-7f7832c04000 ---p 00000000 00:00 0 -7f7832c04000-7f7832d01000 rw-p 00000000 00:00 0 -7f7832d01000-7f7832d05000 ---p 00000000 00:00 0 -7f7832d05000-7f7832e02000 rw-p 00000000 00:00 0 -7f7832e02000-7f7832e06000 ---p 00000000 00:00 0 -7f7832e06000-7f7832f02000 rw-p 00000000 00:00 0 -7f7832f02000-7f7832f9a000 r--p 00000000 103:05 69220070 /usr/share/fonts/noto/NotoSans-Medium.ttf -7f7832f9a000-7f7833054000 r--p 00000000 103:05 68061758 /usr/share/fonts/TTF/DejaVuSans.ttf -7f7833054000-7f78330ec000 r--p 00000000 103:05 69220086 /usr/share/fonts/noto/NotoSans-Regular.ttf -7f78330ec000-7f7833152000 r--p 00000000 103:05 68166300 /usr/share/fonts/liberation/LiberationSans-Bold.ttf -7f7833152000-7f7833200000 r--p 00000000 103:05 68061753 /usr/share/fonts/TTF/DejaVuSans-Bold.ttf -7f7833200000-7f783355b000 r--p 00000000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 -7f783355b000-7f7837af8000 r-xp 0035b000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 -7f7837af8000-7f78397e8000 r--p 048f8000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 -7f78397e8000-7f7839a3a000 r--p 065e7000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 -7f7839a3a000-7f7839bc5000 rw-p 06839000 103:05 67934535 /usr/lib/libnvidia-gpucomp.so.610.57.04 -7f7839bc5000-7f7839c80000 rw-p 00000000 00:00 0 -7f7839c80000-7f7839ce5000 r--p 00000000 103:05 68166303 /usr/share/fonts/liberation/LiberationSans-Regular.ttf -7f7839ce5000-7f7839d80000 r--p 00000000 103:05 69220021 /usr/share/fonts/noto/NotoSans-Bold.ttf -7f7839d80000-7f7839e00000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f7839e00000-7f783a050000 r-xp 00000000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 -7f783a050000-7f783a071000 rwxp 00250000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 -7f783a071000-7f783a200000 r-xp 00271000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 -7f783a200000-7f783ae00000 r-xp 00400000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 -7f783ae00000-7f783c3f9000 r-xp 01000000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 -7f783c3f9000-7f783c3fa000 ---p 025f9000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 -7f783c3fa000-7f783c572000 r--p 025f9000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 -7f783c572000-7f783c5d5000 rw-p 02771000 103:05 67933291 /usr/lib/libnvidia-glcore.so.610.57.04 -7f783c5d5000-7f783c5f5000 rw-p 00000000 00:00 0 -7f783c70d000-7f783c78d000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f783c78d000-7f783c7cd000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f783c7cd000-7f783c81e000 r--p 00000000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 -7f783c81e000-7f783c87b000 r-xp 00051000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 -7f783c87b000-7f783c8b0000 rwxp 000ae000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 -7f783c8b0000-7f783c8b1000 r-xp 000e3000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 -7f783c8b1000-7f783c8d1000 r--p 000e4000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 -7f783c8d1000-7f783c8f1000 r--p 00103000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 -7f783c8f1000-7f783c8f6000 rw-p 00123000 103:05 67920781 /usr/lib/libGLX_nvidia.so.610.57.04 -7f783c8f6000-7f783c8f8000 rw-p 00000000 00:00 0 -7f783c8f8000-7f783c8f9000 rw-p 00000000 00:00 0 -7f783c8f9000-7f783c8fd000 ---p 00000000 00:00 0 -7f783c8fd000-7f783c9fa000 rw-p 00000000 00:00 0 -7f783c9fa000-7f783c9fe000 ---p 00000000 00:00 0 -7f783c9fe000-7f783cafb000 rw-p 00000000 00:00 0 -7f783cafb000-7f783caff000 ---p 00000000 00:00 0 -7f783caff000-7f783cbfc000 rw-p 00000000 00:00 0 -7f783cbfc000-7f783cc00000 ---p 00000000 00:00 0 -7f783cc00000-7f783ccfd000 rw-p 00000000 00:00 0 -7f783ccfd000-7f783cd01000 ---p 00000000 00:00 0 -7f783cd01000-7f783cdfe000 rw-p 00000000 00:00 0 -7f783cdfe000-7f783ce02000 ---p 00000000 00:00 0 -7f783ce02000-7f783ceff000 rw-p 00000000 00:00 0 -7f783ceff000-7f783cf03000 ---p 00000000 00:00 0 -7f783cf03000-7f783d800000 rw-p 00000000 00:00 0 -7f7841820000-7f7841823000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f7841823000-7f7841825000 rw-s 00000000 00:01 796506 /memfd:/.glXXXXXX (deleted) -7f7841829000-7f784183c000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f784183c000-7f784183e000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f784183e000-7f784187e000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f784187e000-7f78418fe000 rw-s 00000000 00:07 1598 /dev/nvidia0 -7f78418fe000-7f7842200000 rw-p 00000000 00:00 0 -7f7846200000-7f7846201000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f7846201000-7f7846221000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f7846221000-7f7846241000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f7846241000-7f7846281000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f784629c000-7f784629e000 r--p 00000000 103:05 54040293 /opt/android-studio/jbr/lib/libextnet.so -7f784629e000-7f784629f000 r-xp 00001000 103:05 54040293 /opt/android-studio/jbr/lib/libextnet.so -7f784629f000-7f78462a0000 r--p 00001000 103:05 54040293 /opt/android-studio/jbr/lib/libextnet.so -7f78462a0000-7f78462a1000 rw-p 00001000 103:05 54040293 /opt/android-studio/jbr/lib/libextnet.so -7f78462a1000-7f78462a4000 r--p 00000000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 -7f78462a4000-7f78462b8000 r-xp 00003000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 -7f78462b8000-7f78462c2000 r--p 00017000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 -7f78462c2000-7f78462c3000 r--p 00020000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 -7f78462c3000-7f78462c4000 rw-p 00021000 103:05 67924792 /usr/lib/libnvidia-allocator.so.610.57.04 -7f78462c4000-7f78462c6000 rw-p 00000000 00:00 0 -7f78462c6000-7f78462cb000 r--p 00000000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 -7f78462cb000-7f78462d7000 r-xp 00005000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 -7f78462d7000-7f78462db000 r--p 00011000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 -7f78462db000-7f78462dc000 r--p 00015000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 -7f78462dc000-7f78462dd000 rw-p 00016000 103:05 67924770 /usr/lib/libdrm.so.2.134.0 -7f78462dd000-7f78462ed000 rw-s 00000000 00:07 1598 /dev/nvidia0 -7f78462ed000-7f78462f0000 r--p 00000000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 -7f78462f0000-7f78462f3000 r-xp 00003000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 -7f78462f3000-7f78462f4000 r--p 00006000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 -7f78462f4000-7f78462f5000 r--p 00007000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 -7f78462f5000-7f78462f6000 rw-p 00008000 103:05 67929201 /usr/lib/libxcb-xfixes.so.0.0.0 -7f78462f6000-7f78462f9000 r--p 00000000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 -7f78462f9000-7f78462fc000 r-xp 00003000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 -7f78462fc000-7f78462fd000 r--p 00006000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 -7f78462fd000-7f78462fe000 r--p 00007000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 -7f78462fe000-7f78462ff000 rw-p 00008000 103:05 67929195 /usr/lib/libxcb-sync.so.1.0.0 -7f78462ff000-7f7846b00000 rw-p 00000000 00:00 0 -7f7846b00000-7f7846b05000 r--p 00000000 103:05 67898329 /usr/lib/libmvec.so.1 -7f7846b05000-7f7846b27000 r-xp 00005000 103:05 67898329 /usr/lib/libmvec.so.1 -7f7846b27000-7f7846bfe000 r--p 00027000 103:05 67898329 /usr/lib/libmvec.so.1 -7f7846bfe000-7f7846bff000 r--p 000fe000 103:05 67898329 /usr/lib/libmvec.so.1 -7f7846bff000-7f7846c00000 rw-p 000ff000 103:05 67898329 /usr/lib/libmvec.so.1 -7f7846c00000-7f7846c03000 r--p 00000000 103:05 67984015 /usr/lib/libopus.so.0.11.1 -7f7846c03000-7f7846ccb000 r-xp 00003000 103:05 67984015 /usr/lib/libopus.so.0.11.1 -7f7846ccb000-7f784704a000 r--p 000cb000 103:05 67984015 /usr/lib/libopus.so.0.11.1 -7f784704a000-7f784704e000 r--p 0044a000 103:05 67984015 /usr/lib/libopus.so.0.11.1 -7f784704e000-7f784704f000 rw-p 0044e000 103:05 67984015 /usr/lib/libopus.so.0.11.1 -7f784704f000-7f7847053000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f7847053000-7f784705f000 r--p 00000000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 -7f784705f000-7f7847069000 r-xp 0000c000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 -7f7847069000-7f784706e000 r--p 00016000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 -7f784706e000-7f7847070000 r--p 0001b000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 -7f7847070000-7f7847071000 rw-p 0001d000 103:05 67929162 /usr/lib/libxcb-glx.so.0.0.0 -7f7847071000-7f7847081000 r--p 00000000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 -7f7847081000-7f78470e1000 r-xp 00010000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 -7f78470e1000-7f78470fe000 r--p 00070000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 -7f78470fe000-7f7847104000 r--p 0008c000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 -7f7847104000-7f7847105000 rw-p 00092000 103:05 67934204 /usr/lib/libnvidia-glsi.so.610.57.04 -7f7847105000-7f784710d000 rw-p 00000000 00:00 0 -7f784710d000-7f7847111000 r--p 00000000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 -7f7847111000-7f7847146000 r-xp 00004000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 -7f7847146000-7f7847158000 r--p 00039000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 -7f7847158000-7f7847159000 r--p 0004b000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 -7f7847159000-7f784715a000 rw-p 0004c000 103:05 67940972 /usr/lib/libvorbis.so.0.4.9 -7f784715a000-7f784715f000 r--p 00000000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 -7f784715f000-7f7847188000 r-xp 00005000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 -7f7847188000-7f7847195000 r--p 0002e000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 -7f7847195000-7f7847196000 r--p 0003b000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 -7f7847196000-7f7847197000 rw-p 0003c000 103:05 67985749 /usr/lib/libmp3lame.so.0.0.0 -7f7847197000-7f78471a8000 rw-p 00000000 00:00 0 -7f78471a8000-7f78471ac000 r--p 00000000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 -7f78471ac000-7f78471ea000 r-xp 00004000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 -7f78471ea000-7f7847205000 r--p 00042000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 -7f7847205000-7f7847206000 r--p 0005d000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 -7f7847206000-7f7847207000 rw-p 0005e000 103:05 67940998 /usr/lib/libmpg123.so.0.49.4 -7f7847207000-7f7847210000 r--p 00000000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 -7f7847210000-7f784726b000 r-xp 00009000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 -7f784726b000-7f7847274000 r--p 00064000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 -7f7847274000-7f7847275000 r--p 0006d000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 -7f7847275000-7f7847276000 rw-p 0006e000 103:05 67940956 /usr/lib/libFLAC.so.14.0.0 -7f7847276000-7f7847277000 r--p 00000000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 -7f7847277000-7f784727c000 r-xp 00001000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 -7f784727c000-7f78472f3000 r--p 00006000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 -7f78472f3000-7f784730f000 r--p 0007c000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 -7f784730f000-7f7847310000 rw-p 00098000 103:05 67940978 /usr/lib/libvorbisenc.so.2.0.12 -7f7847310000-7f7847315000 r--p 00000000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 -7f7847315000-7f78473b7000 r-xp 00005000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 -7f78473b7000-7f78473d4000 r--p 000a7000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 -7f78473d4000-7f78473d7000 r--p 000c4000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 -7f78473d7000-7f78473d8000 rw-p 000c7000 103:05 67941007 /usr/lib/libsndfile.so.1.0.37 -7f78473d8000-7f78473da000 rw-p 00000000 00:00 0 -7f78473da000-7f78477de000 rw-p 00000000 00:00 0 -7f78477de000-7f78477f0000 r--p 00000000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 -7f78477f0000-7f78478b6000 r-xp 00012000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 -7f78478b6000-7f78478f0000 r--p 000d8000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 -7f78478f0000-7f78478f6000 r--p 00111000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 -7f78478f6000-7f78478f7000 rw-p 00117000 103:05 67950495 /usr/lib/libvlccore.so.9.0.1 -7f78478f7000-7f78478f9000 rw-p 00000000 00:00 0 -7f78478f9000-7f7848000000 rw-p 00000000 00:00 0 -7f7848000000-7f784811b000 rw-p 00000000 00:00 0 -7f784811b000-7f784c000000 ---p 00000000 00:00 0 -7f784c000000-7f784c001000 r--p 00000000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 -7f784c001000-7f784c002000 r-xp 00001000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 -7f784c002000-7f784c003000 r--p 00002000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 -7f784c003000-7f784c004000 r--p 00002000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 -7f784c004000-7f784c005000 rw-p 00003000 103:05 67929166 /usr/lib/libxcb-present.so.0.0.0 -7f784c005000-7f784c006000 r--p 00000000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 -7f784c006000-7f784c007000 r-xp 00001000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 -7f784c007000-7f784c008000 r--p 00002000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 -7f784c008000-7f784c009000 r--p 00002000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 -7f784c009000-7f784c00a000 rw-p 00003000 103:05 67931557 /usr/lib/libX11-xcb.so.1.0.0 -7f784c00a000-7f784c01e000 r--p 00000000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so -7f784c01e000-7f784c070000 r-xp 00014000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so -7f784c070000-7f784c096000 r--p 00066000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so -7f784c096000-7f784c098000 r--p 0008c000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so -7f784c098000-7f784c099000 rw-p 0008e000 103:05 68194041 /usr/lib/pulseaudio/libpulsecommon-17.0.so -7f784c099000-7f784c0a5000 r--p 00000000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 -7f784c0a5000-7f784c0dc000 r-xp 0000c000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 -7f784c0dc000-7f784c0f2000 r--p 00043000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 -7f784c0f2000-7f784c0f4000 r--p 00059000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 -7f784c0f4000-7f784c0f5000 rw-p 0005b000 103:05 67924987 /usr/lib/libpulse.so.0.24.3 -7f784c0f5000-7f784cb00000 rw-p 00000000 00:00 0 -7f784cb00000-7f784cb04000 ---p 00000000 00:00 0 -7f784cb04000-7f784cc00000 rw-p 00000000 00:00 0 -7f784cc00000-7f784cc1e000 r-xp 00000000 103:05 96629338 /home/mey/.cache/JNA/temp/jna12348363668190973279.tmp (deleted) -7f784cc1e000-7f784ce1d000 ---p 0001e000 103:05 96629338 /home/mey/.cache/JNA/temp/jna12348363668190973279.tmp (deleted) -7f784ce1d000-7f784ce1e000 rw-p 0001d000 103:05 96629338 /home/mey/.cache/JNA/temp/jna12348363668190973279.tmp (deleted) -7f784ce1e000-7f784ce1f000 rw-p 00000000 00:00 0 -7f784ce1f000-7f784ce20000 rw-s 00000000 00:07 1598 /dev/nvidia0 -7f784ce20000-7f784ce22000 r--p 00000000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 -7f784ce22000-7f784ce24000 r-xp 00002000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 -7f784ce24000-7f784ce25000 r--p 00004000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 -7f784ce25000-7f784ce26000 r--p 00004000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 -7f784ce26000-7f784ce27000 rw-p 00005000 103:05 67929157 /usr/lib/libxcb-dri3.so.0.1.0 -7f784ce27000-7f784ce2e000 r--p 00000000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 -7f784ce2e000-7f784ce34000 r-xp 00007000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 -7f784ce34000-7f784ce37000 r--p 0000d000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 -7f784ce37000-7f784ce38000 r--p 00010000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 -7f784ce38000-7f784ce39000 rw-p 00011000 103:05 67929169 /usr/lib/libxcb-randr.so.0.1.0 -7f784ce39000-7f784ce3a000 r--p 00000000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 -7f784ce3a000-7f784ce3c000 r-xp 00001000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 -7f784ce3c000-7f784ce3d000 r--p 00003000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 -7f784ce3d000-7f784ce3e000 r--p 00003000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 -7f784ce3e000-7f784ce3f000 rw-p 00004000 103:05 67945516 /usr/lib/libnvidia-tls.so.610.57.04 -7f784ce3f000-7f784ce46000 r--s 00000000 103:05 67988359 /usr/lib/gconv/gconv-modules.cache -7f784ce46000-7f784ce48000 r--p 00000000 103:05 67940946 /usr/lib/libogg.so.0.8.6 -7f784ce48000-7f784ce4c000 r-xp 00002000 103:05 67940946 /usr/lib/libogg.so.0.8.6 -7f784ce4c000-7f784ce4f000 r--p 00006000 103:05 67940946 /usr/lib/libogg.so.0.8.6 -7f784ce4f000-7f784ce50000 r--p 00009000 103:05 67940946 /usr/lib/libogg.so.0.8.6 -7f784ce50000-7f784ce51000 rw-p 0000a000 103:05 67940946 /usr/lib/libogg.so.0.8.6 -7f784cee7000-7f784cee8000 rw-p 00000000 00:00 0 -7f784cee8000-7f784ceec000 ---p 00000000 00:00 0 -7f784ceec000-7f784cfe9000 rw-p 00000000 00:00 0 -7f784cfe9000-7f784cfed000 ---p 00000000 00:00 0 -7f784cfed000-7f784d0ea000 rw-p 00000000 00:00 0 -7f784d0ea000-7f784d0ee000 ---p 00000000 00:00 0 -7f784d0ee000-7f784d1eb000 rw-p 00000000 00:00 0 -7f784d1eb000-7f784d1ef000 ---p 00000000 00:00 0 -7f784d1ef000-7f784d2ec000 rw-p 00000000 00:00 0 -7f784d2ec000-7f784d2f0000 ---p 00000000 00:00 0 -7f784d2f0000-7f784d3ed000 rw-p 00000000 00:00 0 -7f784d3ed000-7f784d3f1000 ---p 00000000 00:00 0 -7f784d3f1000-7f784d4ee000 rw-p 00000000 00:00 0 -7f784d4ee000-7f784d4f2000 ---p 00000000 00:00 0 -7f784d4f2000-7f784d5ef000 rw-p 00000000 00:00 0 -7f784d5ef000-7f784d5f3000 ---p 00000000 00:00 0 -7f784d5f3000-7f784d6f0000 rw-p 00000000 00:00 0 -7f784d6f0000-7f784d6f4000 ---p 00000000 00:00 0 -7f784d6f4000-7f784d7f0000 rw-p 00000000 00:00 0 -7f784d7f0000-7f784d8e5000 r--s 00000000 103:05 3171936 /var/cache/fontconfig/6d006ae94db972f3bc99bc2999c017b0-le64.cache-12 -7f784d8e5000-7f784d9fe000 r--s 00000000 103:05 3171934 /var/cache/fontconfig/923e285e415b1073c8df160bee08820f-le64.cache-12 -7f784d9fe000-7f784d9ff000 rw-p 00000000 00:00 0 -7f784d9ff000-7f784da03000 ---p 00000000 00:00 0 -7f784da03000-7f784db00000 rw-p 00000000 00:00 0 -7f784db00000-7f784db04000 ---p 00000000 00:00 0 -7f784db04000-7f784dc00000 rw-p 00000000 00:00 0 -7f784dc00000-7f784fb94000 r--p 00000000 103:05 67913512 /usr/lib/libicudata.so.78.3 -7f784fb94000-7f784fb95000 r--p 01f93000 103:05 67913512 /usr/lib/libicudata.so.78.3 -7f784fb95000-7f784fb96000 r--p 00000000 103:05 54040303 /opt/android-studio/jbr/lib/libjawt.so -7f784fb96000-7f784fb97000 r-xp 00000000 103:05 54040303 /opt/android-studio/jbr/lib/libjawt.so -7f784fb97000-7f784fb98000 r--p 00000000 103:05 54040303 /opt/android-studio/jbr/lib/libjawt.so -7f784fb98000-7f784fb99000 rw-p 00000000 103:05 54040303 /opt/android-studio/jbr/lib/libjawt.so -7f784fb99000-7f784fb9b000 r--p 00000000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 -7f784fb9b000-7f784fb9e000 r-xp 00002000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 -7f784fb9e000-7f784fb9f000 r--p 00005000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 -7f784fb9f000-7f784fba0000 r--p 00005000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 -7f784fba0000-7f784fba1000 rw-p 00006000 103:05 67941630 /usr/lib/libasyncns.so.0.3.1 -7f784fbb4000-7f784fbbd000 r--p 00000000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 -7f784fbbd000-7f784fbd4000 r-xp 00009000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 -7f784fbd4000-7f784fbdb000 r--p 00020000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 -7f784fbdb000-7f784fbdc000 r--p 00027000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 -7f784fbdc000-7f784fbdd000 rw-p 00028000 103:05 67950492 /usr/lib/libvlc.so.5.6.1 -7f784fbdd000-7f784fbde000 rw-p 00000000 00:00 0 -7f784fbde000-7f784fbe2000 ---p 00000000 00:00 0 -7f784fbe2000-7f784fc00000 rw-p 00000000 00:00 0 -7f784fc00000-7f784fc48000 r--p 00000000 103:05 67913537 /usr/lib/libicuuc.so.78.3 -7f784fc48000-7f784fda7000 r-xp 00048000 103:05 67913537 /usr/lib/libicuuc.so.78.3 -7f784fda7000-7f784fe43000 r--p 001a7000 103:05 67913537 /usr/lib/libicuuc.so.78.3 -7f784fe43000-7f784fe56000 r--p 00243000 103:05 67913537 /usr/lib/libicuuc.so.78.3 -7f784fe56000-7f784fe57000 rw-p 00256000 103:05 67913537 /usr/lib/libicuuc.so.78.3 -7f784fe57000-7f784fe59000 rw-p 00000000 00:00 0 -7f784fe59000-7f784fe5a000 r--p 00000000 103:05 54040300 /opt/android-studio/jbr/lib/libjaas.so -7f784fe5a000-7f784fe5b000 r-xp 00000000 103:05 54040300 /opt/android-studio/jbr/lib/libjaas.so -7f784fe5b000-7f784fe5c000 r--p 00000000 103:05 54040300 /opt/android-studio/jbr/lib/libjaas.so -7f784fe5c000-7f784fe5d000 rw-p 00000000 103:05 54040300 /opt/android-studio/jbr/lib/libjaas.so -7f784fe67000-7f784fe71000 r--p 00000000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 -7f784fe71000-7f784ffc0000 r-xp 0000a000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 -7f784ffc0000-7f784fff7000 r--p 00159000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 -7f784fff7000-7f784fffb000 r--p 00190000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 -7f784fffb000-7f7850000000 rw-p 00194000 103:05 67910702 /usr/lib/libsqlite3.so.3.53.4 -7f7850000000-7f78501d3000 rw-p 00000000 00:00 0 -7f78501d3000-7f7854000000 ---p 00000000 00:00 0 -7f7854000000-7f7854001000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f7854001000-7f7854002000 rw-s 00000000 00:07 1596 /dev/nvidiactl -7f785400c000-7f785401c000 r--s 00000000 103:05 3171928 /var/cache/fontconfig/491f45a0a771fef1c10b9b647a97fb82-le64.cache-12 -7f785401c000-7f78540ca000 r--s 00000000 103:05 3171352 /var/cache/fontconfig/f6b893a7224233d96cb72fd88691c0b4-le64.cache-12 -7f78540ca000-7f78540e4000 r--p 00000000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 -7f78540e4000-7f78541b8000 r-xp 0001a000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 -7f78541b8000-7f78541f5000 r--p 000ee000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 -7f78541f5000-7f78541fe000 r--p 0012b000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 -7f78541fe000-7f78541ff000 rw-p 00134000 103:05 67945305 /usr/lib/libxml2.so.16.1.3 -7f78541ff000-7f7854200000 rw-p 00000000 00:00 0 -7f7854200000-7f78542c3000 r--p 00000000 103:05 67983832 /usr/lib/libglycin-2.so.0 -7f78542c3000-7f78545e0000 r-xp 000c2000 103:05 67983832 /usr/lib/libglycin-2.so.0 -7f78545e0000-7f78545f9000 r--p 003de000 103:05 67983832 /usr/lib/libglycin-2.so.0 -7f78545f9000-7f78545fd000 rw-p 003f6000 103:05 67983832 /usr/lib/libglycin-2.so.0 -7f78545fd000-7f78545fe000 r--s 00000000 00:07 1596 /dev/nvidiactl -7f78545fe000-7f78545ff000 rw-p 00000000 00:00 0 -7f78545ff000-7f7854605000 r--s 00000000 103:05 3171931 /var/cache/fontconfig/b67b32625a2bb51b023d3814a918f351-le64.cache-12 -7f7854605000-7f7854613000 r--s 00000000 103:05 3171920 /var/cache/fontconfig/210c0516121708a580e22e6b1f9a103a-le64.cache-12 -7f7854613000-7f785461f000 r--s 00000000 103:05 3171347 /var/cache/fontconfig/4b31aef11cb0687b6d280d34cbe23592-le64.cache-12 -7f785461f000-7f7854621000 r--p 00000000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 -7f7854621000-7f7854631000 r-xp 00002000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 -7f7854631000-7f785464a000 r--p 00012000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 -7f785464a000-7f785464b000 r--p 0002b000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 -7f785464b000-7f785464c000 rw-p 0002c000 103:05 67912424 /usr/lib/libseccomp.so.2.6.0 -7f785464c000-7f7854655000 r--p 00000000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 -7f7854655000-7f785469c000 r-xp 00009000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 -7f785469c000-7f78546b1000 r--p 00050000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 -7f78546b1000-7f78546b4000 r--p 00064000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 -7f78546b4000-7f78546b7000 rw-p 00067000 103:05 67934479 /usr/lib/liblcms2.so.2.0.19 -7f78546b7000-7f78546b8000 rw-p 00000000 00:00 0 -7f78546b8000-7f78546ba000 r--p 00000000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 -7f78546ba000-7f78546be000 r-xp 00002000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 -7f78546be000-7f78546bf000 r--p 00006000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 -7f78546bf000-7f78546c0000 r--p 00006000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 -7f78546c0000-7f78546c1000 rw-p 00007000 103:05 67933617 /usr/lib/libdatrie.so.1.4.1 -7f78546c1000-7f78546ca000 r--p 00000000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 -7f78546ca000-7f78546e3000 r-xp 00009000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 -7f78546e3000-7f78546ec000 r--p 00022000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 -7f78546ec000-7f78546ed000 r--p 0002b000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 -7f78546ed000-7f78546ee000 rw-p 0002c000 103:05 67924902 /usr/lib/libjson-glib-1.0.so.0.1000.8 -7f78546ee000-7f78546fc000 r--p 00000000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 -7f78546fc000-7f7854734000 r-xp 0000e000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 -7f7854734000-7f7854746000 r--p 00046000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 -7f7854746000-7f7854748000 r--p 00057000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 -7f7854748000-7f7854749000 rw-p 00059000 103:05 67919023 /usr/lib/libdbus-1.so.3.38.3 -7f7854749000-7f7854757000 r--p 00000000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 -7f7854757000-7f7854772000 r-xp 0000e000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 -7f7854772000-7f7854780000 r--p 00029000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 -7f7854780000-7f7854784000 r--p 00037000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 -7f7854784000-7f7854785000 rw-p 0003b000 103:05 67937996 /usr/lib/libatspi.so.0.0.1 -7f7854785000-7f7854787000 r--p 00000000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 -7f7854787000-7f78547a0000 r-xp 00002000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 -7f78547a0000-7f78547a5000 r--p 0001b000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 -7f78547a5000-7f78547a7000 r--p 0001f000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 -7f78547a7000-7f78547a8000 rw-p 00021000 103:05 67985487 /usr/lib/libgraphite2.so.3.3.1 -7f78547a8000-7f78547ad000 r--p 00000000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 -7f78547ad000-7f785491d000 r-xp 00005000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 -7f785491d000-7f7854932000 r--p 00175000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 -7f7854932000-7f785493a000 r--p 0018a000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 -7f785493a000-7f785493b000 rw-p 00192000 103:05 67983575 /usr/lib/libpixman-1.so.0.46.4 -7f785493b000-7f785493c000 r--p 00000000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 -7f785493c000-7f785493d000 r-xp 00001000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 -7f785493d000-7f785493e000 r--p 00002000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 -7f785493e000-7f785493f000 r--p 00002000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 -7f785493f000-7f7854940000 rw-p 00003000 103:05 67929192 /usr/lib/libxcb-shm.so.0.0.0 -7f7854940000-7f7854946000 r--p 00000000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 -7f7854946000-7f785494c000 r-xp 00006000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 -7f785494c000-7f785494e000 r--p 0000c000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 -7f785494e000-7f785494f000 r--p 0000e000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 -7f785494f000-7f7854950000 rw-p 0000f000 103:05 67929177 /usr/lib/libxcb-render.so.0.0.0 -7f7854950000-7f7854952000 r--p 00000000 103:05 67983934 /usr/lib/libthai.so.0.3.2 -7f7854952000-7f7854958000 r-xp 00002000 103:05 67983934 /usr/lib/libthai.so.0.3.2 -7f7854958000-7f785495b000 r--p 00008000 103:05 67983934 /usr/lib/libthai.so.0.3.2 -7f785495b000-7f785495c000 r--p 0000a000 103:05 67983934 /usr/lib/libthai.so.0.3.2 -7f785495c000-7f785495d000 rw-p 0000b000 103:05 67983934 /usr/lib/libthai.so.0.3.2 -7f785495d000-7f785495f000 r--p 00000000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 -7f785495f000-7f7854969000 r-xp 00002000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 -7f7854969000-7f785496b000 r--p 0000c000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 -7f785496b000-7f785496c000 r--p 0000d000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 -7f785496c000-7f785496d000 rw-p 0000e000 103:05 67934164 /usr/lib/libXrandr.so.2.2.0 -7f785496d000-7f785496e000 r--p 00000000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 -7f785496e000-7f785496f000 r-xp 00001000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 -7f785496f000-7f7854970000 r--p 00002000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 -7f7854970000-7f7854971000 r--p 00002000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 -7f7854971000-7f7854972000 rw-p 00003000 103:05 67933907 /usr/lib/libXcomposite.so.1.0.0 -7f7854972000-7f7854973000 r--p 00000000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 -7f7854973000-7f7854974000 r-xp 00001000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 -7f7854974000-7f7854975000 r--p 00002000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 -7f7854975000-7f7854976000 r--p 00002000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 -7f7854976000-7f7854977000 rw-p 00003000 103:05 67939152 /usr/lib/libXdamage.so.1.1.0 -7f7854977000-7f785497b000 r--p 00000000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 -7f785497b000-7f78549aa000 r-xp 00004000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 -7f78549aa000-7f78549d3000 r--p 00033000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 -7f78549d3000-7f78549d5000 r--p 0005b000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 -7f78549d5000-7f78549d6000 rw-p 0005d000 103:05 67934046 /usr/lib/libxkbcommon.so.0.13.2 -7f78549d6000-7f78549e6000 r--p 00000000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 -7f78549e6000-7f7854a65000 r-xp 00010000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 -7f7854a65000-7f7854a9a000 r--p 0008f000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 -7f7854a9a000-7f7854aa9000 r--p 000c3000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 -7f7854aa9000-7f7854aaa000 rw-p 000d2000 103:05 67925313 /usr/lib/libtinysparql-3.0.so.0.1101.0 -7f7854aaa000-7f7854ab1000 r--p 00000000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 -7f7854ab1000-7f7854abb000 r-xp 00007000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 -7f7854abb000-7f7854ac0000 r--p 00011000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 -7f7854ac0000-7f7854ac1000 r--p 00016000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 -7f7854ac1000-7f7854ac2000 rw-p 00017000 103:05 67938995 /usr/lib/libcloudproviders.so.0.4.0 -7f7854ac2000-7f7854acd000 r--p 00000000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 -7f7854acd000-7f7854ae6000 r-xp 0000b000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 -7f7854ae6000-7f7854afa000 r--p 00024000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 -7f7854afa000-7f7854afc000 r--p 00037000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 -7f7854afc000-7f7854afe000 rw-p 00039000 103:05 67937991 /usr/lib/libatk-bridge-2.0.so.0.0.0 -7f7854afe000-7f7854b48000 r--p 00000000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 -7f7854b48000-7f7854bb3000 r-xp 0004a000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 -7f7854bb3000-7f7854c10000 r--p 000b5000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 -7f7854c10000-7f7854c18000 r--p 00112000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 -7f7854c18000-7f7854c1f000 rw-p 0011a000 103:05 67934196 /usr/lib/libepoxy.so.0.0.0 -7f7854c1f000-7f7854c27000 r--p 00000000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 -7f7854c27000-7f7854c37000 r-xp 00008000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 -7f7854c37000-7f7854c42000 r--p 00018000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 -7f7854c42000-7f7854c45000 r--p 00023000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 -7f7854c45000-7f7854c46000 rw-p 00026000 103:05 67937986 /usr/lib/libatk-1.0.so.0.26015.1 -7f7854c46000-7f7854c4d000 r--p 00000000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 -7f7854c4d000-7f7854c6f000 r-xp 00007000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 -7f7854c6f000-7f7854c7d000 r--p 00029000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 -7f7854c7d000-7f7854c7f000 r--p 00036000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 -7f7854c7f000-7f7854c80000 rw-p 00038000 103:05 67933473 /usr/lib/libgdk_pixbuf-2.0.so.0.4400.7 -7f7854c80000-7f7854c82000 r--p 00000000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 -7f7854c82000-7f7854c8a000 r-xp 00002000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 -7f7854c8a000-7f7854ca1000 r--p 0000a000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 -7f7854ca1000-7f7854ca2000 r--p 00021000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 -7f7854ca2000-7f7854ca3000 rw-p 00022000 103:05 67933580 /usr/lib/libfribidi.so.0.4.0 -7f7854ca3000-7f7854cab000 r--p 00000000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 -7f7854cab000-7f7854cbb000 r-xp 00008000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 -7f7854cbb000-7f7854cc0000 r--p 00018000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 -7f7854cc0000-7f7854cc1000 r--p 0001d000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 -7f7854cc1000-7f7854cc2000 rw-p 0001e000 103:05 67976860 /usr/lib/libpangoft2-1.0.so.0.5800.2 -7f7854cc2000-7f7854ccf000 r--p 00000000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 -7f7854ccf000-7f7854dc1000 r-xp 0000d000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 -7f7854dc1000-7f7854df5000 r--p 000ff000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 -7f7854df5000-7f7854df7000 r--p 00132000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 -7f7854df7000-7f7854df8000 rw-p 00134000 103:05 67986003 /usr/lib/libharfbuzz.so.0.61430.0 -7f7854df8000-7f7854e06000 r--p 00000000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 -7f7854e06000-7f7854f4c000 r-xp 0000e000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 -7f7854f4c000-7f7854f8a000 r--p 00154000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 -7f7854f8a000-7f7854f8e000 r--p 00192000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 -7f7854f8e000-7f7854f90000 rw-p 00196000 103:05 67933134 /usr/lib/libcairo.so.2.11804.4 -7f7854f90000-7f7854f91000 rw-p 00000000 00:00 0 -7f7854f91000-7f7854f9f000 r--p 00000000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 -7f7854f9f000-7f7854fdf000 r-xp 0000e000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 -7f7854fdf000-7f7854ffb000 r--p 0004e000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 -7f7854ffb000-7f7854fff000 r--p 00069000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 -7f7854fff000-7f7855000000 rw-p 0006d000 103:05 67933738 /usr/lib/libpango-1.0.so.0.5800.2 -7f7855000000-7f7855070000 r--p 00000000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 -7f7855070000-7f7855427000 r-xp 00070000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 -7f7855427000-7f78556e5000 r--p 00427000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 -7f78556e5000-7f78556f7000 r--p 006e5000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 -7f78556f7000-7f78556f9000 rw-p 006f7000 103:05 67939525 /usr/lib/libgtk-3.so.0.2420.32 -7f78556f9000-7f78556fe000 rw-p 00000000 00:00 0 -7f78556fe000-7f78556ff000 r-xp 00000000 00:2f 21007 /tmp/ffi7UxbMI (deleted) -7f78556ff000-7f7855700000 rw-p 00000000 00:00 0 -7f7855700000-7f7855701000 r--p 00000000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 -7f7855701000-7f7855702000 r-xp 00001000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 -7f7855702000-7f7855703000 r--p 00002000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 -7f7855703000-7f7855704000 r--p 00002000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 -7f7855704000-7f7855705000 rw-p 00003000 103:05 67982373 /usr/lib/libwayland-egl.so.1.26.0 -7f7855705000-7f7855708000 r--p 00000000 103:05 67934087 /usr/lib/libXi.so.6.1.0 -7f7855708000-7f7855715000 r-xp 00003000 103:05 67934087 /usr/lib/libXi.so.6.1.0 -7f7855715000-7f7855718000 r--p 00010000 103:05 67934087 /usr/lib/libXi.so.6.1.0 -7f7855718000-7f7855719000 r--p 00012000 103:05 67934087 /usr/lib/libXi.so.6.1.0 -7f7855719000-7f785571a000 rw-p 00013000 103:05 67934087 /usr/lib/libXi.so.6.1.0 -7f785571a000-7f7855737000 r--p 00000000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 -7f7855737000-7f78557b9000 r-xp 0001d000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 -7f78557b9000-7f78557fb000 r--p 0009f000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 -7f78557fb000-7f7855805000 r--p 000e0000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 -7f7855805000-7f7855807000 rw-p 000ea000 103:05 67939520 /usr/lib/libgdk-3.so.0.2420.32 -7f7855807000-7f7855808000 rw-p 00000000 00:00 0 -7f7855808000-7f785580c000 ---p 00000000 00:00 0 -7f785580c000-7f7855908000 rw-p 00000000 00:00 0 -7f7855908000-7f785591d000 r--p 00000000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 -7f785591d000-7f78559f6000 r-xp 00015000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 -7f78559f6000-7f7855a45000 r--p 000ee000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 -7f7855a45000-7f7855a4e000 r--p 0013c000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 -7f7855a4e000-7f7855a4f000 rw-p 00145000 103:05 67899181 /usr/lib/libsystemd.so.0.44.0 -7f7855a4f000-7f7855a50000 rw-p 00000000 00:00 0 -7f7855a50000-7f7855a55000 r--p 00000000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 -7f7855a55000-7f7855a7e000 r-xp 00005000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 -7f7855a7e000-7f7855a89000 r--p 0002e000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 -7f7855a89000-7f7855a8f000 r--p 00038000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 -7f7855a8f000-7f7855a90000 rw-p 0003e000 103:05 67902781 /usr/lib/libblkid.so.1.1.0 -7f7855a90000-7f7855a93000 r--p 00000000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 -7f7855a93000-7f7855b2a000 r-xp 00003000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 -7f7855b2a000-7f7855b59000 r--p 0009a000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 -7f7855b59000-7f7855b5a000 r--p 000c8000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 -7f7855b5a000-7f7855b5b000 rw-p 000c9000 103:05 67912664 /usr/lib/libpcre2-8.so.0.15.0 -7f7855b5b000-7f7855b65000 r--p 00000000 103:05 67902806 /usr/lib/libmount.so.1.1.0 -7f7855b65000-7f7855ba3000 r-xp 0000a000 103:05 67902806 /usr/lib/libmount.so.1.1.0 -7f7855ba3000-7f7855bb4000 r--p 00048000 103:05 67902806 /usr/lib/libmount.so.1.1.0 -7f7855bb4000-7f7855bb6000 r--p 00059000 103:05 67902806 /usr/lib/libmount.so.1.1.0 -7f7855bb6000-7f7855bb7000 rw-p 0005b000 103:05 67902806 /usr/lib/libmount.so.1.1.0 -7f7855bb7000-7f7855bc3000 r--p 00000000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 -7f7855bc3000-7f7855bfc000 r-xp 0000c000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 -7f7855bfc000-7f7855c16000 r--p 00045000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 -7f7855c16000-7f7855c19000 r--p 0005e000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 -7f7855c19000-7f7855c1a000 rw-p 00061000 103:05 67918010 /usr/lib/libgobject-2.0.so.0.8800.3 -7f7855c1a000-7f7855c38000 r--p 00000000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 -7f7855c38000-7f7855ce6000 r-xp 0001e000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 -7f7855ce6000-7f7855d7a000 r--p 000cc000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 -7f7855d7a000-7f7855d7b000 r--p 00160000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 -7f7855d7b000-7f7855d7c000 rw-p 00161000 103:05 67917992 /usr/lib/libglib-2.0.so.0.8800.3 -7f7855d7c000-7f7855d7d000 rw-p 00000000 00:00 0 -7f7855d7d000-7f7855daf000 r--p 00000000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 -7f7855daf000-7f7855ed6000 r-xp 00032000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 -7f7855ed6000-7f7855f50000 r--p 00159000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 -7f7855f50000-7f7855f59000 r--p 001d2000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 -7f7855f59000-7f7855f5a000 rw-p 001db000 103:05 67917947 /usr/lib/libgio-2.0.so.0.8800.3 -7f7855f5a000-7f7855f5c000 rw-p 00000000 00:00 0 -7f7855f5c000-7f7855fa4000 r--p 00000000 103:05 54040294 /opt/android-studio/jbr/lib/libfontmanager.so -7f7855fa4000-7f78560a6000 r-xp 00047000 103:05 54040294 /opt/android-studio/jbr/lib/libfontmanager.so -7f78560a6000-7f78560a8000 r--p 00148000 103:05 54040294 /opt/android-studio/jbr/lib/libfontmanager.so -7f78560a8000-7f78560aa000 rw-p 00149000 103:05 54040294 /opt/android-studio/jbr/lib/libfontmanager.so -7f78560aa000-7f78560ab000 rw-p 00000000 00:00 0 -7f78560ab000-7f78560d9000 r--p 00000000 103:05 54040291 /opt/android-studio/jbr/lib/libawt_xawt.so -7f78560d9000-7f7856121000 r-xp 0002d000 103:05 54040291 /opt/android-studio/jbr/lib/libawt_xawt.so -7f7856121000-7f7856126000 r--p 00074000 103:05 54040291 /opt/android-studio/jbr/lib/libawt_xawt.so -7f7856126000-7f785612a000 rw-p 00078000 103:05 54040291 /opt/android-studio/jbr/lib/libawt_xawt.so -7f785612a000-7f785612b000 rw-p 00000000 00:00 0 -7f785612b000-7f7856156000 r--p 00000000 103:05 54040288 /opt/android-studio/jbr/lib/libawt.so -7f7856156000-7f78561ce000 r-xp 0002a000 103:05 54040288 /opt/android-studio/jbr/lib/libawt.so -7f78561ce000-7f78561cf000 r--p 000a1000 103:05 54040288 /opt/android-studio/jbr/lib/libawt.so -7f78561cf000-7f78561db000 rw-p 000a1000 103:05 54040288 /opt/android-studio/jbr/lib/libawt.so -7f78561db000-7f7856200000 rw-p 00000000 00:00 0 -7f7856200000-7f785629a000 r--p 00000000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 -7f785629a000-7f785641d000 r-xp 0009a000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 -7f785641d000-7f78564bd000 r--p 0021d000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 -7f78564bd000-7f78564ce000 r--p 002bc000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 -7f78564ce000-7f78564cf000 rw-p 002cd000 103:05 67980038 /usr/lib/libstdc++.so.6.0.36 -7f78564cf000-7f78564d3000 rw-p 00000000 00:00 0 -7f78564d3000-7f78564d6000 r--s 00000000 103:05 3171923 /var/cache/fontconfig/6ba42ae0000f58711b5caaf10d690066-le64.cache-12 -7f78564d6000-7f78564d7000 r--p 00000000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 -7f78564d7000-7f78564d9000 r-xp 00001000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 -7f78564d9000-7f78564da000 r--p 00003000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 -7f78564da000-7f78564db000 r--p 00003000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 -7f78564db000-7f78564df000 rw-p 00004000 103:05 67982367 /usr/lib/libwayland-cursor.so.0.26.0 -7f78564df000-7f78564e4000 r--p 00000000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 -7f78564e4000-7f78564ed000 r-xp 00005000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 -7f78564ed000-7f78564ef000 r--p 0000e000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 -7f78564ef000-7f78564f0000 r--p 00010000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 -7f78564f0000-7f78564f1000 rw-p 00011000 103:05 67974855 /usr/lib/libpangocairo-1.0.so.0.5800.2 -7f78564f1000-7f78564f4000 r--p 00000000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 -7f78564f4000-7f78564fb000 r-xp 00003000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 -7f78564fb000-7f78564fd000 r--p 0000a000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 -7f78564fd000-7f78564fe000 r--p 0000c000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 -7f78564fe000-7f78564ff000 rw-p 0000d000 103:05 67933923 /usr/lib/libXcursor.so.1.0.2 -7f78564ff000-7f7856500000 rw-p 00000000 00:00 0 -7f7856500000-7f7856504000 ---p 00000000 00:00 0 -7f7856504000-7f7856600000 rw-p 00000000 00:00 0 -7f7856600000-7f7857f1a000 r-xp 00000000 103:05 98306206 /home/mey/.skiko/skiko-linux-x64-434f7633c843870c0839a1137b128f6c3db3c5d58dcad3545e90827e3e341afb/libskiko-linux-x64.so -7f7857f1a000-7f7857f52000 r--p 01919000 103:05 98306206 /home/mey/.skiko/skiko-linux-x64-434f7633c843870c0839a1137b128f6c3db3c5d58dcad3545e90827e3e341afb/libskiko-linux-x64.so -7f7857f52000-7f7857f55000 rw-p 01951000 103:05 98306206 /home/mey/.skiko/skiko-linux-x64-434f7633c843870c0839a1137b128f6c3db3c5d58dcad3545e90827e3e341afb/libskiko-linux-x64.so -7f7857f55000-7f7857f5f000 rw-p 00000000 00:00 0 -7f7857f5f000-7f7857f62000 r--s 00000000 103:05 3171350 /var/cache/fontconfig/f349e9996a5320f6dd491cedd2b1f964-le64.cache-12 -7f7857f62000-7f7857f64000 r--p 00000000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 -7f7857f64000-7f7857f66000 r-xp 00002000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 -7f7857f66000-7f7857f68000 r--p 00004000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 -7f7857f68000-7f7857f6a000 r--p 00006000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 -7f7857f6a000-7f7857f6b000 rw-p 00008000 103:05 67933126 /usr/lib/libcairo-gobject.so.2.11804.4 -7f7857f6b000-7f7857f86000 r--p 00000000 103:05 67919883 /usr/share/glib-2.0/schemas/gschemas.compiled -7f7857f86000-7f7857f8a000 r--p 00000000 103:05 67932369 /usr/lib/libXext.so.6.4.0 -7f7857f8a000-7f7857f9a000 r-xp 00004000 103:05 67932369 /usr/lib/libXext.so.6.4.0 -7f7857f9a000-7f7857f9e000 r--p 00014000 103:05 67932369 /usr/lib/libXext.so.6.4.0 -7f7857f9e000-7f7857f9f000 r--p 00017000 103:05 67932369 /usr/lib/libXext.so.6.4.0 -7f7857f9f000-7f7857fa0000 rw-p 00018000 103:05 67932369 /usr/lib/libXext.so.6.4.0 -7f7857fa0000-7f7857fa1000 r--p 00000000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 -7f7857fa1000-7f7857fa2000 r-xp 00001000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 -7f7857fa2000-7f7857fc1000 r--p 00002000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 -7f7857fc1000-7f7857fc2000 r--p 00021000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 -7f7857fc2000-7f7857fc3000 rw-p 00022000 103:05 67915281 /usr/lib/libbrotlicommon.so.1.2.0 -7f7857fc3000-7f7857fc9000 r--p 00000000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 -7f7857fc9000-7f7857ff4000 r-xp 00006000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 -7f7857ff4000-7f7857ffe000 r--p 00031000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 -7f7857ffe000-7f7857fff000 r--p 0003a000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 -7f7857fff000-7f7858000000 rw-p 0003b000 103:05 67949569 /usr/lib/libpng16.so.16.58.0 -7f7858000000-7f7858ffc000 rw-p 00000000 00:00 0 -7f7858ffc000-7f785c000000 ---p 00000000 00:00 0 -7f785c000000-7f785d1fd000 rw-p 00000000 00:00 0 -7f785d1fd000-7f7860000000 ---p 00000000 00:00 0 -7f7860000000-7f7860021000 rw-p 00000000 00:00 0 -7f7860021000-7f7864000000 ---p 00000000 00:00 0 -7f7864000000-7f78651c4000 rw-p 00000000 00:00 0 -7f78651c4000-7f7868000000 ---p 00000000 00:00 0 -7f7868000000-7f786a381000 rw-p 00000000 00:00 0 -7f786a381000-7f786c000000 ---p 00000000 00:00 0 -7f786c000000-7f786ca91000 rw-p 00000000 00:00 0 -7f786ca91000-7f7870000000 ---p 00000000 00:00 0 -7f7870000000-7f787065c000 rw-p 00000000 00:00 0 -7f787065c000-7f7874000000 ---p 00000000 00:00 0 -7f7874000000-7f7875ffe000 rw-p 00000000 00:00 0 -7f7875ffe000-7f7878000000 ---p 00000000 00:00 0 -7f7878000000-7f7878057000 rw-p 00000000 00:00 0 -7f7878057000-7f787c000000 ---p 00000000 00:00 0 -7f787c000000-7f787c08e000 rw-p 00000000 00:00 0 -7f787c08e000-7f7880000000 ---p 00000000 00:00 0 -7f7880000000-7f7880027000 rw-p 00000000 00:00 0 -7f7880027000-7f7884000000 ---p 00000000 00:00 0 -7f7884000000-7f788414a000 rw-p 00000000 00:00 0 -7f788414a000-7f7888000000 ---p 00000000 00:00 0 -7f7888000000-7f788827d000 rw-p 00000000 00:00 0 -7f788827d000-7f788c000000 ---p 00000000 00:00 0 -7f788c000000-7f788c021000 rw-p 00000000 00:00 0 -7f788c021000-7f7890000000 ---p 00000000 00:00 0 -7f7890000000-7f7890500000 rw-p 00000000 00:00 0 -7f7890500000-7f7890540000 rw-p 00000000 00:00 0 -7f7890540000-7f7890580000 rw-p 00000000 00:00 0 -7f7890580000-7f78906a0000 rw-p 00000000 00:00 0 -7f78906a0000-7f78906c0000 rw-p 00000000 00:00 0 -7f78906c0000-7f7890700000 rw-p 00000000 00:00 0 -7f7890700000-7f7890840000 rw-p 00000000 00:00 0 -7f7890840000-7f7890880000 rw-p 00000000 00:00 0 -7f7890880000-7f7890900000 rw-p 00000000 00:00 0 -7f7890900000-7f7890b00000 rw-p 00000000 00:00 0 -7f7890b00000-7f7890b80000 rw-p 00000000 00:00 0 -7f7890b80000-7f7890d00000 rw-p 00000000 00:00 0 -7f7890d00000-7f7890e00000 rw-p 00000000 00:00 0 -7f7890e00000-7f7891100000 rw-p 00000000 00:00 0 -7f7891100000-7f7891200000 rw-p 00000000 00:00 0 -7f7891200000-7f7891300000 rw-p 00000000 00:00 0 -7f7891300000-7f7891400000 rw-p 00000000 00:00 0 -7f7891400000-7f7891500000 rw-p 00000000 00:00 0 -7f7891500000-7f7891600000 rw-p 00000000 00:00 0 -7f7891600000-7f7891780000 rw-p 00000000 00:00 0 -7f7891780000-7f7891800000 rw-p 00000000 00:00 0 -7f7891800000-7f7891900000 rw-p 00000000 00:00 0 -7f7891900000-7f7891a00000 rw-p 00000000 00:00 0 -7f7891a00000-7f7891b80000 rw-p 00000000 00:00 0 -7f7891b80000-7f7891c00000 rw-p 00000000 00:00 0 -7f7891c00000-7f7891f00000 rw-p 00000000 00:00 0 -7f7891f00000-7f7892200000 rw-p 00000000 00:00 0 -7f7892200000-7f7892b00000 rw-p 00000000 00:00 0 -7f7892b00000-7f7892c00000 rw-p 00000000 00:00 0 -7f7892c00000-7f7892e30000 rw-p 00000000 00:00 0 -7f7892e30000-7f7892f00000 ---p 00000000 00:00 0 -7f7892f00000-7f7892f30000 rw-p 00000000 00:00 0 -7f7892f30000-7f7894000000 ---p 00000000 00:00 0 -7f7894000000-7f7894024000 rw-p 00000000 00:00 0 -7f7894024000-7f7898000000 ---p 00000000 00:00 0 -7f7898000000-7f789803c000 rw-p 00000000 00:00 0 -7f789803c000-7f789c000000 ---p 00000000 00:00 0 -7f789c000000-7f789c021000 rw-p 00000000 00:00 0 -7f789c021000-7f78a0000000 ---p 00000000 00:00 0 -7f78a0000000-7f78a0002000 r--p 00000000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 -7f78a0002000-7f78a0005000 r-xp 00002000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 -7f78a0005000-7f78a0006000 r--p 00005000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 -7f78a0006000-7f78a0007000 r--p 00006000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 -7f78a0007000-7f78a0008000 rw-p 00007000 103:05 67932475 /usr/lib/libXfixes.so.3.1.0 -7f78a0008000-7f78a000a000 r--p 00000000 103:05 67925758 /usr/lib/libffi.so.8.5.0 -7f78a000a000-7f78a0012000 r-xp 00002000 103:05 67925758 /usr/lib/libffi.so.8.5.0 -7f78a0012000-7f78a0014000 r--p 0000a000 103:05 67925758 /usr/lib/libffi.so.8.5.0 -7f78a0014000-7f78a0015000 r--p 0000b000 103:05 67925758 /usr/lib/libffi.so.8.5.0 -7f78a0015000-7f78a0016000 rw-p 0000c000 103:05 67925758 /usr/lib/libffi.so.8.5.0 -7f78a0016000-7f78a0019000 r--p 00000000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng -7f78a0019000-7f78a0046000 r-xp 00003000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng -7f78a0046000-7f78a0050000 r--p 00030000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng -7f78a0050000-7f78a0051000 r--p 00039000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng -7f78a0051000-7f78a0052000 rw-p 0003a000 103:05 67902721 /usr/lib/libz.so.1.3.1.zlib-ng -7f78a0052000-7f78a0058000 r--p 00000000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 -7f78a0058000-7f78a00f0000 r-xp 00006000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 -7f78a00f0000-7f78a0119000 r--p 0009e000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 -7f78a0119000-7f78a0121000 r--p 000c6000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 -7f78a0121000-7f78a0122000 rw-p 000ce000 103:05 67946985 /usr/lib/libfreetype.so.6.20.6 -7f78a0122000-7f78a0124000 r--p 00000000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 -7f78a0124000-7f78a0127000 r-xp 00002000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 -7f78a0127000-7f78a0162000 r-xp 00005000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 -7f78a0162000-7f78a0163000 r-xp 00040000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 -7f78a0163000-7f78a0176000 r--p 00041000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 -7f78a0176000-7f78a0192000 r--p 00054000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 -7f78a0192000-7f78a0193000 rw-p 00070000 103:05 67982409 /usr/lib/libGLdispatch.so.0.0.0 -7f78a0193000-7f78a019b000 rw-p 00000000 00:00 0 -7f78a019b000-7f78a01a1000 r--p 00000000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 -7f78a01a1000-7f78a01d6000 r-xp 00006000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 -7f78a01d6000-7f78a020c000 r--p 0003b000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 -7f78a020c000-7f78a0215000 r--p 00071000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 -7f78a0215000-7f78a0216000 rw-p 0007a000 103:05 67932870 /usr/lib/libfontconfig.so.1.17.0 -7f78a0216000-7f78a022d000 r--p 00000000 103:05 67931562 /usr/lib/libX11.so.6.4.0 -7f78a022d000-7f78a02e6000 r-xp 00017000 103:05 67931562 /usr/lib/libX11.so.6.4.0 -7f78a02e6000-7f78a037b000 r--p 000d0000 103:05 67931562 /usr/lib/libX11.so.6.4.0 -7f78a037b000-7f78a037e000 r--p 00164000 103:05 67931562 /usr/lib/libX11.so.6.4.0 -7f78a037e000-7f78a0381000 rw-p 00167000 103:05 67931562 /usr/lib/libX11.so.6.4.0 -7f78a0381000-7f78a0382000 rw-p 00000000 00:00 0 -7f78a0382000-7f78a03b1000 r--p 00000000 103:05 67932688 /usr/lib/libGL.so.1.7.0 -7f78a03b1000-7f78a03b4000 r-xp 0002f000 103:05 67932688 /usr/lib/libGL.so.1.7.0 -7f78a03b4000-7f78a03cf000 r-xp 00032000 103:05 67932688 /usr/lib/libGL.so.1.7.0 -7f78a03cf000-7f78a03d0000 r-xp 0004d000 103:05 67932688 /usr/lib/libGL.so.1.7.0 -7f78a03d0000-7f78a03e4000 r--p 0004e000 103:05 67932688 /usr/lib/libGL.so.1.7.0 -7f78a03e4000-7f78a03f2000 r--p 00062000 103:05 67932688 /usr/lib/libGL.so.1.7.0 -7f78a03f2000-7f78a03f3000 rw-p 00070000 103:05 67932688 /usr/lib/libGL.so.1.7.0 -7f78a03f3000-7f78a03f4000 rw-p 00000000 00:00 0 -7f78a03f4000-7f78a03f5000 rw-p 00000000 00:00 0 -7f78a03f5000-7f78a03f9000 ---p 00000000 00:00 0 -7f78a03f9000-7f78a04f6000 rw-p 00000000 00:00 0 -7f78a04f6000-7f78a04fa000 ---p 00000000 00:00 0 -7f78a04fa000-7f78a05f7000 rw-p 00000000 00:00 0 -7f78a05f7000-7f78a05fb000 ---p 00000000 00:00 0 -7f78a05fb000-7f78a06f8000 rw-p 00000000 00:00 0 -7f78a06f8000-7f78a06fc000 ---p 00000000 00:00 0 -7f78a06fc000-7f78a07f9000 rw-p 00000000 00:00 0 -7f78a07f9000-7f78a07fd000 ---p 00000000 00:00 0 -7f78a07fd000-7f78a08fa000 rw-p 00000000 00:00 0 -7f78a08fa000-7f78a08fe000 ---p 00000000 00:00 0 -7f78a08fe000-7f78a09fb000 rw-p 00000000 00:00 0 -7f78a09fb000-7f78a09ff000 ---p 00000000 00:00 0 -7f78a09ff000-7f78a0afc000 rw-p 00000000 00:00 0 -7f78a0afc000-7f78a0b00000 ---p 00000000 00:00 0 -7f78a0b00000-7f78a0bfd000 rw-p 00000000 00:00 0 -7f78a0bfd000-7f78a0c01000 ---p 00000000 00:00 0 -7f78a0c01000-7f78a0cfe000 rw-p 00000000 00:00 0 -7f78a0cfe000-7f78a0d02000 ---p 00000000 00:00 0 -7f78a0d02000-7f78a0dff000 rw-p 00000000 00:00 0 -7f78a0dff000-7f78a0e03000 ---p 00000000 00:00 0 -7f78a0e03000-7f78a0f00000 rw-p 00000000 00:00 0 -7f78a0f00000-7f78a0f04000 ---p 00000000 00:00 0 -7f78a0f04000-7f78a1000000 rw-p 00000000 00:00 0 -7f78a1000000-7f78a12ed000 r--p 00000000 103:05 67988358 /usr/lib/locale/locale-archive -7f78a12ed000-7f78a12ee000 rw-p 00000000 00:00 0 -7f78a12ee000-7f78a12ef000 r--p 00000000 00:00 0 -7f78a12ef000-7f78a12f2000 r--p 00000000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 -7f78a12f2000-7f78a12f9000 r-xp 00003000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 -7f78a12f9000-7f78a12fc000 r--p 0000a000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 -7f78a12fc000-7f78a12fe000 r--p 0000c000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 -7f78a12fe000-7f78a12ff000 rw-p 0000e000 103:05 67932621 /usr/lib/libwayland-client.so.0.26.0 -7f78a12ff000-7f78a1303000 r--p 00000000 103:05 67980018 /usr/lib/libgcc_s.so.1 -7f78a1303000-7f78a1326000 r-xp 00004000 103:05 67980018 /usr/lib/libgcc_s.so.1 -7f78a1326000-7f78a132a000 r--p 00027000 103:05 67980018 /usr/lib/libgcc_s.so.1 -7f78a132a000-7f78a132b000 r--p 0002b000 103:05 67980018 /usr/lib/libgcc_s.so.1 -7f78a132b000-7f78a132c000 rw-p 0002c000 103:05 67980018 /usr/lib/libgcc_s.so.1 -7f78a132c000-7f78a132e000 r--p 00000000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 -7f78a132e000-7f78a134e000 r-xp 00002000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 -7f78a134e000-7f78a1357000 r--p 00022000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 -7f78a1357000-7f78a1359000 r--p 0002a000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 -7f78a1359000-7f78a135a000 rw-p 0002c000 103:05 67940675 /usr/lib/libexpat.so.1.12.3 -7f78a135a000-7f78a135b000 rw-p 00000000 00:00 0 -7f78a135b000-7f78a135f000 ---p 00000000 00:00 0 -7f78a135f000-7f78a165d000 rw-p 00000000 00:00 0 -7f78a165d000-7f78a17f8000 rw-p 00000000 00:00 0 -7f78a17f8000-7f78a19fa000 rw-p 00000000 00:00 0 -7f78a19fa000-7f78a423e000 rw-p 00000000 00:00 0 -7f78a423e000-7f78a8000000 ---p 00000000 00:00 0 -7f78a8000000-7f78a8491000 rw-p 00000000 00:00 0 -7f78a8491000-7f78ac000000 ---p 00000000 00:00 0 -7f78ac000000-7f78ad191000 rw-p 00000000 00:00 0 -7f78ad191000-7f78b0000000 ---p 00000000 00:00 0 -7f78b0000000-7f78b0002000 r--p 00000000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 -7f78b0002000-7f78b000a000 r-xp 00002000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 -7f78b000a000-7f78b000c000 r--p 0000a000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 -7f78b000c000-7f78b000d000 r--p 0000c000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 -7f78b000d000-7f78b000e000 rw-p 0000d000 103:05 67925190 /usr/lib/libXrender.so.1.3.0 -7f78b000e000-7f78b001a000 r--p 00000000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 -7f78b001a000-7f78b0030000 r-xp 0000c000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 -7f78b0030000-7f78b0039000 r--p 00022000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 -7f78b0039000-7f78b003a000 r--p 0002a000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 -7f78b003a000-7f78b003b000 rw-p 0002b000 103:05 67929223 /usr/lib/libxcb.so.1.1.0 -7f78b003b000-7f78b0c47000 rw-p 00000000 00:00 0 -7f78b0c47000-7f78b0f4a000 rw-p 00000000 00:00 0 -7f78b0f4a000-7f78b10e6000 rw-p 00000000 00:00 0 -7f78b10e6000-7f78b1586000 ---p 00000000 00:00 0 -7f78b1586000-7f78b15a6000 rw-p 00000000 00:00 0 -7f78b15a6000-7f78b1f86000 ---p 00000000 00:00 0 -7f78b1f86000-7f78b1fc6000 rw-p 00000000 00:00 0 -7f78b1fc6000-7f78c0806000 ---p 00000000 00:00 0 -7f78c0806000-7f78c081a000 rw-p 00000000 00:00 0 -7f78c081a000-7f78c08ae000 ---p 00000000 00:00 0 -7f78c08ae000-7f78c08b2000 rw-p 00000000 00:00 0 -7f78c08b2000-7f78c09ee000 ---p 00000000 00:00 0 -7f78c09ee000-7f78c09f6000 rw-p 00000000 00:00 0 -7f78c09f6000-7f78c26fe000 ---p 00000000 00:00 0 -7f78c26fe000-7f78c2712000 rw-p 00000000 00:00 0 -7f78c2712000-7f78c27a6000 ---p 00000000 00:00 0 -7f78c27a6000-7f78c27aa000 rw-p 00000000 00:00 0 -7f78c27aa000-7f78c28e6000 ---p 00000000 00:00 0 -7f78c28e6000-7f78c28ee000 rw-p 00000000 00:00 0 -7f78c28ee000-7f78c45f6000 ---p 00000000 00:00 0 -7f78c45f6000-7f78c5000000 rw-p 00000000 00:00 0 -7f78c5000000-7f78c5bb0000 rwxp 00000000 00:00 0 -7f78c5bb0000-7f78cc45f000 ---p 00000000 00:00 0 -7f78cc45f000-7f78cca2f000 rwxp 00000000 00:00 0 -7f78cca2f000-7f78ccb9f000 ---p 00000000 00:00 0 -7f78ccb9f000-7f78ccf2f000 rwxp 00000000 00:00 0 -7f78ccf2f000-7f78d4000000 ---p 00000000 00:00 0 -7f78d4000000-7f78d4913000 rw-p 00000000 00:00 0 -7f78d4913000-7f78d8000000 ---p 00000000 00:00 0 -7f78d8000000-7f78d8001000 rw-p 00000000 00:00 0 -7f78d8001000-7f78d8002000 r--p 00000000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 -7f78d8002000-7f78d800c000 r-xp 00001000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 -7f78d800c000-7f78d800f000 r--p 0000b000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 -7f78d800f000-7f78d8010000 r--p 0000d000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 -7f78d8010000-7f78d8011000 rw-p 0000e000 103:05 67915289 /usr/lib/libbrotlidec.so.1.2.0 -7f78d8011000-7f78d8013000 r--p 00000000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 -7f78d8013000-7f78d8022000 r-xp 00002000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 -7f78d8022000-7f78d8024000 r--p 00011000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 -7f78d8024000-7f78d8025000 r--p 00012000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 -7f78d8025000-7f78d8026000 rw-p 00013000 103:05 67912408 /usr/lib/libbz2.so.1.0.8 -7f78d8026000-7f78d8836000 rw-p 00000000 00:00 0 -7f78d8836000-7f78d8917000 ---p 00000000 00:00 0 -7f78d8917000-7f78d892f000 rw-p 00000000 00:00 0 -7f78d892f000-7f78d8a00000 ---p 00000000 00:00 0 -7f78d8a00000-7f78dbef3000 r--s 00000000 103:05 54040325 /opt/android-studio/jbr/lib/modules -7f78dbef3000-7f78dbef4000 r-xp 00000000 00:2f 21007 /tmp/ffi7UxbMI (deleted) -7f78dbef4000-7f78dbef5000 rw-p 00000000 00:00 0 -7f78dbef5000-7f78dbef7000 r--p 00000000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 -7f78dbef7000-7f78dbef9000 r-xp 00002000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 -7f78dbef9000-7f78dbefa000 r--p 00004000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 -7f78dbefa000-7f78dbefb000 r--p 00004000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 -7f78dbefb000-7f78dbefc000 rw-p 00005000 103:05 67918001 /usr/lib/libgmodule-2.0.so.0.8800.3 -7f78dbefc000-7f78dbefe000 r--p 00000000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 -7f78dbefe000-7f78dbf18000 r-xp 00002000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 -7f78dbf18000-7f78dbf1b000 r--p 0001c000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 -7f78dbf1b000-7f78dbf1c000 r--p 0001f000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 -7f78dbf1c000-7f78dbf1d000 rw-p 00020000 103:05 67982384 /usr/lib/libGLX.so.0.0.0 -7f78dbf1d000-7f78dbfc9000 rw-p 00000000 00:00 0 -7f78dbfc9000-7f78dbfcd000 ---p 00000000 00:00 0 -7f78dbfcd000-7f78dc0c9000 rw-p 00000000 00:00 0 -7f78dc0c9000-7f78dc0d9000 r--p 00000000 103:05 67898319 /usr/lib/libm.so.6 -7f78dc0d9000-7f78dc175000 r-xp 00010000 103:05 67898319 /usr/lib/libm.so.6 -7f78dc175000-7f78dc1fe000 r--p 000ac000 103:05 67898319 /usr/lib/libm.so.6 -7f78dc1fe000-7f78dc1ff000 r--p 00134000 103:05 67898319 /usr/lib/libm.so.6 -7f78dc1ff000-7f78dc200000 rw-p 00135000 103:05 67898319 /usr/lib/libm.so.6 -7f78dc200000-7f78dc81f000 r--p 00000000 103:05 54040334 /opt/android-studio/jbr/lib/server/libjvm.so -7f78dc81f000-7f78dd5c0000 r-xp 0061e000 103:05 54040334 /opt/android-studio/jbr/lib/server/libjvm.so -7f78dd5c0000-7f78dd6ae000 r--p 013be000 103:05 54040334 /opt/android-studio/jbr/lib/server/libjvm.so -7f78dd6ae000-7f78dd6cb000 rw-p 014ab000 103:05 54040334 /opt/android-studio/jbr/lib/server/libjvm.so -7f78dd6cb000-7f78dd75e000 rw-p 00000000 00:00 0 -7f78dd75e000-7f78dd75f000 r--s 00000000 103:05 3171939 /var/cache/fontconfig/787b69f04c7448ed6c581b040220c91f-le64.cache-12 -7f78dd75f000-7f78dd760000 r--s 00000000 103:05 96460852 /home/mey/.cache/fontconfig/3e2c0d130455d44a3f0cd652a06f4a14-le64.cache-12 -7f78dd760000-7f78dd761000 rw-p 00000000 00:00 0 -7f78dd761000-7f78dd762000 rw-p 00000000 00:00 0 -7f78dd762000-7f78dd764000 r--p 00000000 103:05 54040295 /opt/android-studio/jbr/lib/libfontmanager_xawt.so -7f78dd764000-7f78dd765000 r-xp 00001000 103:05 54040295 /opt/android-studio/jbr/lib/libfontmanager_xawt.so -7f78dd765000-7f78dd766000 r--p 00001000 103:05 54040295 /opt/android-studio/jbr/lib/libfontmanager_xawt.so -7f78dd766000-7f78dd768000 rw-p 00001000 103:05 54040295 /opt/android-studio/jbr/lib/libfontmanager_xawt.so -7f78dd768000-7f78dd76a000 r--p 00000000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 -7f78dd76a000-7f78dd76c000 r-xp 00002000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 -7f78dd76c000-7f78dd76e000 r--p 00004000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 -7f78dd76e000-7f78dd76f000 r--p 00005000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 -7f78dd76f000-7f78dd770000 rw-p 00006000 103:05 67947240 /usr/lib/libXdmcp.so.6.0.0 -7f78dd770000-7f78dd771000 r--p 00000000 103:05 67925070 /usr/lib/libXau.so.6.0.0 -7f78dd771000-7f78dd773000 r-xp 00001000 103:05 67925070 /usr/lib/libXau.so.6.0.0 -7f78dd773000-7f78dd774000 r--p 00003000 103:05 67925070 /usr/lib/libXau.so.6.0.0 -7f78dd774000-7f78dd775000 r--p 00003000 103:05 67925070 /usr/lib/libXau.so.6.0.0 -7f78dd775000-7f78dd776000 rw-p 00004000 103:05 67925070 /usr/lib/libXau.so.6.0.0 -7f78dd776000-7f78dd77a000 r--p 00000000 103:05 54040314 /opt/android-studio/jbr/lib/libnet.so -7f78dd77a000-7f78dd782000 r-xp 00003000 103:05 54040314 /opt/android-studio/jbr/lib/libnet.so -7f78dd782000-7f78dd783000 r--p 0000a000 103:05 54040314 /opt/android-studio/jbr/lib/libnet.so -7f78dd783000-7f78dd784000 rw-p 0000a000 103:05 54040314 /opt/android-studio/jbr/lib/libnet.so -7f78dd784000-7f78dd800000 rw-p 00000000 00:00 0 -7f78dd800000-7f78dd824000 r--p 00000000 103:05 67898298 /usr/lib/libc.so.6 -7f78dd824000-7f78dd99f000 r-xp 00024000 103:05 67898298 /usr/lib/libc.so.6 -7f78dd99f000-7f78dda15000 r--p 0019f000 103:05 67898298 /usr/lib/libc.so.6 -7f78dda15000-7f78dda19000 r--p 00214000 103:05 67898298 /usr/lib/libc.so.6 -7f78dda19000-7f78dda1b000 rw-p 00218000 103:05 67898298 /usr/lib/libc.so.6 -7f78dda1b000-7f78dda23000 rw-p 00000000 00:00 0 -7f78dda23000-7f78dda24000 r--p 00000000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 -7f78dda24000-7f78dda25000 r-xp 00001000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 -7f78dda25000-7f78dda26000 r--p 00002000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 -7f78dda26000-7f78dda27000 r--p 00002000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 -7f78dda27000-7f78dda28000 rw-p 00003000 103:05 67934185 /usr/lib/libXinerama.so.1.0.0 -7f78dda28000-7f78dda33000 r--p 00000000 103:05 54040315 /opt/android-studio/jbr/lib/libnio.so -7f78dda33000-7f78dda3c000 r-xp 0000a000 103:05 54040315 /opt/android-studio/jbr/lib/libnio.so -7f78dda3c000-7f78dda3d000 r--p 00012000 103:05 54040315 /opt/android-studio/jbr/lib/libnio.so -7f78dda3d000-7f78dda3e000 rw-p 00012000 103:05 54040315 /opt/android-studio/jbr/lib/libnio.so -7f78dda3e000-7f78dda47000 r--p 00000000 103:05 54040324 /opt/android-studio/jbr/lib/libzip.so -7f78dda47000-7f78dda5c000 r-xp 00008000 103:05 54040324 /opt/android-studio/jbr/lib/libzip.so -7f78dda5c000-7f78dda5d000 r--p 0001c000 103:05 54040324 /opt/android-studio/jbr/lib/libzip.so -7f78dda5d000-7f78dda5e000 rw-p 0001c000 103:05 54040324 /opt/android-studio/jbr/lib/libzip.so -7f78dda5e000-7f78ddaa8000 rw-p 00000000 00:00 0 -7f78ddaa8000-7f78ddaab000 ---p 00000000 00:00 0 -7f78ddaab000-7f78ddabc000 r--p 00000000 103:05 54040301 /opt/android-studio/jbr/lib/libjava.so -7f78ddabc000-7f78ddacb000 r-xp 00010000 103:05 54040301 /opt/android-studio/jbr/lib/libjava.so -7f78ddacb000-7f78ddacd000 r--p 0001e000 103:05 54040301 /opt/android-studio/jbr/lib/libjava.so -7f78ddacd000-7f78ddacf000 rw-p 0001f000 103:05 54040301 /opt/android-studio/jbr/lib/libjava.so -7f78ddacf000-7f78ddad7000 rw-s 00000000 00:2f 21006 /tmp/hsperfdata_mey/1041994 -7f78ddad7000-7f78ddad8000 ---p 00000000 00:00 0 -7f78ddad8000-7f78ddad9000 r--p 00000000 00:00 0 -7f78ddad9000-7f78ddada000 ---p 00000000 00:00 0 -7f78ddada000-7f78ddae3000 r--p 00000000 103:05 54040305 /opt/android-studio/jbr/lib/libjimage.so -7f78ddae3000-7f78ddaf7000 r-xp 00008000 103:05 54040305 /opt/android-studio/jbr/lib/libjimage.so -7f78ddaf7000-7f78ddaf9000 r--p 0001b000 103:05 54040305 /opt/android-studio/jbr/lib/libjimage.so -7f78ddaf9000-7f78ddafa000 rw-p 0001c000 103:05 54040305 /opt/android-studio/jbr/lib/libjimage.so -7f78ddafa000-7f78ddafb000 r--p 00000000 103:05 67898353 /usr/lib/librt.so.1 -7f78ddafb000-7f78ddafc000 r-xp 00001000 103:05 67898353 /usr/lib/librt.so.1 -7f78ddafc000-7f78ddafd000 r--p 00002000 103:05 67898353 /usr/lib/librt.so.1 -7f78ddafd000-7f78ddafe000 r--p 00002000 103:05 67898353 /usr/lib/librt.so.1 -7f78ddafe000-7f78ddaff000 rw-p 00003000 103:05 67898353 /usr/lib/librt.so.1 -7f78ddaff000-7f78ddb01000 rw-p 00000000 00:00 0 -7f78ddb01000-7f78ddb02000 r--p 00000000 103:05 67898348 /usr/lib/libpthread.so.0 -7f78ddb02000-7f78ddb03000 r-xp 00001000 103:05 67898348 /usr/lib/libpthread.so.0 -7f78ddb03000-7f78ddb04000 r--p 00002000 103:05 67898348 /usr/lib/libpthread.so.0 -7f78ddb04000-7f78ddb05000 r--p 00002000 103:05 67898348 /usr/lib/libpthread.so.0 -7f78ddb05000-7f78ddb06000 rw-p 00003000 103:05 67898348 /usr/lib/libpthread.so.0 -7f78ddb06000-7f78ddb07000 r--p 00000000 103:05 67898309 /usr/lib/libdl.so.2 -7f78ddb07000-7f78ddb08000 r-xp 00001000 103:05 67898309 /usr/lib/libdl.so.2 -7f78ddb08000-7f78ddb09000 r--p 00002000 103:05 67898309 /usr/lib/libdl.so.2 -7f78ddb09000-7f78ddb0a000 r--p 00002000 103:05 67898309 /usr/lib/libdl.so.2 -7f78ddb0a000-7f78ddb0b000 rw-p 00003000 103:05 67898309 /usr/lib/libdl.so.2 -7f78ddb0b000-7f78ddb14000 r--p 00000000 103:05 54040306 /opt/android-studio/jbr/lib/libjli.so -7f78ddb14000-7f78ddb23000 r-xp 00008000 103:05 54040306 /opt/android-studio/jbr/lib/libjli.so -7f78ddb23000-7f78ddb24000 r--p 00016000 103:05 54040306 /opt/android-studio/jbr/lib/libjli.so -7f78ddb24000-7f78ddb25000 rw-p 00016000 103:05 54040306 /opt/android-studio/jbr/lib/libjli.so -7f78ddb25000-7f78ddb28000 rw-p 00000000 00:00 0 -7f78ddb28000-7f78ddb6d000 r--p 00000000 103:05 71566212 /etc/ld.so.cache -7f78ddb6d000-7f78ddb71000 r--p 00000000 00:00 0 [vvar] -7f78ddb71000-7f78ddb73000 r--p 00000000 00:00 0 [vvar_vclock] -7f78ddb73000-7f78ddb75000 r-xp 00000000 00:00 0 [vdso] -7f78ddb75000-7f78ddb76000 r--p 00000000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 -7f78ddb76000-7f78ddba3000 r-xp 00001000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 -7f78ddba3000-7f78ddbb2000 r--p 0002e000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 -7f78ddbb2000-7f78ddbb4000 r--p 0003d000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 -7f78ddbb4000-7f78ddbb5000 rw-p 0003f000 103:05 67898281 /usr/lib/ld-linux-x86-64.so.2 -7f78ddbb5000-7f78ddbb6000 rw-p 00000000 00:00 0 -7ffdf5441000-7ffdf5465000 rw-p 00000000 00:00 0 [stack] -ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] -Total number of mappings: 922 - -JVMTI agents: none - - -VM Arguments: -jvm_args: -Dcompose.application.configure.swing.globals=true -Dcompose.application.resources.dir=/home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/desktop/build/compose/tmp/prepareAppResources -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -Dawt.toolkit.name=XToolkit -java_command: capital.yuri.yuriplayer.desktop.MainKt -java_class_path (initial): /home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/desktop/build/libs/desktop.jar:/home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/components/build/libs/components-jvm.jar:/home/mey/dev/repos/git.yuri.capital/yuri/YuriPlayer/core/build/libs/core-jvm.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-client-cio-jvm/3.5.2/cd0673816e97d9a658efae6892cf9f93df2591f1/ktor-client-cio-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.desktop/desktop-jvm/1.8.2/61be96a019ac35e8751b08b33856344a24586648/desktop-jvm-1.8.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-network-ktor3-jvm/3.5.0/d85dbeb646cf44c53a733fcb63cc82f6a6d54f16/coil-network-ktor3-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-compose-jvm/3.5.0/820523ee39decd98eecdb61a7fc03412eedbbabc/coil-compose-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-jvm/3.5.0/602087c06fa4b1795a1572a26b174516c4d932e5/coil-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-compose-core-jvm/3.5.0/8ebdd416d1157820ca47dc6a7644c56716a9c8fa/coil-compose-core-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-network-core-jvm/3.5.0/5a995a2262efe8e8569b40fee8ce223aaa5599d8/coil-network-core-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.coil-kt.coil3/coil-core-jvm/3.5.0/5e82ee4827a88516057cb4ebd22871f6672643a8/coil-core-jvm-3.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-client-core-jvm/3.5.2/d6525cc26aa12e341b14a37dea698fcb4eca54cb/ktor-client-core-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-http-cio-jvm/3.5.2/4d8f22cce83a2c865e0d3e64d4abfd4ea13a8e6c/ktor-http-cio-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-websocket-serialization-jvm/3.5.2/d391929e7082af2e6f0cddcf7df4b8f934b0a297/ktor-websocket-serialization-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-serialization-jvm/3.5.2/beed828bb6d5388782ee3d7ab9e17f19888a60a9/ktor-serialization-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-websockets-jvm/3.5.2/9906c60d014018f83dd0f4f6eff306956dc1fe09/ktor-websockets-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-network-tls-jvm/3.5.2/893f2bffbb2308e2fca22ec9b35a72ce3e3f8738/ktor-network-tls-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-slf4j/1.11.0/556153d93573d016dd4e5df37e3d03d07d8d303/kotlinx-coroutines-slf4j-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material3/material3-desktop/1.8.2/1f1f838506ceb2c25499517559f120517770b014/material3-desktop-1.8.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-desktop/1.8.2/7aae6f749a060718d931957a27d495f26b2d4f37/material-desktop-1.8.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-ripple-desktop/1.8.2/5c9c96a95930f802de1b611fb610db4a0eff49cf/material-ripple-desktop-1.8.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.animation/animation-core-desktop/1.11.1/1bf633ecd1a38a1cf7a41630bc56cba0da9cc25b/animation-core-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.animation/animation-desktop/1.11.1/f873fb28f1a08d45a4dd20a0130b3da8cadf383c/animation-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.foundation/foundation-layout-desktop/1.11.1/853fc5d01388da6515ce40b719798914012f6469/foundation-layout-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.foundation/foundation-desktop/1.11.1/6df316e7af2c0c97f200de289925f62ca8fce53d/foundation-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-icons-extended-desktop/1.7.3/c7c5465dd754043076875b32bce0dd7f5a354fe8/material-icons-extended-desktop-1.7.3.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.material/material-icons-core-desktop/1.7.3/414b3096050fe647b0638832eace80f22c06b85c/material-icons-core-desktop-1.7.3.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-tooling-preview-desktop/1.11.1/97ec578d33adc34adcaa63e9af7bb2328c6c29b7/ui-tooling-preview-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-backhandler-desktop/1.11.1/bd3540bb1d6abd60c5e58f32663b2f3e89406cd1/ui-backhandler-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-unit-desktop/1.11.1/f6c06f98da206d01dc40c1587c6bc4c40cbef623/ui-unit-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-graphics-desktop/1.11.1/6ea106d02adf29694eedf12fdfb621947d3621bf/ui-graphics-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-geometry-desktop/1.11.1/5e430152487375359973ac9874653d335dcc220/ui-geometry-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-util-desktop/1.11.1/4302c582878b82ab24e1f34e6e90f967f7b416ed/ui-util-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-text-desktop/1.11.1/6ded685e2601260d6231c859a849b96ee6c60368/ui-text-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.ui/ui-desktop/1.11.1/24c40c0d60c77a6fb34f930f13bfa4a3ff62c0f/ui-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.skiko/skiko-awt-runtime-linux-x64/0.144.6/291f8626a2fecd06e9f585424360560189ab7303/skiko-awt-runtime-linux-x64-0.144.6.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.skiko/skiko-awt/0.144.6/c9bec39e3c517ed5591c71c7db0cbbe64b6be849/skiko-awt-0.144.6.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-http-jvm/3.5.2/d28bf8120efdc33354d91d99c0842187759e3c70/ktor-http-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-events-jvm/3.5.2/5e64959ec6f91dff8ea281cfa102ae73bce000f7/ktor-events-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-sse-jvm/3.5.2/84a9273210b25e32403252149f7bcd16b7ab9fbb/ktor-sse-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-network-jvm/3.5.2/1d4a6deadcc3a30de665ea26f98eea3fc52750bb/ktor-network-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-utils-jvm/3.5.2/dc58dadecf1646d013ba0dda5dffab0b71a3fefd/ktor-utils-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-io-jvm/3.5.2/b27620278e10b206310a22b9303c5274f239872d/ktor-io-jvm-3.5.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.navigationevent/navigationevent-desktop/1.0.1/ddb1e7fe97ed51c3d35bba493f97fcf12398719/navigationevent-desktop-1.0.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.compose.runtime/runtime-annotation-jvm/1.11.2/db4aa964627ac96331b72e231b9ad54318d112f4/runtime-annotation-jvm-1.11.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.compose.runtime/runtime-retain-desktop/1.11.2/3aa3ec26ef2a3d26bc556b51b33d09efc1a198e8/runtime-retain-desktop-1.11.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.compose.runtime/runtime-saveable-desktop/1.11.2/1618fae89791692a224fe7aa79edbd7a8f5b7522/runtime-saveable-desktop-1.11.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.runtime/runtime-saveable-desktop/1.11.1/b7c8a8ed985a0f15b4d8d206de1fc6125b499388/runtime-saveable-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.androidx.lifecycle/lifecycle-runtime-compose-desktop/2.9.6/7b6b6793f3c4c5996b20d1d99cf728e5fac00364/lifecycle-runtime-compose-desktop-2.9.6.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.androidx.savedstate/savedstate-compose-desktop/1.3.6/63692375ffe667f12cb3ea8aa1e605d659ed2add/savedstate-compose-desktop-1.3.6.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.compose.runtime/runtime-desktop/1.11.1/1941336b045aac4b87cc3be81d9fdb9977c88c61/runtime-desktop-1.11.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-runtime-desktop/2.9.4/1cc820319ec0139e76b8a2fe4be0b356b4e624c4/lifecycle-runtime-desktop-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common-jvm/2.9.4/ab739bccdb3541983385af169565c7c035d897e7/lifecycle-common-jvm-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-runtime-compose-desktop/2.9.4/86a4ffec845d4e488078118a0b5095bd1fabcafe/lifecycle-runtime-compose-desktop-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-viewmodel-desktop/2.9.4/93fee6d78f0df0e38f36e0fa2eff74f8da6d5481/lifecycle-viewmodel-desktop-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-viewmodel-savedstate-desktop/2.9.4/72a1919b08c5a4da224d8a65ce1589aa69934e8a/lifecycle-viewmodel-savedstate-desktop-2.9.4.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.savedstate/savedstate-desktop/1.4.0/3b049a0f81643d6b4b900de95bc23efc085c32d5/savedstate-desktop-1.4.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.savedstate/savedstate-compose-desktop/1.4.0/d3d6e0e2108528eb6080dc250da925a572be4202/savedstate-compose-desktop-1.4.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.compose.runtime/runtime-desktop/1.11.2/e93d95553cdd86c1cb8c02f50260cc1c8e341da8/runtime-desktop-1.11.2.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm/1.11.0/3d57dc678bd8d72a60e7adf0eca5c54e3f4f4b79/kotlinx-coroutines-core-jvm-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-swing/1.11.0/3cf134b8aefd813622edc37e60d66b939b55cb36/kotlinx-coroutines-swing-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-serialization-core-jvm/1.11.0/c1905c9bf7452b045d5af9248835abe64ddad6bf/kotlinx-serialization-core-jvm-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-serialization-json-jvm/1.11.0/e7e045e99e56f07599bb9ccb708c2190e2af0606/kotlinx-serialization-json-jvm-1.11.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/2.1.21/fb62497994f5cfa4ad41fcff05020c35976307c6/kotlin-stdlib-jdk8-2.1.21.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-datetime-jvm/0.6.0/a6362032556c0fa4a06be6105a21e944f120fd2d/kotlinx-datetime-jvm-0.6.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.collection/collection-jvm/1.5.0/7ba2c69414d46ebc2dd76598bdd0a75c54281a57/collection-jvm-1.5.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.2.0/5e1b8b81dfd5f52c56a8d53b18ca759c19a301f3/core-common-2.2.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation-jvm/1.9.1/b17951747e38bf3986a24431b9ba0d039958aa5f/annotation-jvm-1.9.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/2.1.21/f5e72fe5f9501e0d27ec633beb3996c29641b877/kotlin-stdlib-jdk7-2.1.21.jar:/home/mey/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio-jvm/3.17.0/a12d684ec0df9541b3869ae60a4a5257fd3613df/okio-jvm-3.17.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/atomicfu-jvm/0.28.0/6a9ac536f4b2144e57c176fd4e26821eb12084f4/atomicfu-jvm-0.28.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-io-core-jvm/0.9.1/8c926478f17f8109e01e68adee93795c68dcad3c/kotlinx-io-core-jvm-0.9.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-io-bytestring-jvm/0.9.1/7a28d93fce9fc8b663a6fe19cb2ae14a6ade7e8a/kotlinx-io-bytestring-jvm-0.9.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/2.4.0/4e9f4b531dce5c7dee58ed2de9a8b9a763c8dc6c/kotlin-stdlib-2.4.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/uk.co.caprica/vlcj/4.10.1/958b353e8b85450e92df5ba56f5cf734fce2d1c2/vlcj-4.10.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/com.github.hypfvieh/dbus-java-transport-native-unixsocket/5.1.1/40db43e95df145299a9dc97f611c8a4e1f7477a3/dbus-java-transport-native-unixsocket-5.1.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/com.github.hypfvieh/dbus-java-core/5.1.1/36af749ddaefd567bd4e8a41c24845a584ce76d9/dbus-java-core-5.1.1.jar:/home/mey/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna-platform/5.17.0/a4934c44d25a9d8c2ddf4203affd20330cb3426f/jna-platform-5.17.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna/5.17.0/33d12735bef894440780fce64f9758d420c7bae2/jna-5.17.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/23.0.0/8cc20c07506ec18e0834947b84a864bfc094484e/annotations-23.0.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/com.github.goxr3plus/jaudiotagger/2.2.7/2d229776d40b33aca730f2e31157079189431cbf/jaudiotagger-2.2.7.jar:/home/mey/.gradle/caches/modules-2/files-2.1/uk.co.caprica/vlcj-natives/4.8.3/2a84facef1dac138801fdd3d85ffb77d3d1b10a7/vlcj-natives-4.8.3.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.18/78a9e7a37cd6360e0b818e86341b24123d28d4df/slf4j-api-2.0.18.jar:/home/mey/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna-platform-jpms/5.14.0/24fde384a42c2c75c46116edda48895e5763fc85/jna-platform-jpms-5.14.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/net.java.dev.jna/jna-jpms/5.14.0/9513388696e34db7c9b564aaec0ccbd7395e9389/jna-jpms-5.14.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jetbrains.runtime/jbr-api/1.9.0/2bc161f5472b5fd5610740e4d6ebe896c811cb0e/jbr-api-1.9.0.jar:/home/mey/.gradle/caches/modules-2/files-2.1/org.jspecify/jspecify/1.0.0/7425a601c1c7ec76645a78d22b8c6a627edee507/jspecify-1.0.0.jar -Launcher Type: SUN_STANDARD - -[Global flags] - intx CICompilerCount = 12 {product} {ergonomic} - uint ConcGCThreads = 5 {product} {ergonomic} - uint G1ConcRefinementThreads = 18 {product} {ergonomic} - size_t G1HeapRegionSize = 8388608 {product} {ergonomic} - size_t InitialHeapSize = 1040187392 {product} {ergonomic} - size_t MarkStackSize = 4194304 {product} {ergonomic} - size_t MarkStackSizeMax = 536870912 {product} {ergonomic} - size_t MaxHeapSize = 16626221056 {product} {ergonomic} - size_t MaxNewSize = 9974054912 {product} {ergonomic} - size_t MinHeapDeltaBytes = 8388608 {product} {ergonomic} - size_t MinHeapSize = 8388608 {product} {ergonomic} - uintx NonNMethodCodeHeapSize = 7602176 {pd product} {ergonomic} - uintx NonProfiledCodeHeapSize = 122032128 {pd product} {ergonomic} - uintx ProfiledCodeHeapSize = 122023936 {pd product} {ergonomic} - uintx ReservedCodeCacheSize = 251658240 {pd product} {ergonomic} - bool SegmentedCodeCache = true {product} {ergonomic} - size_t SoftMaxHeapSize = 16626221056 {manageable} {ergonomic} - bool UseCompressedOops = true {product lp64_product} {ergonomic} - bool UseG1GC = true {product} {ergonomic} - -Logging: -Log output configuration: - #0: stdout all=warning uptime,level,tags foldmultilines=false - #1: stderr all=off uptime,level,tags foldmultilines=false - -Release file: -JAVA_VERSION="25.0.2" -MODULES="java.base java.compiler java.datatransfer java.xml java.prefs java.desktop java.instrument java.logging java.management java.security.sasl java.naming java.rmi java.management.rmi java.net.http java.scripting java.security.jgss java.transaction.xa java.sql java.sql.rowset java.xml.crypto java.se java.smartcardio jdk.accessibility jdk.internal.jvmstat jdk.attach jdk.charsets jdk.internal.opt jdk.zipfs jdk.compiler jdk.crypto.cryptoki jdk.crypto.ec jdk.dynalink jdk.hotspot.agent jdk.httpserver jdk.internal.ed jdk.internal.le jdk.internal.md jdk.internal.vm.ci jdk.jartool jdk.javadoc jdk.jcmd jdk.jdeps jdk.jdwp.agent jdk.jdi jdk.jfr jdk.jlink jdk.localedata jdk.management jdk.management.agent jdk.management.jfr jdk.naming.dns jdk.naming.rmi jdk.net jdk.sctp jdk.security.auth jdk.security.jgss jdk.unsupported jdk.unsupported.desktop jdk.xml.dom" -IMPLEMENTOR="JetBrains s.r.o." -JAVA_RUNTIME_VERSION="25.0.2+-15348964-b329.117" -LIBC="gnu" -OS_ARCH="x86_64" -OS_NAME="Linux" -SOURCE=".:git:3cf398e84a39" - -Environment Variables: -PATH=/home/mey/.local/share/../bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/opt/cuda/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/rustup/bin:/home/mey/.local/share/JetBrains/Toolbox/scripts:/home/mey/.lmstudio/bin -SHELL=/usr/bin/fish -DISPLAY=:0 -WAYLAND_DISPLAY=wayland-0 -LANG=en_US.UTF-8 -TERM=linux - -Active Locale: -LC_ALL=en_US.UTF-8 -LC_COLLATE=en_US.UTF-8 -LC_CTYPE=en_US.UTF-8 -LC_MESSAGES=en_US.UTF-8 -LC_MONETARY=en_US.UTF-8 -LC_NUMERIC=en_US.UTF-8 -LC_TIME=en_US.UTF-8 - -Signal Handlers: - SIGSEGV: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked - SIGBUS: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked - SIGFPE: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked - SIGPIPE: javaSignalHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked - SIGXFSZ: javaSignalHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked - SIGILL: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked - SIGUSR2: SR_handler in libjvm.so, mask=00000000000000000000000000000000, flags=SA_RESTART|SA_SIGINFO, blocked - SIGHUP: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked - SIGINT: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked - SIGTERM: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked - SIGQUIT: UserHandler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, blocked - SIGTRAP: crash_handler in libjvm.so, mask=11100100010111111101111111111110, flags=SA_RESTART|SA_SIGINFO, unblocked - - -Compilation memory statistics disabled. - -Periodic native trim disabled - -JNI global refs: -JNI global refs: 371, weak refs: 55 - -JNI global refs memory usage: 4019, weak refs: 1489 - -Process memory usage: -Resident Set Size: 576188K (0% of 64925104K total physical memory with 27131332K free physical memory) - -OOME stack traces (most recent first): -Classloader memory used: -Loader jdk.internal.loader.ClassLoaders$AppClassLoader : 30410 K -Loader bootstrap : 22525 K -Loader jdk.internal.loader.ClassLoaders$PlatformClassLoader : 27088 B - ---------------- S Y S T E M --------------- - -OS: -DISTRIB_ID="Arch" -DISTRIB_RELEASE="rolling" -DISTRIB_DESCRIPTION="Arch Linux" -uname: Linux 7.1.8-arch1-3 #1 SMP PREEMPT_DYNAMIC Tue, 11 Aug 2026 09:16:08 +0000 x86_64 -OS uptime: 2 days 8:29 hours -libc: glibc 2.44 NPTL 2.44 -rlimit (soft/hard): STACK 8192k/infinity , CORE infinity/infinity , NPROC 252603/252603 , NOFILE 524288/524288 , AS infinity/infinity , CPU infinity/infinity , DATA infinity/infinity , FSIZE infinity/infinity , MEMLOCK 8192k/8192k -load average: 2.19 1.44 2.00 - -/proc/meminfo: -MemTotal: 64925104 kB -MemFree: 1446336 kB -MemAvailable: 27130844 kB -Buffers: 2096932 kB -Cached: 24164020 kB -SwapCached: 126880 kB -Active: 21758016 kB -Inactive: 35235200 kB -Active(anon): 17947668 kB -Inactive(anon): 14684956 kB -Active(file): 3810348 kB -Inactive(file): 20550244 kB -Unevictable: 11528 kB -Mlocked: 11528 kB -SwapTotal: 244197372 kB -SwapFree: 243609164 kB -Zswap: 99928 kB -Zswapped: 384652 kB -Dirty: 15344 kB -Writeback: 0 kB -AnonPages: 30008184 kB -Mapped: 8290788 kB -Shmem: 1892888 kB -KReclaimable: 2047956 kB -Slab: 2721332 kB -SReclaimable: 2047956 kB -SUnreclaim: 673376 kB -KernelStack: 53312 kB -PageTables: 390472 kB -SecPageTables: 2488 kB -NFS_Unstable: 0 kB -Bounce: 0 kB -WritebackTmp: 0 kB -CommitLimit: 276659924 kB -Committed_AS: 76815368 kB -VmallocTotal: 34359738367 kB -VmallocUsed: 320400 kB -VmallocChunk: 0 kB -Percpu: 21312 kB -HardwareCorrupted: 0 kB -AnonHugePages: 12435456 kB -ShmemHugePages: 0 kB -ShmemPmdMapped: 0 kB -FileHugePages: 2408448 kB -FilePmdMapped: 1605632 kB -CmaTotal: 0 kB -CmaFree: 0 kB -Unaccepted: 0 kB -Balloon: 0 kB -GPUActive: 15884 kB -GPUReclaim: 0 kB -HugePages_Total: 0 -HugePages_Free: 0 -HugePages_Rsvd: 0 -HugePages_Surp: 0 -Hugepagesize: 2048 kB -Hugetlb: 0 kB -DirectMap4k: 33897344 kB -DirectMap2M: 32294912 kB -DirectMap1G: 1048576 kB - -/sys/kernel/mm/transparent_hugepage/enabled: [always] madvise never -/sys/kernel/mm/transparent_hugepage/hpage_pmd_size: 2097152 -/sys/kernel/mm/transparent_hugepage/shmem_enabled: always within_size [advise] never deny force -/sys/kernel/mm/transparent_hugepage/defrag (defrag/compaction efforts parameter): always defer defer+madvise [madvise] never - -Process Memory: -Virtual Size: 20007456K (peak: 20162384K) -Resident Set Size: 576184K (peak: 576184K) (anon: 383780K, file: 192352K, shmem: 52K) -Swapped out: 0K -C-Heap outstanding allocations: 132885K, retained: 125414K -glibc malloc tunables: (default) - -/proc/sys/kernel/threads-max (system-wide limit on the number of threads): 505206 -/proc/sys/vm/max_map_count (maximum number of memory map areas a process may have): 1048576 -/proc/sys/vm/swappiness (control to define how aggressively the kernel swaps out anonymous memory): 60 -/proc/sys/kernel/pid_max (system-wide limit on number of process identifiers): 4194304 - -container information not found. -Steal ticks since vm start: 0 -Steal ticks percentage since vm start: 0.000 - -CPU: total 24 (initial active 24) (24 cores per cpu, 2 threads per core) family 25 model 97 stepping 2 microcode 0xa60120a, cx8, cmov, fxsr, ht, mmx, 3dnowpref, sse, sse2, sse3, ssse3, sse4a, sse4.1, sse4.2, popcnt, lzcnt, tsc, tscinvbit, avx, avx2, aes, erms, clmul, bmi1, bmi2, adx, avx512f, avx512dq, avx512cd, avx512bw, avx512vl, sha, fma, vzeroupper, avx512_vpopcntdq, avx512_vpclmulqdq, avx512_vaes, avx512_vnni, clflush, clflushopt, clwb, avx512_vbmi2, avx512_vbmi, rdtscp, rdpid, fsrm, gfni, avx512_bitalg, f16c, pku, ospke, cet_ss, avx512_ifma -CPU Model and flags from /proc/cpuinfo: -model name : AMD Ryzen 9 7900X 12-Core Processor -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good amd_lbr_v2 nopl xtopology nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpuid_fault cpb cat_l3 cdp_l3 hw_pstate ssbd mba perfmon_v2 ibrs ibpb stibp ibrs_enhanced vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local user_shstk avx512_bf16 clzero irperf xsaveerptr rdpru wbnoinvd cppc arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic vgif x2avic v_spec_ctrl vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid overflow_recov succor smca fsrm flush_l1d amd_lbr_pmc_freeze - -Online cpus: 0-23 -Offline cpus: -BIOS frequency limitation: -Frequency switch latency (ns): 0 -Available cpu frequencies: -Current governor: powersave -Core performance/turbo boost: 1 - -Memory: 4k page, physical 64925104k(27130844k free), swap 244197372k(243609164k free) -Page Sizes: 4k - -vm_info: OpenJDK 64-Bit Server VM (25.0.2+-15348964-b329.117) for linux-amd64 JRE (25.0.2+-15348964-b329.117), built on 2026-05-05T19:01:01Z with clang Android (14488419, +pgo, +bolt, +lto, +mlgo, based on r584948) Clang 22.0.0 (https://android.googlesource.com/toolchain/llvm-project 2d65e4108033380e6fe8e08b1f1826cd2bfb0c99) - -END. From 6f14272dd7fe8c70932bdb5779d7128d48a5ba29 Mon Sep 17 00:00:00 2001 From: Mey Date: Thu, 27 Aug 2026 09:48:32 -0500 Subject: [PATCH 64/69] ci: use beta compileSdk 37 --- .github/workflows/ci.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2a2391f..bdd325ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,15 +24,22 @@ jobs: distribution: temurin java-version: 21 - - name: Set up Android SDK + # compileSdk 37 is currently only published on the Android SDK beta/canary + # channel, so we install it manually instead of via setup-android's stable + # `packages` list (which fails with "Failed to find package platforms;android-37"). + - name: Set up Android SDK (stable packages) uses: android-actions/setup-android@v3 with: packages: > tools platform-tools - platforms;android-37 build-tools;36.0.0 + - name: Install compileSdk 37 (beta channel) + run: | + sdkmanager --channel=3 "platforms;android-37" "build-tools;37.0.0" + yes | sdkmanager --licenses > /dev/null + - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 From 19064358d51f520aa192209b4fddec95e9993e30 Mon Sep 17 00:00:00 2001 From: Mey Date: Thu, 27 Aug 2026 10:01:37 -0500 Subject: [PATCH 65/69] ci: go back to 36 --- .github/workflows/ci.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdd325ae..61f82b06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,22 +24,15 @@ jobs: distribution: temurin java-version: 21 - # compileSdk 37 is currently only published on the Android SDK beta/canary - # channel, so we install it manually instead of via setup-android's stable - # `packages` list (which fails with "Failed to find package platforms;android-37"). - - name: Set up Android SDK (stable packages) + - name: Set up Android SDK uses: android-actions/setup-android@v3 with: packages: > tools platform-tools + platforms;android-36 build-tools;36.0.0 - - name: Install compileSdk 37 (beta channel) - run: | - sdkmanager --channel=3 "platforms;android-37" "build-tools;37.0.0" - yes | sdkmanager --licenses > /dev/null - - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 From 9263c68067596c9cbd51f238a41f0782a16d1774 Mon Sep 17 00:00:00 2001 From: Mey Date: Thu, 27 Aug 2026 10:58:28 -0500 Subject: [PATCH 66/69] build: go back to 36 --- app/build.gradle.kts | 4 ++-- components/build.gradle.kts | 2 +- core/build.gradle.kts | 2 +- gradle/libs.versions.toml | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index aa4804ce..9374a913 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -62,13 +62,13 @@ val gitDirty: Provider = gitStatus.map { it != "unknown" && it.isNotBla android { namespace = "capital.yuri.yuriplayer" compileSdk { - version = release(37) + version = release(36) } defaultConfig { applicationId = "capital.yuri.yuriplayer" minSdk = 27 - targetSdk = 37 + targetSdk = 36 versionCode = 1 versionName = "0.1.0-local" diff --git a/components/build.gradle.kts b/components/build.gradle.kts index 9e450f23..9d541458 100644 --- a/components/build.gradle.kts +++ b/components/build.gradle.kts @@ -7,7 +7,7 @@ plugins { kotlin { android { namespace = "capital.yuri.yuriplayer.components" - compileSdk = 37 + compileSdk = 36 minSdk = 27 compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 67739d91..60ec7794 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -11,7 +11,7 @@ plugins { kotlin { android { namespace = "capital.yuri.yuriplayer.core" - compileSdk = 37 + compileSdk = 36 minSdk = 27 compilerOptions { jvmTarget.set(JvmTarget.JVM_11) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 21747597..2efd31a7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,15 @@ [versions] agp = "9.3.2" -coreKtx = "1.19.0" +coreKtx = "1.18.0" junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" appcompat = "1.8.0" material = "1.13.0" -lifecycleRuntimeKtx = "2.11.0" +lifecycleRuntimeKtx = "2.10.0" activityCompose = "1.13.0" kotlin = "2.3.21" -composeBom = "2026.08.00" +composeBom = "2026.06.00" media3 = "1.11.0" media = "1.7.1" koin = "4.2.2" From 2bd9f87db7b8ecd9117a2c93099612ad48f483da Mon Sep 17 00:00:00 2001 From: Mey Date: Fri, 28 Aug 2026 13:52:58 -0500 Subject: [PATCH 67/69] fix: context menu submenu alignment & artist folding changes --- .../components/platform/Screen.android.kt | 12 ++ .../components/menu/PredictionCone.kt | 11 +- .../components/menu/YuriContextMenu.kt | 154 ++++++++++++++---- .../yuriplayer/components/model/UiModels.kt | 11 +- .../yuriplayer/components/platform/Screen.kt | 17 ++ .../components/platform/Screen.jvm.kt | 31 ++++ .../yuriplayer/core/library/SongSearch.kt | 24 +++ .../yuri/yuriplayer/data/AlbumMerge.kt | 9 +- .../yuri/yuriplayer/data/AlbumTrackExpand.kt | 7 +- .../capital/yuri/yuriplayer/data/Song.kt | 21 ++- .../core/library/ArtistIdentityTest.kt | 39 +++++ 11 files changed, 276 insertions(+), 60 deletions(-) create mode 100644 components/src/androidMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.android.kt create mode 100644 components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.kt create mode 100644 components/src/jvmMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.jvm.kt create mode 100644 core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/ArtistIdentityTest.kt diff --git a/components/src/androidMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.android.kt b/components/src/androidMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.android.kt new file mode 100644 index 00000000..4e730847 --- /dev/null +++ b/components/src/androidMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.android.kt @@ -0,0 +1,12 @@ +package capital.yuri.yuriplayer.components.platform + +import android.content.res.Resources +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize + +actual fun screenSizePx(): IntSize { + val metrics = Resources.getSystem().displayMetrics + return IntSize(metrics.widthPixels, metrics.heightPixels) +} + +actual fun windowPositionOnScreenPx(): IntOffset = IntOffset.Zero diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/PredictionCone.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/PredictionCone.kt index cca3703a..a208ff95 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/PredictionCone.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/PredictionCone.kt @@ -30,17 +30,18 @@ fun pointInTriangle(p: Offset, a: Offset, b: Offset, c: Offset): Boolean { return u >= -0.02f && v >= -0.02f && u + v <= 1.02f } -fun predictionCone(origin: Offset, submenu: Rect): Triple? { +fun predictionCone(origin: Offset, submenu: Rect, openLeft: Boolean = false): Triple? { if (!finite(origin) || submenu.isEmpty) return null + val edge = if (openLeft) submenu.right else submenu.left return Triple( origin, - Offset(submenu.left, submenu.top), - Offset(submenu.left, submenu.bottom) + Offset(edge, submenu.top), + Offset(edge, submenu.bottom) ) } -fun pointerInPredictionCone(current: Offset, origin: Offset, submenu: Rect): Boolean { - val cone = predictionCone(origin, submenu) ?: return false +fun pointerInPredictionCone(current: Offset, origin: Offset, submenu: Rect, openLeft: Boolean = false): Boolean { + val cone = predictionCone(origin, submenu, openLeft) ?: return false return pointInTriangle(current, cone.first, cone.second, cone.third) } diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt index 7e0a843c..e250f85c 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/menu/YuriContextMenu.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -25,6 +26,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -41,9 +43,18 @@ import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.isSecondaryPressed import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.boundsInParent import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp +import capital.yuri.yuriplayer.components.platform.screenSizePx +import capital.yuri.yuriplayer.components.platform.windowPositionOnScreenPx +import kotlin.math.roundToInt /** * Material 3 menu with nested submenus and a prediction cone so the pointer @@ -61,49 +72,107 @@ fun YuriContextMenu( var coneOrigin by remember { mutableStateOf(Offset.Unspecified) } var submenuRect by remember { mutableStateOf(Rect.Zero) } + val density = LocalDensity.current + // Submenus share the same width bounds as the parent column; include the + // Surface's 4dp padding on each side to stay consistent with measured sizes. + val submenuMaxWidthPx = with(density) { 288.dp.toPx() } + + // Viewport width and the popup window's on-screen origin (platform-specific). + val screenWidthPx = remember { screenSizePx().width } + val windowOrigin = remember(openSubmenu) { + if (openSubmenu >= 0) windowPositionOnScreenPx() else IntOffset.Zero + } + + var parentSize by remember { mutableStateOf(IntSize.Zero) } + var popupPosInWindow by remember { mutableStateOf(IntOffset.Zero) } + var parentTopInRoot by remember { mutableStateOf(0f) } + val itemTopsInRoot = remember { mutableStateMapOf() } + + // Open the submenu to the left when the parent menu would otherwise push it + // past the right edge of the viewport. + val flipToLeft = parentSize.width > 0 && screenWidthPx > 0 && + (windowOrigin.x + popupPosInWindow.x + parentSize.width + submenuMaxWidthPx.toInt()) > screenWidthPx + + // Vertical offset of the highlighted item relative to the parent menu top. + val itemY = if (openSubmenu >= 0) { + itemTopsInRoot[openSubmenu]?.let { (it - parentTopInRoot).roundToInt().coerceAtLeast(0) } ?: 0 + } else 0 + Box( - modifier.pointerInput(entries) { - awaitPointerEventScope { - while (true) { - val event = awaitPointerEvent(PointerEventPass.Initial) - val change = event.changes.lastOrNull() ?: continue - val next = change.position - if (pointer != Offset.Unspecified) coneOrigin = pointer - pointer = next + modifier + .pointerInput(entries) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent(PointerEventPass.Initial) + val change = event.changes.lastOrNull() ?: continue + val next = change.position + if (pointer != Offset.Unspecified) coneOrigin = pointer + pointer = next + } } } - } + .onGloballyPositioned { coords -> + val pos = coords.positionInWindow() + popupPosInWindow = IntOffset(pos.x.roundToInt(), pos.y.roundToInt()) + } ) { - Row(verticalAlignment = Alignment.Top) { - MenuColumn( - entries = entries, - openSubmenu = openSubmenu, - onHover = { index -> - val origin = if (coneOrigin != Offset.Unspecified) coneOrigin else pointer - val blocked = openSubmenu >= 0 && - index != openSubmenu && - pointerInPredictionCone(pointer, origin, submenuRect) - if (!blocked) openSubmenu = index - }, - onOpenSubmenu = { openSubmenu = it }, - onDismiss = onDismiss - ) - val nested = entries.getOrNull(openSubmenu)?.nestedChildren() - if (!nested.isNullOrEmpty()) { + Layout( + content = { MenuColumn( - entries = nested, - openSubmenu = -1, - onHover = {}, - onOpenSubmenu = {}, + entries = entries, + openSubmenu = openSubmenu, + onHover = { index -> + val origin = if (coneOrigin != Offset.Unspecified) coneOrigin else pointer + val blocked = openSubmenu >= 0 && + index != openSubmenu && + pointerInPredictionCone(pointer, origin, submenuRect, flipToLeft) + if (!blocked) openSubmenu = index + }, + onOpenSubmenu = { openSubmenu = it }, onDismiss = onDismiss, - modifier = Modifier.onGloballyPositioned { - submenuRect = it.boundsInParent() + submenuOpensLeft = flipToLeft, + onItemPositioned = { index, topInRoot -> itemTopsInRoot[index] = topInRoot }, + modifier = Modifier.onGloballyPositioned { coords -> + parentSize = coords.size + parentTopInRoot = coords.positionInRoot().y } ) + val nested = entries.getOrNull(openSubmenu)?.nestedChildren() + if (!nested.isNullOrEmpty()) { + MenuColumn( + entries = nested, + openSubmenu = -1, + onHover = {}, + onOpenSubmenu = {}, + onDismiss = onDismiss, + modifier = Modifier.onGloballyPositioned { + submenuRect = it.boundsInParent() + } + ) + } + } + ) { measurables, constraints -> + val parent = measurables[0].measure(constraints) + val submenu = measurables.getOrNull(1)?.measure(constraints) + val submenuWidth = submenu?.width ?: 0 + val submenuHeight = submenu?.height ?: 0 + + val width = if (submenu != null) parent.width + submenuWidth else parent.width + val height = if (submenu != null) maxOf(parent.height, itemY + submenuHeight) else parent.height + + layout(width, height) { + if (submenu != null && flipToLeft) { + submenu.place(0, itemY) + parent.place(submenuWidth, 0) + } else { + parent.place(0, 0) + submenu?.place(parent.width, itemY) + } } } + if (showPredictionCone && openSubmenu >= 0) { - val cone = predictionCone(pointer, submenuRect) + val cone = predictionCone(pointer, submenuRect, flipToLeft) if (cone != null) { Canvas(Modifier.matchParentSize()) { val path = Path().apply { @@ -143,7 +212,9 @@ private fun MenuColumn( onHover: (Int) -> Unit, onOpenSubmenu: (Int) -> Unit, onDismiss: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + submenuOpensLeft: Boolean = false, + onItemPositioned: (Int, Float) -> Unit = { _, _ -> } ) { Surface( modifier = modifier.padding(4.dp), @@ -170,6 +241,10 @@ private fun MenuColumn( enabled = entry.enabled, selected = openSubmenu == index && entry.alternate.isNotEmpty(), hasSubmenu = entry.alternate.isNotEmpty(), + submenuOpensLeft = submenuOpensLeft, + modifier = Modifier.onGloballyPositioned { coords -> + onItemPositioned(index, coords.positionInRoot().y) + }, onHover = { onHover(-1) }, onClick = { if (entry.enabled) { @@ -188,6 +263,10 @@ private fun MenuColumn( enabled = true, selected = openSubmenu == index, hasSubmenu = true, + submenuOpensLeft = submenuOpensLeft, + modifier = Modifier.onGloballyPositioned { coords -> + onItemPositioned(index, coords.positionInRoot().y) + }, onHover = { onHover(index) }, onClick = { onHover(index) } ) @@ -207,7 +286,9 @@ private fun MenuItemRow( hasSubmenu: Boolean, onHover: () -> Unit, onClick: () -> Unit, - onAltClick: (() -> Unit)? = null + onAltClick: (() -> Unit)? = null, + modifier: Modifier = Modifier, + submenuOpensLeft: Boolean = false ) { val interaction = remember { MutableInteractionSource() } val hovered by interaction.collectIsHoveredAsState() @@ -218,7 +299,7 @@ private fun MenuItemRow( else -> MaterialTheme.colorScheme.onSurface } Row( - Modifier + modifier .fillMaxWidth() .padding(horizontal = 6.dp) .clip(RoundedCornerShape(8.dp)) @@ -264,7 +345,8 @@ private fun MenuItemRow( } if (hasSubmenu) { Icon( - Icons.AutoMirrored.Filled.KeyboardArrowRight, + if (submenuOpensLeft) Icons.AutoMirrored.Filled.KeyboardArrowLeft + else Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = null, tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.45f), modifier = Modifier.padding(start = 8.dp) diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt index 1ae6b90a..fc7c1e57 100644 --- a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/model/UiModels.kt @@ -3,7 +3,8 @@ package capital.yuri.yuriplayer.components.model import capital.yuri.yuriplayer.core.library.albumGroupKey import capital.yuri.yuriplayer.core.library.catalogKey import capital.yuri.yuriplayer.core.library.collapseAlbumTracks -import capital.yuri.yuriplayer.core.library.matchesSearch +import capital.yuri.yuriplayer.core.library.matchesArtistName +import capital.yuri.yuriplayer.core.library.matchesCreditedArtist import capital.yuri.yuriplayer.data.Song data class CoverRef( @@ -112,12 +113,12 @@ fun List.artistPage( recents: List = emptyList() ): ArtistPageModel { val allAlbums = albums() - val discography = allAlbums.filter { it.artist.matchesSearch(name) } + val discography = allAlbums.filter { it.artist.matchesArtistName(name) } .sortedByDescending { it.year ?: Int.MIN_VALUE } val appearsOn = allAlbums.filter { album -> - !album.artist.matchesSearch(name) && + !album.artist.matchesArtistName(name) && album.tracks.any { row -> - row.artist.matchesSearch(name) + row.artist.matchesCreditedArtist(name) } }.sortedByDescending { it.year ?: Int.MIN_VALUE } @@ -125,7 +126,7 @@ fun List.artistPage( // same song collapse onto a single entry, so track counts / popular / liked // don't inflate for multi-source discographies (e.g. Lemon Demon). val ofArtist = filter { - it.displayArtist.matchesSearch(name) || (it.albumArtist?.matchesSearch(name) == true) + it.displayArtist.matchesArtistName(name) || (it.albumArtist?.matchesArtistName(name) == true) }.distinctBy { it.catalogKey() } val recentRank = recents.mapIndexed { i, t -> t.songKey to i }.toMap() diff --git a/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.kt b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.kt new file mode 100644 index 00000000..78212606 --- /dev/null +++ b/components/src/commonMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.kt @@ -0,0 +1,17 @@ +package capital.yuri.yuriplayer.components.platform + +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize + +/** + * Size of the primary display / viewport in pixels. + */ +expect fun screenSizePx(): IntSize + +/** + * Top-left of the current (focused or active) window on screen, in pixels. + * + * Android has a single full-screen window, so this is always [IntOffset.Zero]. + * Desktop reads the AWT window position (the popup window while a menu is open). + */ +expect fun windowPositionOnScreenPx(): IntOffset diff --git a/components/src/jvmMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.jvm.kt b/components/src/jvmMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.jvm.kt new file mode 100644 index 00000000..62325d68 --- /dev/null +++ b/components/src/jvmMain/kotlin/capital/yuri/yuriplayer/components/platform/Screen.jvm.kt @@ -0,0 +1,31 @@ +package capital.yuri.yuriplayer.components.platform + +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import java.awt.GraphicsEnvironment +import java.awt.KeyboardFocusManager +import java.awt.Toolkit + +actual fun screenSizePx(): IntSize { + if (GraphicsEnvironment.isHeadless()) return IntSize.Zero + return try { + val size = Toolkit.getDefaultToolkit().screenSize + IntSize(size.width, size.height) + } catch (_: Throwable) { + IntSize.Zero + } +} + +actual fun windowPositionOnScreenPx(): IntOffset { + if (GraphicsEnvironment.isHeadless()) return IntOffset.Zero + return try { + val focus = KeyboardFocusManager.getCurrentKeyboardFocusManager() + val window = focus.focusedWindow ?: focus.activeWindow + window?.let { + val loc = it.locationOnScreen + IntOffset(loc.x, loc.y) + } ?: IntOffset.Zero + } catch (_: Throwable) { + IntOffset.Zero + } +} diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/SongSearch.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/SongSearch.kt index 536d920b..1f46e41b 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/SongSearch.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/core/library/SongSearch.kt @@ -1,6 +1,10 @@ package capital.yuri.yuriplayer.core.library import capital.yuri.yuriplayer.data.Song +import capital.yuri.yuriplayer.data.TrackIdentity +import capital.yuri.yuriplayer.data.foldTagToken +import capital.yuri.yuriplayer.data.parseArtistCredits +import capital.yuri.yuriplayer.data.primaryArtistName /** * Library search matching used by desktop Explore (and shared later). @@ -25,6 +29,26 @@ fun String.matchesSearch(query: String): Boolean { return tokens.all { hay.contains(it) } } +/** + * Precise artist-identity comparison for artist pages: does the primary artist + * of [this] equal the primary artist of [name] (case/accent folded)? This is + * whole-name equality, NOT substring — "Rav" must not match "Ravenna Golden". + */ +fun String.matchesArtistName(name: String): Boolean = + TrackIdentity.albumArtistsMatch(this, name) + +/** + * Precise "appears on" check: is [name] credited (primary OR featured) on [this] + * tag blob? Whole-name only, so it never matches a shorter prefix like "Rav" + * against "Ravenna Golden". + */ +fun String.matchesCreditedArtist(name: String): Boolean { + if (isBlank()) return false + val target = foldTagToken(primaryArtistName(name) ?: name) + if (target.isEmpty()) return false + return parseArtistCredits(this).any { foldTagToken(it) == target } +} + fun searchTokens(query: String): List = foldSearch(query).split(' ').filter { it.isNotEmpty() } diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumMerge.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumMerge.kt index 0bfff0ab..3ff09745 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumMerge.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumMerge.kt @@ -106,15 +106,14 @@ fun findLocalAlbum( } val nameFolded = TrackIdentity.normalizeToken(name) - val artistFolded = TrackIdentity.normalizeToken(artist) val matches = library.songs().filter { song -> TrackIdentity.albumsMatch(song.album, name) || TrackIdentity.normalizeToken(song.album) == nameFolded }.filter { song -> - if (artistFolded.isEmpty()) return@filter true - val aa = TrackIdentity.normalizeToken(song.effectiveAlbumArtist) - if (aa.isEmpty()) return@filter true - aa == artistFolded || aa.contains(artistFolded) || artistFolded.contains(aa) + if (artist.isNullOrBlank()) return@filter true + val aa = song.effectiveAlbumArtist + if (aa.isNullOrBlank()) return@filter true + TrackIdentity.albumArtistsMatch(aa, artist) } if (matches.isEmpty()) return null AlbumLog.i(name, "findLocalAlbum matches=${matches.size} key='$key'") diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumTrackExpand.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumTrackExpand.kt index 44784dbf..a0935755 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumTrackExpand.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/AlbumTrackExpand.kt @@ -79,8 +79,7 @@ suspend fun fastExpandAlbumTracks( primaryArtistName(row.albumArtist ?: row.artist) ?: row.albumArtist ?: row.artist ?: "" ) val ta = foldTagToken(primaryArtistName(row.artist) ?: row.artist ?: "") - aa.isEmpty() || aa == artistFolded || ta == artistFolded || - aa.contains(artistFolded) || artistFolded.contains(aa) + aa.isEmpty() || aa == artistFolded || ta == artistFolded }.map { it.toSong() } val deduped = dedupeAlbumPageTracks(songs + extraSeedSongs) @@ -183,9 +182,7 @@ suspend fun expandAlbumTracksByName( ) val ta = foldTagToken(primaryArtistName(t.artist) ?: t.artist ?: "") if (aa.isEmpty() && ta.isEmpty()) return true - if (aa == artistFolded || ta == artistFolded) return true - return aa.contains(artistFolded) || artistFolded.contains(aa) || - ta.contains(artistFolded) || artistFolded.contains(ta) + return aa == artistFolded || ta == artistFolded } fun albumOk(t: CatalogTrackEntity): Boolean { diff --git a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/Song.kt b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/Song.kt index 82cdd8f1..b9943e53 100644 --- a/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/Song.kt +++ b/core/src/commonMain/kotlin/capital/yuri/yuriplayer/data/Song.kt @@ -353,15 +353,28 @@ object TrackIdentity { /** * Loose album-artist comparison: ignores `feat.` credits and ordering of * multiple (comma/slash/ampersand-separated) artists. + * + * Whole-name only — never substring — so "Rav" does NOT match + * "Ravenna Golden". */ fun albumArtistsMatch(a: String?, b: String?): Boolean { if (a.isNullOrBlank() || b.isNullOrBlank()) return false - val fa = normalizeToken(a) - val fb = normalizeToken(b) - if (fa == fb) return true - return fa.contains(fb) || fb.contains(fa) + val na = primaryArtistNames(a) + val nb = primaryArtistNames(b) + return na.isNotEmpty() && nb.isNotEmpty() && na.intersect(nb).isNotEmpty() } + /** + * Folded set of PRIMARY artist names in a tag blob, splitting on the same + * separators [parseArtistCreditList] understands (comma/slash/ampersand, + * `feat.`), but without featured credits. + */ + private fun primaryArtistNames(raw: String): Set = + parseArtistCreditList(raw) + .filter { it.role == ArtistRole.PRIMARY } + .mapNotNull { foldTagToken(it.name).takeIf { n -> n.isNotEmpty() } } + .toSet() + fun matches(a: Song, b: Song): Boolean = titlesNearlyMatch(a.title, b.title) && (albumsMatch(a.album, b.album) || diff --git a/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/ArtistIdentityTest.kt b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/ArtistIdentityTest.kt new file mode 100644 index 00000000..7a15713d --- /dev/null +++ b/core/src/jvmTest/kotlin/capital/yuri/yuriplayer/core/library/ArtistIdentityTest.kt @@ -0,0 +1,39 @@ +package capital.yuri.yuriplayer.core.library + +import capital.yuri.yuriplayer.data.TrackIdentity +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ArtistIdentityTest { + + @Test + fun prefixArtistDoesNotMatchLongerName() { + // Regression: "Rav" must never collapse with "Ravenna Golden" just + // because the folded shorter name is a substring of the longer one. + assertFalse(TrackIdentity.albumArtistsMatch("Rav", "Ravenna Golden")) + assertFalse("Rav".matchesArtistName("Ravenna Golden")) + assertFalse("Ravenna Golden".matchesArtistName("Rav")) + assertFalse("Rav".matchesCreditedArtist("Ravenna Golden")) + } + + @Test + fun samePrimaryArtistMatchesIgnoringCaseAndStylization() { + assertTrue(TrackIdentity.albumArtistsMatch("Lemon Demon", "lemon demon")) + assertTrue("Lemon Demon".matchesArtistName("LEMON DEMON")) + assertTrue("Twenty Øne Piløts".matchesArtistName("twenty one pilots")) + } + + @Test + fun featCreditStillResolvesPrimary() { + // "Camellia feat. nanahira" primary is "Camellia". + assertTrue(TrackIdentity.albumArtistsMatch("Camellia", "Camellia feat. nanahira")) + assertTrue("Camellia".matchesArtistName("Camellia feat. nanahira")) + } + + @Test + fun featuredCreditMatchesAppearsOn() { + assertTrue("Lemon Demon feat. Rav".matchesCreditedArtist("Rav")) + assertTrue("Rav".matchesCreditedArtist("Rav")) + } +} From 4aae71bbb5c95b4d1c36d672857d6c155ef0ab85 Mon Sep 17 00:00:00 2001 From: Mey Date: Fri, 28 Aug 2026 13:56:08 -0500 Subject: [PATCH 68/69] chore: update .idea gitignores --- .idea/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.idea/.gitignore b/.idea/.gitignore index 26d33521..55529824 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,3 +1,5 @@ # Default ignored files /shelf/ /workspace.xml +/discord.xml +/planningMode.xml \ No newline at end of file From a7abc9595b8a50417eb01e2cbbb6b30fbec9c9a8 Mon Sep 17 00:00:00 2001 From: Liz Ainslie Date: Tue, 8 Sep 2026 09:13:16 +0000 Subject: [PATCH 69/69] Find system LibVLC on NixOS instead of FHS-only paths. vlcj only searches /usr/lib*. NixOS keeps libvlc.so in the store and exposes it via /run/current-system/sw (IntelliJ PATH often omits that). Parse wrapProgram scripts, set VLC_PLUGIN_PATH via setenv, and register a vlcj DiscoveryDirectoryProvider so native init succeeds. --- desktop/build.gradle.kts | 1 + .../desktop/player/LibVlcBootstrap.kt | 75 ++++--- .../desktop/player/LibVlcLocator.kt | 196 ++++++++++++++++++ .../player/NixLibVlcDirectoryProvider.kt | 18 ++ .../desktop/player/VlcjPlaybackEngine.kt | 34 ++- .../yuriplayer/desktop/ui/DesktopSettings.kt | 6 +- ...covery.provider.DiscoveryDirectoryProvider | 1 + .../desktop/player/LibVlcLocatorTest.kt | 69 ++++++ 8 files changed, 363 insertions(+), 37 deletions(-) create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcLocator.kt create mode 100644 desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/NixLibVlcDirectoryProvider.kt create mode 100644 desktop/src/main/resources/META-INF/services/uk.co.caprica.vlcj.factory.discovery.provider.DiscoveryDirectoryProvider create mode 100644 desktop/src/test/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcLocatorTest.kt diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 20947484..a2ee53fb 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation(libs.ktor.client.cio) implementation(libs.jaudiotagger) implementation(project(":mediasession")) + testImplementation(kotlin("test")) } val libvlcRoot = layout.buildDirectory.dir("libvlc") diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt index cac5c709..1ff09fd4 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcBootstrap.kt @@ -1,49 +1,72 @@ package capital.yuri.yuriplayer.desktop.player import capital.yuri.yuriplayer.core.log.yuriLog +import capital.yuri.yuriplayer.core.platform.HostOs +import capital.yuri.yuriplayer.core.platform.hostOs +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.NativeLibrary +import uk.co.caprica.vlcj.binding.support.runtime.RuntimeUtil import java.io.File /** - * Points JNA / LibVLC at a bundled copy before [uk.co.caprica.vlcj.factory.MediaPlayerFactory] - * loads. Order: `-Dyuri.libvlc.dir`, `YURI_LIBVLC`, Compose app-resources, then system VLC. + * Points JNA / LibVLC at a usable native install before + * [uk.co.caprica.vlcj.factory.MediaPlayerFactory] loads. * - * Windows/macOS natives come from VideoLAN's official LibVLC NuGet packages. - * Linux uses extracted Debian libvlc + plugins (glibc) as a fallback; distro - * packages are still preferred when present. + * Order: `-Dyuri.libvlc.dir`, `YURI_LIBVLC`, Compose app-resources, then + * system VLC (NixOS profiles, FHS, PATH wrappers). + * + * [System.setProperty] is **not** enough for plugins — LibVLC reads + * `VLC_PLUGIN_PATH` via `getenv`, so we also `setenv`. */ object LibVlcBootstrap { private val log = yuriLog("LibVlc") fun install(): File? { - val dir = resolveDir() ?: return null - val path = dir.canonicalPath + val home = LibVlcLocator.find() + if (home == null) { + log.w { "no LibVLC directory found (NixOS: add vlc to systemPackages / home.packages, or set YURI_LIBVLC)" } + return null + } + val path = home.libDir.canonicalPath + prependJnaPath(path) + NativeLibrary.addSearchPath(RuntimeUtil.getLibVlcLibraryName(), path) + home.pluginDir?.let { plugins -> + setEnv("VLC_PLUGIN_PATH", plugins.canonicalPath) + log.i { "VLC_PLUGIN_PATH=${plugins.canonicalPath}" } + } + log.i { "LibVLC at $path" } + return home.libDir + } + + fun isNixOs(): Boolean = + File("/etc/NIXOS").exists() || + !System.getenv("NIX_PATH").isNullOrBlank() || + !System.getenv("NIX_STORE").isNullOrBlank() + + private fun prependJnaPath(path: String) { val existing = System.getProperty("jna.library.path") System.setProperty( "jna.library.path", if (existing.isNullOrBlank()) path else "$path${File.pathSeparator}$existing" ) - val plugins = File(dir, "plugins") - if (plugins.isDirectory) { - System.setProperty("VLC_PLUGIN_PATH", plugins.canonicalPath) - } - log.w { "LibVLC bundle: $path" } - return dir } - private fun resolveDir(): File? { - val candidates = listOfNotNull( - System.getProperty("yuri.libvlc.dir"), - System.getenv("YURI_LIBVLC"), - System.getProperty("compose.application.resources.dir") - ) - return candidates - .map(::File) - .firstOrNull { hasLibvlc(it) } + private fun setEnv(name: String, value: String) { + runCatching { + if (hostOs() == HostOs.WINDOWS) { + Native.load("msvcrt", WinC::class.java)._putenv("$name=$value") + } else { + Native.load("c", PosixC::class.java).setenv(name, value, 1) + } + }.onFailure { log.w(it) { "could not setenv $name" } } + } + + private interface PosixC : Library { + fun setenv(name: String, value: String, overwrite: Int): Int } - private fun hasLibvlc(dir: File): Boolean { - if (!dir.isDirectory) return false - val names = dir.list()?.toList().orEmpty() - return names.any { it.startsWith("libvlc") && (it.endsWith(".dll") || it.endsWith(".so") || it.endsWith(".dylib") || it.contains(".so.")) } + private interface WinC : Library { + fun _putenv(env: String): Int } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcLocator.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcLocator.kt new file mode 100644 index 00000000..74ea14ad --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcLocator.kt @@ -0,0 +1,196 @@ +package capital.yuri.yuriplayer.desktop.player + +import java.io.File + +/** + * Find a directory that actually contains LibVLC + LibVLCCore. + * + * vlcj's default Linux search is FHS-only (`/usr/lib`, `/usr/lib64`, …). NixOS, + * Guix, and a lot of IDE/Gradle launches never put those on disk — VLC lives + * under `/nix/store` and is exposed through `/run/current-system/sw` or a + * profile. IntelliJ often has a stripped PATH, so we look at those locations + * even when `which vlc` would fail. + */ +internal object LibVlcLocator { + + data class Home( + val libDir: File, + val pluginDir: File? + ) + + fun find(): Home? { + val homes = explicitHomes() + systemLibDirs().mapNotNull { homeIfLibDir(it) } + return homes.firstOrNull() + } + + internal fun looksLikeLibDir(names: Collection): Boolean { + val hasVlc = names.any { LIBVLC_NAME.matches(it) } + val hasCore = names.any { LIBVLCCORE_NAME.matches(it) } + return hasVlc && hasCore + } + + internal fun pluginDir(libDir: File): File? = sequenceOf( + File(libDir, "vlc/plugins"), + File(libDir, "plugins") + ).firstOrNull { it.isDirectory } + + /** + * Nix `wrapProgram` scripts export `VLC_PLUGIN_PATH` and exec a store + * binary. Pull the real lib dir out of that so JNA can load `libvlc.so`. + */ + internal fun libDirFromWrapper(text: String): File? { + pluginPathFromWrapper(text)?.let { plugin -> + val pluginFile = File(plugin) + homeIfLibDir(pluginFile.parentFile?.parentFile)?.libDir?.let { return it } + homeIfLibDir(pluginFile.parentFile)?.libDir?.let { return it } + } + ldLibraryDirs(text).forEach { dir -> + homeIfLibDir(dir)?.libDir?.let { return it } + } + wrappedBinary(text)?.let { bin -> + val prefix = bin.parentFile?.parentFile + homeIfLibDir(prefix?.let { File(it, "lib") })?.libDir?.let { return it } + homeIfLibDir(bin.parentFile)?.libDir?.let { return it } + } + return null + } + + internal fun pluginPathFromWrapper(text: String): String? = + ENV_ASSIGN.find(text, "VLC_PLUGIN_PATH") + + private fun explicitHomes(): List = listOfNotNull( + System.getProperty("yuri.libvlc.dir"), + System.getenv("YURI_LIBVLC"), + System.getProperty("compose.application.resources.dir") + ).map(::File).mapNotNull { homeIfLibDir(it) } + + private fun systemLibDirs(): List { + val out = ArrayList() + fun add(file: File?) { + if (file != null) out += file + } + + pathDirs().forEach { dir -> + VLC_BINARIES.forEach { name -> + val bin = File(dir, name) + if (bin.isFile) locateFromBinary(bin)?.let { add(it) } + } + } + + wellKnownBins().forEach { bin -> + if (bin.isFile) locateFromBinary(bin)?.let { add(it) } + } + + wellKnownLibDirs().forEach(::add) + + System.getenv("LD_LIBRARY_PATH") + ?.split(File.pathSeparator) + ?.map { it.trim() } + ?.filter { it.isNotEmpty() } + ?.forEach { add(File(it)) } + + nixProfileLibs().forEach(::add) + return out.distinctBy { it.absolutePath } + } + + private fun locateFromBinary(bin: File): File? { + runCatching { bin.readText() }.getOrNull() + ?.takeIf { looksLikeWrapper(it) } + ?.let { libDirFromWrapper(it) } + ?.let { return it } + + val real = runCatching { bin.canonicalFile }.getOrDefault(bin) + val parent = real.parentFile ?: return null + homeIfLibDir(parent)?.libDir?.let { return it } + val prefix = parent.parentFile ?: return null + return homeIfLibDir(File(prefix, "lib"))?.libDir + ?: homeIfLibDir(File(prefix, "lib64"))?.libDir + } + + private fun homeIfLibDir(dir: File?): Home? { + if (dir == null || !dir.isDirectory) return null + val names = dir.list()?.toList().orEmpty() + if (!looksLikeLibDir(names)) return null + return Home(dir, pluginDir(dir)) + } + + private fun wellKnownBins(): List { + val home = System.getProperty("user.home") ?: "." + return listOf( + File("/run/current-system/sw/bin/vlc"), + File("/run/current-system/profile/bin/vlc"), + File(home, ".nix-profile/bin/vlc"), + File(home, ".guix-profile/bin/vlc"), + File("/usr/bin/vlc"), + File("/usr/local/bin/vlc"), + File("/opt/homebrew/bin/vlc"), + File("/usr/local/opt/vlc/bin/vlc") + ) + } + + private fun wellKnownLibDirs(): List { + val home = System.getProperty("user.home") ?: "." + return listOf( + File("/run/current-system/sw/lib"), + File("/run/current-system/profile/lib"), + File(home, ".nix-profile/lib"), + File(home, ".guix-profile/lib"), + File("/usr/lib/x86_64-linux-gnu"), + File("/usr/lib/aarch64-linux-gnu"), + File("/usr/lib64"), + File("/usr/local/lib64"), + File("/usr/lib"), + File("/usr/local/lib"), + File("/opt/homebrew/lib"), + File("/usr/local/opt/vlc/lib") + ) + } + + private fun nixProfileLibs(): List { + val profiles = System.getenv("NIX_PROFILES") ?: return emptyList() + return profiles.split(Regex("\\s+")) + .map { it.trim() } + .filter { it.isNotEmpty() } + .map { File(it, "lib") } + } + + private fun pathDirs(): List { + val path = System.getenv("PATH") ?: return emptyList() + return path.split(File.pathSeparator).map { File(it) } + } + + private fun looksLikeWrapper(text: String): Boolean { + val head = text.take(200) + return head.startsWith("#!") || + text.contains("VLC_PLUGIN_PATH") || + text.contains(".vlc-wrapped") + } + + private fun ldLibraryDirs(text: String): List { + val raw = ENV_ASSIGN.find(text, "LD_LIBRARY_PATH") ?: return emptyList() + return raw.split(File.pathSeparator) + .map { it.trim() } + .filter { it.isNotEmpty() } + .map(::File) + } + + private fun wrappedBinary(text: String): File? { + val match = WRAPPED_BIN.find(text) ?: return null + return File(match.groupValues[1]) + } + + private val LIBVLC_NAME = Regex("""libvlc(\.so(\.\d+)*|\.dylib|\.dll)""", RegexOption.IGNORE_CASE) + private val LIBVLCCORE_NAME = Regex("""libvlccore(\.so(\.\d+)*|\.dylib|\.dll)""", RegexOption.IGNORE_CASE) + private val WRAPPED_BIN = Regex("""(/nix/store/[^'"\s]+/bin/[^'"\s]+)""") + + private val VLC_BINARIES = listOf("vlc", "cvlc", "vlc.exe") +} + +private object ENV_ASSIGN { + fun find(text: String, name: String): String? { + val quoted = Regex("""(?:export\s+)?$name=(['"])(.*?)\1""").find(text) + if (quoted != null) return quoted.groupValues[2].trim() + val bare = Regex("""(?:export\s+)?$name=([^\s]+)""").find(text) + return bare?.groupValues?.get(1)?.trim()?.trim('\'', '"') + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/NixLibVlcDirectoryProvider.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/NixLibVlcDirectoryProvider.kt new file mode 100644 index 00000000..655258c9 --- /dev/null +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/NixLibVlcDirectoryProvider.kt @@ -0,0 +1,18 @@ +package capital.yuri.yuriplayer.desktop.player + +import uk.co.caprica.vlcj.factory.discovery.provider.DiscoveryDirectoryProvider + +/** + * vlcj ServiceLoader hook. Default Linux providers only search FHS paths; + * this one feeds NixOS / Guix / PATH-resolved VLC lib dirs in first. + */ +class NixLibVlcDirectoryProvider : DiscoveryDirectoryProvider { + override fun priority(): Int = 1_000 + + override fun supported(): Boolean = true + + override fun directories(): Array { + val home = LibVlcLocator.find() ?: return emptyArray() + return arrayOf(home.libDir.absolutePath) + } +} diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt index fd68f336..42228b68 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/player/VlcjPlaybackEngine.kt @@ -51,11 +51,14 @@ class VlcjPlaybackEngine : PlaybackEngine { init { val discovered = NativeDiscovery().discover() var created: Result = Result.failure(IllegalStateException("not attempted")) - repeat(FACTORY_RETRIES) { attempt -> - created = runCatching { MediaPlayerFactory(*FACTORY_ARGS) } - if (created.isSuccess) return@repeat - if (attempt < FACTORY_RETRIES - 1) { - Thread.sleep(FACTORY_RETRY_DELAY_MS) + val argSets = listOf(FACTORY_ARGS, FACTORY_ARGS_MINIMAL) + outer@ for (args in argSets) { + repeat(FACTORY_RETRIES) { attempt -> + created = runCatching { MediaPlayerFactory(*args) } + if (created.isSuccess) break@outer + if (attempt < FACTORY_RETRIES - 1) { + Thread.sleep(FACTORY_RETRY_DELAY_MS) + } } } factory = created.getOrNull() @@ -64,11 +67,7 @@ class VlcjPlaybackEngine : PlaybackEngine { factory == null -> { val cause = created.exceptionOrNull() val detail = cause?.message ?: cause?.cause?.message - when { - detail != null -> "LibVLC failed to start: $detail" - !discovered -> "LibVLC not found. Install VLC (libvlc) and restart." - else -> "Could not start LibVLC." - } + describeNativeFailure(detail, discovered) } player == null -> "LibVLC opened but could not create a player." else -> null @@ -356,7 +355,22 @@ class VlcjPlaybackEngine : PlaybackEngine { "--http-user-agent=YuriPlayer/1.0", "--aout=any" ) + /** Last-ditch: skip options older VLC builds may reject. */ + private val FACTORY_ARGS_MINIMAL = arrayOf("--no-video") private const val FACTORY_RETRIES = 3 private const val FACTORY_RETRY_DELAY_MS = 300L + + internal fun describeNativeFailure(detail: String?, discovered: Boolean): String { + val nixHint = if (LibVlcBootstrap.isNixOs()) { + " On NixOS add `vlc` to environment.systemPackages (or home.packages) and restart, or set YURI_LIBVLC to the directory that contains libvlc.so (usually /nix/store/-vlc-*/lib)." + } else { + " Install VLC / libvlc, or set YURI_LIBVLC to the folder that contains libvlc." + } + return when { + detail != null -> "LibVLC failed to start: $detail.$nixHint" + !discovered -> "LibVLC not found.$nixHint" + else -> "Could not start LibVLC.$nixHint" + } + } } } diff --git a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt index 2296d605..c70e14be 100644 --- a/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt +++ b/desktop/src/main/kotlin/capital/yuri/yuriplayer/desktop/ui/DesktopSettings.kt @@ -110,7 +110,11 @@ private fun PlaybackPane() { color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f) ) Spacer(Modifier.height(12.dp)) - Text("LibVLC — bundled on Windows/macOS, system VLC on Linux packages.", color = MaterialTheme.colorScheme.onSurface) + Text( + "LibVLC — bundled on Windows/macOS, system VLC on Linux. " + + "NixOS: add vlc to systemPackages (or set YURI_LIBVLC to the lib dir).", + color = MaterialTheme.colorScheme.onSurface + ) } } diff --git a/desktop/src/main/resources/META-INF/services/uk.co.caprica.vlcj.factory.discovery.provider.DiscoveryDirectoryProvider b/desktop/src/main/resources/META-INF/services/uk.co.caprica.vlcj.factory.discovery.provider.DiscoveryDirectoryProvider new file mode 100644 index 00000000..f132458f --- /dev/null +++ b/desktop/src/main/resources/META-INF/services/uk.co.caprica.vlcj.factory.discovery.provider.DiscoveryDirectoryProvider @@ -0,0 +1 @@ +capital.yuri.yuriplayer.desktop.player.NixLibVlcDirectoryProvider diff --git a/desktop/src/test/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcLocatorTest.kt b/desktop/src/test/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcLocatorTest.kt new file mode 100644 index 00000000..8bd6e998 --- /dev/null +++ b/desktop/src/test/kotlin/capital/yuri/yuriplayer/desktop/player/LibVlcLocatorTest.kt @@ -0,0 +1,69 @@ +package capital.yuri.yuriplayer.desktop.player + +import java.io.File +import kotlin.io.path.createTempDirectory +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class LibVlcLocatorTest { + + @Test + fun recognizesNixAndFhsLibraryNames() { + assertTrue( + LibVlcLocator.looksLikeLibDir(listOf("libvlc.so.5", "libvlccore.so.9", "vlc")) + ) + assertTrue( + LibVlcLocator.looksLikeLibDir(listOf("libvlc.dll", "libvlccore.dll", "plugins")) + ) + assertTrue( + LibVlcLocator.looksLikeLibDir(listOf("libvlc.dylib", "libvlccore.dylib")) + ) + assertFalse(LibVlcLocator.looksLikeLibDir(listOf("libvlc.so.5"))) + } + + @Test + fun parsesNixWrapProgramScript() { + val text = """ + #! /nix/store/aaaa-bash-5.2/bin/bash -e + export PATH='/nix/store/bbbb-coreutils/bin' + export VLC_PLUGIN_PATH='/nix/store/cccc-vlc-3.0.21/lib/vlc/plugins' + export LD_LIBRARY_PATH='/nix/store/cccc-vlc-3.0.21/lib:/nix/store/dddd-lib/lib' + exec -a "$0" /nix/store/cccc-vlc-3.0.21/bin/.vlc-wrapped "$@" + """.trimIndent() + val plugin = LibVlcLocator.pluginPathFromWrapper(text) + assertEquals("/nix/store/cccc-vlc-3.0.21/lib/vlc/plugins", plugin) + } + + @Test + fun libDirFromWrapperResolvesExistingLayout() { + val root = createTempDirectory("vlc-nix").toFile() + try { + val lib = File(root, "lib").apply { mkdirs() } + File(lib, "libvlc.so.5").writeText("") + File(lib, "libvlccore.so.9").writeText("") + File(lib, "vlc/plugins").mkdirs() + val text = """ + #! /nix/store/aaaa-bash/bin/bash -e + export VLC_PLUGIN_PATH='${File(lib, "vlc/plugins").absolutePath}' + export LD_LIBRARY_PATH='${lib.absolutePath}' + exec -a "${'$'}0" ${File(root, "bin/.vlc-wrapped").absolutePath} + """.trimIndent() + val found = LibVlcLocator.libDirFromWrapper(text) + assertEquals(lib.canonicalFile, found?.canonicalFile) + } finally { + root.deleteRecursively() + } + } + + @Test + fun describeNativeFailureMentionsOverride() { + val msg = VlcjPlaybackEngine.describeNativeFailure( + "Failed to properly initialise the native library", + discovered = false + ) + assertTrue(msg.contains("YURI_LIBVLC")) + assertTrue(msg.contains("Failed to properly initialise the native library")) + } +}
  • !zkrf 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%0D^Cwd^hR}9_nkfj9+CQd9XD=4d&QZ}WuU~)@W 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

  • !zkrf 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%0D^Cwd^hR}9_nkfj9+CQd9XD=4d&QZ}WuU~)@W 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