diff --git a/.tmp-gradle-run/escalated/gradle-run/e76e2be01f9477bf3dae4d4f5582546f.finished b/.tmp-gradle-run/escalated/gradle-run/e76e2be01f9477bf3dae4d4f5582546f.finished
new file mode 100644
index 0000000..2805836
--- /dev/null
+++ b/.tmp-gradle-run/escalated/gradle-run/e76e2be01f9477bf3dae4d4f5582546f.finished
@@ -0,0 +1 @@
+e76e2be01f9477bf3dae4d4f5582546f
diff --git a/.tmp-gradle-run/escalated/gradle-run/e76e2be01f9477bf3dae4d4f5582546f.lock b/.tmp-gradle-run/escalated/gradle-run/e76e2be01f9477bf3dae4d4f5582546f.lock
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/.tmp-gradle-run/escalated/gradle-run/e76e2be01f9477bf3dae4d4f5582546f.lock differ
diff --git a/.tmp-gradle-run/escalated/hsperfdata_Admin/25704 b/.tmp-gradle-run/escalated/hsperfdata_Admin/25704
new file mode 100644
index 0000000..92ddd7b
Binary files /dev/null and b/.tmp-gradle-run/escalated/hsperfdata_Admin/25704 differ
diff --git a/.tmp-gradle-run/escalated/hsperfdata_Admin/4000 b/.tmp-gradle-run/escalated/hsperfdata_Admin/4000
new file mode 100644
index 0000000..8e7115a
Binary files /dev/null and b/.tmp-gradle-run/escalated/hsperfdata_Admin/4000 differ
diff --git a/.tmp-gradle-run/escalated/jna-63116079/jna4079305537621798898.dll b/.tmp-gradle-run/escalated/jna-63116079/jna4079305537621798898.dll
new file mode 100644
index 0000000..bcca391
Binary files /dev/null and b/.tmp-gradle-run/escalated/jna-63116079/jna4079305537621798898.dll differ
diff --git a/.tmp-gradle-run/escalated/jna-63116079/jna9392113226132068809.dll b/.tmp-gradle-run/escalated/jna-63116079/jna9392113226132068809.dll
new file mode 100644
index 0000000..bcca391
Binary files /dev/null and b/.tmp-gradle-run/escalated/jna-63116079/jna9392113226132068809.dll differ
diff --git a/.tmp-gradle-run/escalated/jna-63116079/jna9392113226132068809.dll.x b/.tmp-gradle-run/escalated/jna-63116079/jna9392113226132068809.dll.x
new file mode 100644
index 0000000..e69de29
diff --git a/.tmp-gradle-run/escalated/kotlin-compiler-in-emby-14545020421804070614.alive b/.tmp-gradle-run/escalated/kotlin-compiler-in-emby-14545020421804070614.alive
new file mode 100644
index 0000000..e69de29
diff --git a/.tmp-gradle-run/escalated/kotlin-daemon.2026-08-22.06-26-48-841.00.log b/.tmp-gradle-run/escalated/kotlin-daemon.2026-08-22.06-26-48-841.00.log
new file mode 100644
index 0000000..a698037
--- /dev/null
+++ b/.tmp-gradle-run/escalated/kotlin-daemon.2026-08-22.06-26-48-841.00.log
@@ -0,0 +1,12 @@
+2026-08-22 06:26:48.954 [daemon] INFO: Kotlin compiler daemon version 2.2.10-release-430
+2026-08-22 06:26:49.155 [daemon] INFO: daemon JVM args: -Djava.awt.headless=true -Djava.rmi.server.hostname=127.0.0.1 -Xmx2048m -XX:ReservedCodeCacheSize=320m -Dkotlin.environment.keepalive -ea -XX:+UseCodeCacheFlushing -XX:+UseParallelGC -Dkotlin.daemon.initiator.marker.file=C:\Users\Matt\emby-client\.tmp-gradle-run\escalated\kotlin-compiler-in-emby-14545020421804070614.alive --add-exports=java.base/sun.nio.ch=ALL-UNNAMED
+2026-08-22 06:26:49.201 [daemon] INFO: daemon args: --daemon-runFilesPath C:\Users\Matt\AppData\Local\kotlin\daemon --daemon-autoshutdownIdleSeconds=7200 --daemon-compilerClasspath C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-compiler-embeddable\2.2.10\6882d39ef03b18879c1a5d2a83c2212361bed5fe\kotlin-compiler-embeddable-2.2.10.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib\2.2.10\30de6faa127a4a012db8e71bf1b9c0a99b1402b2\kotlin-stdlib-2.2.10.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-script-runtime\2.2.10\712f9bc08f378c13a4a00f0bbb28c76e59183e83\kotlin-script-runtime-2.2.10.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-reflect\1.6.10\1cbe9c92c12a94eea200d23c2bbaedaf3daf5132\kotlin-reflect-1.6.10.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-daemon-embeddable\2.2.10\212fd3c2c6e4e5d0f4db3c4191233e2b622380cc\kotlin-daemon-embeddable-2.2.10.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlinx\kotlinx-coroutines-core-jvm\1.8.0\ac1dc37a30a93150b704022f8d895ee1bd3a36b3\kotlinx-coroutines-core-jvm-1.8.0.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains\annotations\13.0\919f0dfe192fb4e063e7dacadee7f8bb9a2672a9\annotations-13.0.jar
+2026-08-22 06:26:49.320 [daemon] INFO: starting daemon
+2026-08-22 06:26:49.753 [compiler] INFO: Registered a client alive file: C:\Users\Matt\emby-client\.tmp-gradle-run\escalated\kotlin-compiler-in-emby-14545020421804070614.alive
+2026-08-22 06:26:49.754 [compiler] INFO: Periodic liveness check activities configured
+2026-08-22 06:26:49.758 [daemon] INFO: daemon is listening on port: 17097
+2026-08-22 06:26:49.763 [compiler] INFO: initiate elections
+2026-08-22 06:26:50.435 [compiler] INFO: getDaemonJVMOptions: DaemonJVMOptions(maxMemory=2048m, maxMetaspaceSize=, reservedCodeCacheSize=320m, jvmParams=[-Djava.awt.headless=true, -Djava.rmi.server.hostname=127.0.0.1, -Dkotlin.environment.keepalive, -ea, -XX:+UseCodeCacheFlushing, -XX:+UseParallelGC, -Dkotlin.daemon.initiator.marker.file=C:\Users\Matt\emby-client\.tmp-gradle-run\escalated\kotlin-compiler-in-emby-14545020421804070614.alive, --add-exports=java.base/sun.nio.ch=ALL-UNNAMED, Dkotlin.environment.keepalive, ea])
+2026-08-22 06:26:50.450 [compiler] INFO: Registered a client alive file: C:\Users\Matt\emby-client\.tmp-gradle-run\escalated\kotlin-compiler-in-emby-14545020421804070614.alive
+2026-08-22 06:26:50.455 [compiler] INFO: leased a new session 1, session alive file: C:\Users\Matt\emby-client\.kotlin\sessions\kotlin-compiler-11810756879782999173.salive
+2026-08-22 06:26:50.679 [compiler] INFO: Starting compilation with args: -Xallow-no-source-files -classpath C:\Users\Matt\AppData\Local\Android\Sdk\platforms\android-37.0\android.jar;C:\Users\Matt\AppData\Local\Android\Sdk\build-tools\36.0.0\core-lambda-stubs.jar;C:\Users\Matt\emby-client\app\build\intermediates\compile_r_class_jar\debug\generateDebugRFile\R.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\fcd33e72a0f24c5d5ff7de2c496a3472\transformed\tv-material-1.1.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\ef05ba05310ee15495d8113051398841\transformed\coil-compose-2.7.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\7ad293321b1532bb2660224d3abfe0ca\transformed\coil-compose-base-2.7.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\f6c102c3d337ae006351dbccc5862a8d\transformed\animation-core-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\52e5d0bb5d24075c4941608ebc281994\transformed\animation-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\378189ed27d712920ae32c5c4f7f5679\transformed\foundation-layout-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\9a5fe74ff49b56e408063f2ff8640e0d\transformed\foundation-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\2942c132ece0e27bee2c67202aa66e85\transformed\ui-tooling-data-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\1a7f1832449474f46b4932d8cbffd621\transformed\ui-unit-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\62c2b2dc69e915f9cba5b6ab8b544104\transformed\ui-geometry-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\566887c084caaf13cbcd2f062cab35c9\transformed\ui-util-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\7477cc8e8242deea6db05821dea84a5f\transformed\ui-text-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\a5b23bdae9677c807a0c669802b8fd6b\transformed\ui-graphics-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\1aaaf133bb0649b46f01704f896c584c\transformed\ui-tooling-preview-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\08da316e556b4ad4a7c4d445859b5df2\transformed\ui-tooling-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\8b1230e0134613d5da1b3fc8ca6d2922\transformed\material-icons-extended-release-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\2c94f99705994c4d7bc1ed82e444b196\transformed\material-icons-core-release-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\9fe0a77b492008f3a34a8977cedd1a7c\transformed\ui-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\5d9abe8ed55b4421eceb0fc006d46cd6\transformed\ui-test-manifest-1.11.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\ba2120f8063ff4462426152f0cdb27f0\transformed\activity-1.13.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\7bec91aa2f38b98a628b79f24b87d2a2\transformed\activity-compose-1.13.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\96e3b5615e0a210f93f0caec5ef024e7\transformed\work-runtime-ktx-2.11.2-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\ef867c2232cbdc796efa9b077ce63119\transformed\work-runtime-2.11.2-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\574995c386bd33e3ed7ed2b94a935866\transformed\tvprovider-1.1.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\eb7be4bf625539c638d624ca33b4032e\transformed\lifecycle-livedata-core-ktx-2.11.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\359fd2579e5e29831a6090184b86aa66\transformed\lifecycle-livedata-core-2.11.0-api.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\androidx.lifecycle\lifecycle-common-jvm\2.11.0\2c4f7d92f1fd723121d394c93455f50158403ad9\lifecycle-common-jvm-2.11.0.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\6591c3fb11f423b821bb096f41c499f3\transformed\lifecycle-viewmodel-savedstate-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\895b7630969bba367a4b1c5e12124895\transformed\core-ktx-1.18.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\582b2ac83812cff66db9a4d73ab5c6bb\transformed\core-1.18.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\e583c44ab1eeec8e706ebeb617629782\transformed\coil-2.7.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\fb28eeb249c82bb1e5d24f5ec8ff5a62\transformed\coil-base-2.7.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\f05bbdb281a7d4f2c6d728ab1437396d\transformed\lifecycle-runtime-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\ac7e039a805f07906a8b54a7e72d92f7\transformed\lifecycle-viewmodel-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\cf43c9ba4f04feb1af2776d42d941a16\transformed\lifecycle-livedata-2.11.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\a177a8a04b2226cd1aa0ce411818c4a3\transformed\lifecycle-runtime-compose-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\766bc57201133b91446c725b40463b09\transformed\lifecycle-viewmodel-compose-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\66105a40f5a431d14ca76d737a595689\transformed\lifecycle-runtime-ktx-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\f3095c0bcb4b9931e29651d3789f3d18\transformed\lifecycle-process-2.11.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\e874e3c919968417a54eb38016b94851\transformed\navigationevent-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\56fc4a76d72e65150ec9b998ab60e909\transformed\navigationevent-compose-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\d6febd1a2f6849d2e6c0fcdffb22a704\transformed\runtime-annotation-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\bd9f2284b6fd8746fe4217be7a859be7\transformed\runtime-retain-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\ea979b01b0e53ab83254bb3c2afaed44\transformed\runtime-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\7d491c83456b76ed261b6248888ea36b\transformed\runtime-saveable-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\963179fbcc1126e664ceacb523152cc6\transformed\savedstate-compose-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\2526b9bc87fe714b70dbc94e560a72e4\transformed\savedstate-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\276366275336425799df9b70b4c26c34\transformed\savedstate-ktx-1.4.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\c19d11737aeedf5cd4e7c9bc87406b95\transformed\icons-lucide-cmp-debug-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\09add3c03b72abbfc4ff2d3d6c7541a3\transformed\metrics-performance-1.0.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\d22c9dcc74d1e5d42c84e0d152428b05\transformed\tracing-1.2.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\51429d69cf8ecb05d353b3e4bcfea176\transformed\tracing-ktx-1.2.0-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\19605f87701560d9f44e22695123647c\transformed\media3-exoplayer-hls-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\b105b83aa8ce22a3de74a3a1a210d9ee\transformed\media3-ffmpeg-decoder-1.9.0+1-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\c82a73d80b5dcab9e3aa978dbe9b49d1\transformed\media3-exoplayer-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\0e6991b0ae13e5ce80ccb94d758af990\transformed\icons-font-awesome-solid-cmp-debug-api.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\com.squareup.okhttp3\logging-interceptor\4.12.0\e922c1f14d365c0f2bed140cc0825e18462c2778\logging-interceptor-4.12.0.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\com.jakewharton.retrofit\retrofit2-kotlinx-serialization-converter\1.0.0\99f70a2daf3256f048893c79a2b795f476f1789f\retrofit2-kotlinx-serialization-converter-1.0.0.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\com.squareup.retrofit2\retrofit\2.11.0\6ca8c6caf842271f3232e075519fe04081ef7069\retrofit-2.11.0.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\09b4aad7158d293bd1591887a8d43778\transformed\media3-datasource-okhttp-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\com.squareup.okhttp3\okhttp\4.12.0\2f4525d4a200e97e1b87449c2cd9bd2e25b7e8cd\okhttp-4.12.0.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib-jdk8\1.8.21\67f57e154437cd9e6e9cf368394b95814836ff88\kotlin-stdlib-jdk8-1.8.21.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlinx\kotlinx-serialization-core-jvm\1.7.3\1f226780b845ff9206474c05159245d861556249\kotlinx-serialization-core-jvm-1.7.3.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlinx\kotlinx-serialization-json-jvm\1.7.3\6701e8c68d9e82387ce72ee96e8ddf058208d58f\kotlinx-serialization-json-jvm-1.7.3.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\androidx.datastore\datastore-core-okio-jvm\1.2.1\16e01a0603ce8cf206b21a304ff1ded7a9bff1da\datastore-core-okio-jvm-1.2.1.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\7c62f251f9c337730d3507248dc7a14b\transformed\datastore-core-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\0e20609b7a5303b553fba77891b0ed6b\transformed\datastore-preferences-core-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\1aa6c4c0f40078d4ce7191bfc4933ed5\transformed\datastore-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\8244a188ff03dafad052672fc5c3c797\transformed\datastore-preferences-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\773860ff5b6ac31a51eafe8e0f1ccf1e\transformed\core-viewtree-1.0.0-api.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlinx\kotlinx-coroutines-core-jvm\1.9.0\9beade4c1c1569e4f36cbd2c37e02e3e41502601\kotlinx-coroutines-core-jvm-1.9.0.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlinx\kotlinx-coroutines-android\1.9.0\6cf3b8df5a2ce8c6b0dffe55657a827c405ee2fe\kotlinx-coroutines-android-1.9.0.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\e69e161091c198a6ed2d314bab8c0b2b\transformed\media3-ui-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\72268b58d1cc7ec684064780ae75bff4\transformed\media3-extractor-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\a42431b82947ec35d737ade7b816cc06\transformed\media3-container-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\78d8c9b0e11b9c780cefb5bd368cc058\transformed\media3-datasource-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\fe310b25b0f2be5d2aa4acc0f8b26a8d\transformed\media3-decoder-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\8e96d8a22e15e9c9c06ea63b91b67df9\transformed\media3-database-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\213f8391964996895059935c91de1b44\transformed\media3-common-1.9.4-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\416b63a7e1e29134f2be962586a8d21b\transformed\annotation-experimental-1.4.1-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\50d0091183a9731c356aee45fa99b1c1\transformed\versionedparcelable-1.1.1-api.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\ceee13b578c114d6ab3143046ec48e8d\transformed\core-runtime-2.2.0-api.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\androidx.arch.core\core-common\2.2.0\5e1b8b81dfd5f52c56a8d53b18ca759c19a301f3\core-common-2.2.0.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\androidx.collection\collection-jvm\1.5.0\7ba2c69414d46ebc2dd76598bdd0a75c54281a57\collection-jvm-1.5.0.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\androidx.annotation\annotation-jvm\1.9.1\b17951747e38bf3986a24431b9ba0d039958aa5f\annotation-jvm-1.9.1.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib-jdk7\1.8.21\7473b8cd3c0ef9932345baf569bc398e8a717046\kotlin-stdlib-jdk7-1.8.21.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\c4d56c67f744eed2c1d2fa33cc09d4e5\transformed\icons-font-awesome-cmp-debug-api.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\com.squareup.okio\okio-jvm\3.9.1\79696e3f4ff05d41ba2b3dc68adb5808a2e5dc53\okio-jvm-3.9.1.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-stdlib\2.2.21\fa374a986e128314c3db00a20aae55f72a258511\kotlin-stdlib-2.2.21.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\98097b476cfbb491663c2e2453aefce7\transformed\profileinstaller-1.4.1-api.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains\annotations\23.0.0\8cc20c07506ec18e0834947b84a864bfc094484e\annotations-23.0.0.jar;C:\Users\Matt\.gradle\caches\9.5.0\transforms\ed5d42cd74a99179745b5a1c59d63761\transformed\startup-runtime-1.1.1-api.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\com.google.guava\guava\33.3.1-android\36162b3b4e9df67dae90db62a2c54616ca42c1bd\guava-33.3.1-android.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\com.google.guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\b421526c5f297295adef1c886e5246c39d4ac629\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jspecify\jspecify\1.0.0\7425a601c1c7ec76645a78d22b8c6a627edee507\jspecify-1.0.0.jar;C:\Users\Matt\.gradle\caches\modules-2\files-2.1\com.google.guava\failureaccess\1.0.2\c4a06a64e650562f30b7bf9aaec1bfed43aca12b\failureaccess-1.0.2.jar -d C:\Users\Matt\emby-client\app\build\tmp\kotlin-classes\debug -jvm-target 17 -module-name app_debug -no-jdk -no-reflect -no-stdlib -Xplugin=C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-compose-compiler-plugin-embeddable\2.2.10\fd3cd12d4e9702cfcd731120564af86814cf95b7\kotlin-compose-compiler-plugin-embeddable-2.2.10.jar,C:\Users\Matt\.gradle\caches\modules-2\files-2.1\org.jetbrains.kotlin\kotlin-serialization-compiler-plugin-embeddable\2.2.10\8410bb1b4213e690e29954abbba401af599afafc\kotlin-serialization-compiler-plugin-embeddable-2.2.10.jar -P plugin:androidx.compose.compiler.plugins.kotlin:sourceInformation=true,plugin:androidx.compose.compiler.plugins.kotlin:traceMarkersEnabled=true -Xuse-inline-scopes-numbers -Xallow-unstable-dependencies C:\Users\Matt\emby-client\app\build\generated\source\buildConfig\debug\com\ponzischeme89\memby\BuildConfig.java C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\analytics\JourneyAnalytics.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\analytics\JourneySink.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\analytics\JourneyTracker.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\analytics\PlaybackEntryPoint.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\analytics\PlaybackJourney.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\analytics\RowAnalytics.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\CastMetadata.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\ClientTrailerResolver.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\ContinueWatching.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\Credits.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\EmbyRepository.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\GenreBrowse.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\ImageCache.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\Intro.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\LocalDays.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\MaintenanceMonitor.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\model\EmbyModels.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\model\GatewayModels.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\model\Ratings.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\playback\AudioCapabilityProbe.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\playback\DeviceAudioCapabilities.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\playback\DevicePlaybackCapabilities.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\PreferencesSync.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\remote\EmbyApi.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\remote\EmbyServiceFactory.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\remote\GatewayApi.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\remote\GatewayServiceFactory.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\remote\HttpStack.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\remote\StreamWarmer.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\remote\TrickplayClient.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\remoteconfig\MembyRemoteConfig.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\SearchRepository.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\SeekPreference.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\SeriesPace.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\ServerConfig.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\SettingsStore.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\SkipIntroPreference.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\SubtitleSupport.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\Themes.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\ThemeSync.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\TrailerSupport.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\Trickplay.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\data\UserPreferences.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\diagnostics\MembyDiagnostics.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\MembyApp.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\performance\PerformanceMonitor.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\performance\StartupTrace.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\screensaver\MembyDreamService.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ServiceLocator.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\tvhome\HomeChannelPublisher.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\tvhome\TvDeepLinkActivity.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\tvhome\TvDeepLinks.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\tvhome\TvHomeIntegrationService.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\tvhome\TvHomeProgram.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\tvhome\WatchNextPublisher.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\alerts\AlertsFormat.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\alerts\AlertsPage.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\alerts\AlertsPaging.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\AppRoot.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\calendar\CalendarAgenda.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\calendar\CalendarGrid.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\calendar\CalendarScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\calendar\CalendarViewModel.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\components\FocusScaleContainer.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\components\media\ContinueWatchingCard.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\components\media\LandscapeCard.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\components\media\MediaBadges.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\components\media\MediaCardMetadata.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\components\media\MediaCardSizing.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\components\media\MediaLoading.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\components\media\MediaResultCard.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\components\media\PosterCard.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\detail\AiringNotice.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\detail\CastGrid.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\detail\DetailCardFocus.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\detail\DetailFacts.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\detail\DetailHeroPhases.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\detail\DetailPosition.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\detail\DetailTabs.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\DetailPageComponents.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\EmbyOutageBanner.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\EpisodeDetailsOverlay.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\ExitMembyScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\genre\GenreBrowseScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\genre\GenreBrowseViewModel.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\genre\GenreCategories.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\genre\GenrePersonalisation.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\home\HomeMetadataPanel.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\home\HomeNavigation.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\home\HomeQuickActions.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\home\HomeRow.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\home\HomeRowBrowse.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\HomeGreeting.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\HomeMovieHero.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\HomeOverlays.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\HomeScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\HomeViewModel.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\InstallPermissionScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\LaunchPreroll.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\ListKeys.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\MainActivity.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\MaintenanceScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\MediaDetailsOverlay.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\MediaReportControl.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\MembyButtons.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\MetadataHero.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\MyShowsNotifications.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\Onboarding.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\AudioPassthrough.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\CastPanel.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\CreditsCountdown.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\CreditsSpeed.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\NextEpisodePreloader.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\NextUpPipeline.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PlaybackIdentity.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PlaybackIdentityLogo.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PlaybackRecovery.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PlaybackStopWorker.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PlaybackTrace.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PlaybackTraceSections.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PlayerActivity.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PlayerEngine.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PreloadPlan.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PrerollCountdownView.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\PrerollPreloader.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\SeekControls.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\SkipIntroCountdownView.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\SubtitleMenu.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\player\TrickplayPreview.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\PreviewSupport.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\profiles\ProfileChooser.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\ProfileViewModelStoreOwner.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\QuickActionsNavigation.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\RadarrMovieDetailsOverlay.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\RatingsStrip.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\RemoteLongPress.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\requests\RequestCards.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\requests\RequestPresentation.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\requests\RequestsScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\requests\RequestsViewModel.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\ResumableMediaCard.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\RowNavigation.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\screensaver\EmbyAppLauncher.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\screensaver\ScreensaverActivity.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\screensaver\ScreensaverContent.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\search\components\SearchGenres.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\search\components\SearchResults.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\search\SearchScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\search\SearchViewModel.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\seasonal\SeasonalDecorations.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\SeriesDetailsOverlay.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\ServiceAlertBanner.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\settings\SettingsSheet.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\settings\VersionHistory.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\setup\SignInContent.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\theme\DesignTokens.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\theme\MembyIconPackFontAwesome.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\theme\MembyIconPackLucide.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\theme\MembyIconPacks.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\theme\MembyIcons.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\theme\Theme.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\TitleLogo.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\UpdateScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\UserQuickActions.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\UserSwitcherNavigation.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\viewers\ViewerControls.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\viewers\ViewerEditing.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\viewers\ViewerManageScreen.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\viewers\ViewerNameEntry.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\viewers\ViewerPicker.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\viewers\ViewerSelection.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\WelcomeQuotes.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\ui\whatsnew\WhatsNew.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\InstallPermission.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\InstallResultReceiver.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\RequiredUpdateGuard.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\RequiredUpdateSignal.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\RequiredUpdateState.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\ServerUpdateService.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\UpdateChecker.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\UpdateCheckSchedule.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\UpdateModels.kt C:\Users\Matt\emby-client\app\src\main\java\com\ponzischeme89\memby\update\UpdateRecoveryReceiver.kt
diff --git a/.tmp-gradle-run/escalated/kotlin-daemon.2026-08-22.06-26-48-841.00.log.lck b/.tmp-gradle-run/escalated/kotlin-daemon.2026-08-22.06-26-48-841.00.log.lck
new file mode 100644
index 0000000..e69de29
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 566a3c9..65fb783 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,78 @@
+## 0.2.98 - 2026-08-22
+- v298 closes the following issues: 116 (Feature: Integrate Memby with Android TV Home Screen), 117 (Improve Row Navigation & Add “View All” Actions) and 115 (Add ratings strip to movie continue watching).
+- Added: Continue Watching support on the Google TV launcher homescreen (116)
+- Added: Ratings strip to continue watching cards (115)
+- Added: Buttons to rows which can take you back to main genre (117).
+- Improved: Cleaned up genres chips row on search page.
+- Improved: Switched cards to list cards for Search.
+- Fixed: RequeststCards.kt shares its component with Search.
+- Improved: Bug fixes & improvements to ResumeableMediaCard.kt, ContinueWatchingCard.kt
+- Updated: Server updated to v0.1.64
+- Publishes partially watched movies/episodes and Next Up episodes through Android TV’s native Watch Next API.
+- Updates launcher progress from existing playback reports.
+- Removes completed, stale, deleted, duplicate, signed-out, or previous-user entries.
+- Launcher cards deep-link through a validated activity directly into PlayerActivity, resuming from current Emby/Memby state.
+- Uses appropriate episode, series, season, runtime, description, and backdrop/poster metadata.
+- Keeps provider failures and offline reconciliation completely supplementary to playback.
+- Added an isolated tvhome architecture:
+- app/src/main/java/com/ponzischeme89/memby/tvhome/TvHomeIntegrationService.kt
+- app/src/main/java/com/ponzischeme89/memby/tvhome/WatchNextPublisher.kt
+- app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinkActivity.kt
+- app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinks.kt
+- app/src/main/java/com/ponzischeme89/memby/tvhome/HomeChannelPublisher.kt
+
+## 0.2.97 - 2026-08-21
+- Improved: Modularised MembyButtons, ProfileChooser, SettingsSheet, ViewerControls
+- Added: Traliers no longer show controls that are outside of our control (e.g. Youtube)
+- Improved: Simplified the Emby exit screen prompt. Moved to EmbyMembyScreen.kt
+
+## 0.2.96 - 2026-08-21
+- Improved: Refactor EmbyRepo
+- Improved: HomeNavigation.kt
+- Added: ServiceLocator.kt
+
+## 0.2.95 - 2026-08-21
+- Improved: MetadataHero.kt
+- Added: MetadataHero.kt opts to Preferences
+
+## 0.2.94 - 2026-08-21
+- Improved: MetadataHero.kt
+
+## 0.2.93 - 2026-08-21
+- Fixed: Bugs with HomeOverlay
+
## 0.2.92 - 2026-08-21
- Improved: App design / layout tweaks.
- Improved: Many behind the scenes tweaks, bug fixes to make the app run smoother.
- Improved: Gateway updated to version 0.1.63.
+## 0.2.91 - 2026-08-21
+- Fixed: Bugs with HomeComponents/HomeScreen
+- Fixed: Bugs with FocusScaleContainer
+- Improved: LandscapeCard, ContinueWatchingCard, MediaBadges, MediaCardMetadata, MediaCardSizing, MediaLoading, PosterCard
+
+## 0.2.90 - 2026-08-21
+- Fixed: Padding fixes with HomeComponents.kt
+
+## 0.2.89 - 2026-08-21
+- Fixed: Responsive row sizing fixes
+
+## 0.2.88 - 2026-08-21
+- Fixed: More bug fixes to HomeComponents.kt, HomeScreen.kt.
+- Tested: Tests passed with HomeMovieHeroTest.
+
+## 0.2.87 - 2026-08-21
+- Fixed: Bugs with HomeComponents.kt
+- Fixed: Bugs with HomeOverlays.kt
+- Fixed: Bugs with HomeScreen.kt
+
+## 0.2.86 - 2026-08-21
+- Fixed: Bugs with HomeComponents
+
+## 0.2.85 - 2026-08-21
+- Fixed: Bug with LaunchPreroll
+- Fixed: Bug fixes to PrerollPreLoader
+- Fixed: Bug which prevented ResumableMediaCardTest from firing
## 0.2.84 - 2026-08-21
- Improved: Loading and performance of the continue watching row.
diff --git a/CLAUDE.md b/CLAUDE.md
index a8f8763..1475437 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -708,7 +708,9 @@ of a copy, wrong the moment Tracearr corrects a session. It is read two ways:
about a title somebody abandoned, and a feed carrying those is one nobody opens.
**Search** (`ui/search/`) is a two-pane instant-search destination on the rail: a fixed
-6×6 on-screen keyboard on the left, a results grid on the right that updates as you type.
+6×6 on-screen keyboard on the left, with `SearchGenres` above `SearchResults` on the right.
+The results list updates as you type and uses the same shared `MediaResultCard` presentation
+as My Requests; request status and actions remain feature-owned slots around that shared row.
Nothing is ever "submitted". `SearchViewModel` runs one pipeline — `debounce(250)` →
`trim` → `distinctUntilChanged` → `collectLatest { repository.search(it) }` — and
`collectLatest` is the load-bearing part: it cancels the in-flight request, so a slow
@@ -785,17 +787,17 @@ at a time. Things to preserve:
the server is the same judgement from the other side, for an Emby that did not count.
- **A page is appended by its own offset**, never by order of arrival: a response for an
offset already scrolled past, or for a genre the viewer has left, is dropped rather than
- pasted into the middle of the grid. A page that fails part way down keeps what is on
+ pasted into the middle of the list. A page that fails part way down keeps what is on
screen and simply stops paging.
- **The scroll trigger is a `snapshotFlow`, not a composable read.** The last visible index
- changes on every frame of a scroll, and reading it in the body would recompose the grid
+ changes on every frame of a scroll, and reading it in the body would recompose the list
the whole way down a genre — the same rule as the animation one above. It asks
- `LOAD_MORE_ROWS_AHEAD` rows early, since a request that starts when the viewer arrives at
+ several items early, since a request that starts when the viewer arrives at
the end is one they watch.
-- **Something has to take the focus the chip was holding**, because opening a shelf unmounts
- the whole discovery pane. The grid claims it when the first page lands, and an empty or
- failed genre hands it back to the keyboard rather than leaving a television with nothing
- focused.
+- **Genre and result focus targets are separate.** The genre strip stays mounted above the
+ results; a genre result claims its own requester when the first page lands, Back restores
+ the strip, and an empty or failed genre hands focus back deterministically rather than
+ leaving a television with nothing focused.
- **Episodes are excluded on both paths.** An episode inherits its series' genres, so
including them fills a page with twenty entries of one comedy and buries the rest.
- **Movies and TV Series have their own full-width genre browser.** A fixed row of colourful
@@ -862,9 +864,9 @@ at a time. Things to preserve:
missing the route, and search results pasted onto the end of a genre would be nonsense.
Focus is the hard part and is explicit: the leftmost keyboard column goes to the rail, the
-rightmost goes to the results grid, the grid's first column goes back to the *last key
-used* (a `FocusRequester` attached to whichever key that is), and the grid has a
-`focusRestorer`. Back moves results → keyboard → genre → leave, one step per press.
+rightmost goes to Genres, Down enters Search Results, and every result goes back to the
+*last key used* (a `FocusRequester` attached to whichever key that is). Back closes an open
+genre shelf before moving results → keyboard → leave, one step per press.
Physical keyboards and phone-remote apps feed the same state through one
`onPreviewKeyEvent` that consumes only printable characters and backspace — D-pad and Back
must fall through. The voice button needs the `android.speech.RecognitionService` entry in
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 90e0ae8..f6303fd 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -64,7 +64,7 @@ val projectNoticeText =
// A release workflow can derive the app version from its Git tag without editing the
// source tree. Local builds keep using the checked-in default.
-val defaultVersionName = "0.2.97"
+val defaultVersionName = "0.2.98"
val membyVersionName: String =
(project.findProperty("memby.versionName") as String?)
?.trim()
@@ -275,6 +275,8 @@ dependencies {
// Compose for TV
implementation("androidx.tv:tv-material:1.1.0")
+ // Android TV's system-owned Watch Next row and optional app-owned home channels.
+ implementation("androidx.tvprovider:tvprovider:1.1.0")
// Image loading
implementation("io.coil-kt:coil-compose:2.7.0")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 323ff76..45cef05 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,8 @@
+
+
+
+
+
+
+
+
+
+
+
= Build.VERSION_CODES.O
+
+ fun eligibleRows(rows: List): List = rows.filter { row ->
+ row.items.isNotEmpty() && row.kind.lowercase() !in WATCH_NEXT_KINDS
+ }
+
+ private companion object {
+ val WATCH_NEXT_KINDS = setOf("continue", "nextup", "next_up")
+ }
+}
diff --git a/app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinkActivity.kt b/app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinkActivity.kt
new file mode 100644
index 0000000..98754fe
--- /dev/null
+++ b/app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinkActivity.kt
@@ -0,0 +1,61 @@
+package com.ponzischeme89.memby.tvhome
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.lifecycle.lifecycleScope
+import com.ponzischeme89.memby.ServiceLocator
+import com.ponzischeme89.memby.data.analytics.PlaybackEntryPoint
+import com.ponzischeme89.memby.ui.player.PlayerActivity
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeoutOrNull
+
+/** Narrow exported trampoline for launcher-owned deep links; [PlayerActivity] stays private. */
+class TvDeepLinkActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val itemId = TvDeepLinks.itemId(intent.data)
+ val requestedProfile = TvDeepLinks.profileKey(intent.data)
+ if (itemId == null || requestedProfile == null) {
+ finish()
+ return
+ }
+
+ lifecycleScope.launch {
+ val current = withTimeoutOrNull(SETTINGS_TIMEOUT_MS) {
+ ServiceLocator.settings.settingsFlow.first()
+ }
+ val profile = current?.let(::tvHomeProfile)
+ if (profile?.key != requestedProfile) {
+ finish()
+ return@launch
+ }
+
+ val item = runCatching { ServiceLocator.repository.getItemDetails(itemId) }.getOrNull()
+ if (item == null || item.userData?.played == true || (!item.isMovie && !item.isEpisode)) {
+ finish()
+ return@launch
+ }
+ val request = runCatching { ServiceLocator.repository.playbackRequest(item) }.getOrNull()
+ if (request == null) {
+ finish()
+ return@launch
+ }
+ startActivity(
+ PlayerActivity.intent(
+ context = this@TvDeepLinkActivity,
+ request = request,
+ posterUrl = ServiceLocator.repository.primaryUrl(item, maxWidth = 500),
+ backdropUrl = ServiceLocator.repository.backdropUrl(item, maxWidth = 1920)
+ ?: ServiceLocator.repository.primaryUrl(item, maxWidth = 1920),
+ journeySource = PlaybackEntryPoint.TV_HOME.id,
+ ),
+ )
+ finish()
+ }
+ }
+
+ private companion object {
+ const val SETTINGS_TIMEOUT_MS = 5_000L
+ }
+}
diff --git a/app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinks.kt b/app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinks.kt
new file mode 100644
index 0000000..31882e7
--- /dev/null
+++ b/app/src/main/java/com/ponzischeme89/memby/tvhome/TvDeepLinks.kt
@@ -0,0 +1,23 @@
+package com.ponzischeme89.memby.tvhome
+
+import android.net.Uri
+
+internal object TvDeepLinks {
+ fun playback(itemId: String, profileKey: String): Uri = Uri.Builder()
+ .scheme("memby")
+ .authority("play")
+ .appendPath(itemId)
+ .appendQueryParameter("profile", profileKey)
+ .build()
+
+ fun itemId(uri: Uri?): String? = uri
+ ?.takeIf { it.scheme == "memby" && it.host == "play" }
+ ?.pathSegments
+ ?.singleOrNull()
+ ?.takeIf(String::isNotBlank)
+
+ fun profileKey(uri: Uri?): String? = uri
+ ?.takeIf { it.scheme == "memby" && it.host == "play" }
+ ?.getQueryParameter("profile")
+ ?.takeIf(String::isNotBlank)
+}
diff --git a/app/src/main/java/com/ponzischeme89/memby/tvhome/TvHomeIntegrationService.kt b/app/src/main/java/com/ponzischeme89/memby/tvhome/TvHomeIntegrationService.kt
new file mode 100644
index 0000000..25b3cbd
--- /dev/null
+++ b/app/src/main/java/com/ponzischeme89/memby/tvhome/TvHomeIntegrationService.kt
@@ -0,0 +1,141 @@
+package com.ponzischeme89.memby.tvhome
+
+import android.content.Context
+import com.ponzischeme89.memby.data.EmbyRepository
+import com.ponzischeme89.memby.data.PlaybackPosition
+import com.ponzischeme89.memby.data.Settings
+import com.ponzischeme89.memby.data.SettingsStore
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Process-scoped bridge between Memby's repositories and Android TV's launcher provider.
+ * Every operation is best-effort: a missing provider, a launcher defect, or an offline
+ * server can cost a launcher card but can never delay or fail playback.
+ */
+class TvHomeIntegrationService internal constructor(
+ context: Context,
+ private val repository: EmbyRepository,
+ private val settings: SettingsStore,
+) {
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+ private val publisher = WatchNextPublisher(context)
+ private val reconciliation = Mutex()
+ private val latestPositions = ConcurrentHashMap()
+ private val completedItems = ConcurrentHashMap.newKeySet()
+
+ @Volatile
+ private var activeProfile: TvHomeProfile? = null
+ private var started = false
+
+ fun start() {
+ if (started) return
+ started = true
+
+ scope.launch {
+ settings.settingsFlow
+ .map(::tvHomeProfile)
+ .distinctUntilChanged()
+ .collectLatest { profile ->
+ latestPositions.clear()
+ completedItems.clear()
+ activeProfile = profile
+ if (profile == null) {
+ publisher.clear()
+ } else {
+ publisher.clearOtherProfiles(profile.key)
+ // A process may have been created by a launcher-card press. Keep the
+ // supplementary row refresh behind item lookup and stream negotiation.
+ delay(INITIAL_RECONCILE_DELAY_MS)
+ reconcile(profile)
+ }
+ }
+ }
+ scope.launch {
+ repository.playbackPositions.collect { position ->
+ activeProfile?.let { updateProgress(it, position) }
+ }
+ }
+ scope.launch {
+ repository.playbackStops.collect {
+ activeProfile?.let { profile -> reconcile(profile) }
+ }
+ }
+ }
+
+ private suspend fun reconcile(profile: TvHomeProfile) {
+ reconciliation.withLock {
+ if (activeProfile != profile) return
+ val items = runCatching { repository.getContinueWatching(WATCH_NEXT_LIMIT) }
+ .getOrNull()
+ ?: return
+ if (activeProfile != profile) return
+ val serverIds = items.mapTo(mutableSetOf()) { it.id }
+ completedItems.retainAll(serverIds)
+ val now = System.currentTimeMillis()
+ val programmes = items.mapIndexedNotNull { index, item ->
+ if (item.id in completedItems) return@mapIndexedNotNull null
+ val local = latestPositions[item.id]
+ val localTicks = local?.positionMs?.times(10_000L) ?: 0L
+ val currentTicks = item.userData?.playbackPositionTicks ?: 0L
+ val currentItem = if (localTicks > currentTicks) {
+ item.copy(
+ userData = (item.userData ?: com.ponzischeme89.memby.data.model.UserItemData())
+ .copy(playbackPositionTicks = localTicks),
+ )
+ } else {
+ item
+ }
+ tvHomeProgram(
+ item = currentItem,
+ profileKey = profile.key,
+ artworkUrl = repository.backdropUrl(item, maxWidth = 1280)
+ ?: repository.primaryUrl(item, maxWidth = 780),
+ // Preserve the repository's recency order even where a Next Up item has
+ // no LastPlayedDate of its own.
+ nowMs = now - index,
+ )
+ }
+ publisher.reconcile(programmes)
+ }
+ }
+
+ private fun updateProgress(profile: TvHomeProfile, position: PlaybackPosition) {
+ latestPositions[position.itemId] = position
+ if (position.completed) completedItems += position.itemId else completedItems -= position.itemId
+ publisher.updateProgress(
+ profileKey = profile.key,
+ itemId = position.itemId,
+ positionMs = position.positionMs,
+ durationMs = position.durationMs,
+ completed = position.completed,
+ )
+ }
+
+ private companion object {
+ const val WATCH_NEXT_LIMIT = 40
+ const val INITIAL_RECONCILE_DELAY_MS = 15_000L
+ }
+}
+
+internal data class TvHomeProfile(val key: String)
+
+internal fun tvHomeProfile(settings: Settings): TvHomeProfile? {
+ if (!settings.isSignedIn) return null
+ return TvHomeProfile(
+ tvHomeProfileKey(
+ serverUrl = requireNotNull(settings.serverUrl),
+ userId = requireNotNull(settings.userId),
+ viewerId = settings.activeViewerId,
+ ),
+ )
+}
diff --git a/app/src/main/java/com/ponzischeme89/memby/tvhome/TvHomeProgram.kt b/app/src/main/java/com/ponzischeme89/memby/tvhome/TvHomeProgram.kt
new file mode 100644
index 0000000..2996098
--- /dev/null
+++ b/app/src/main/java/com/ponzischeme89/memby/tvhome/TvHomeProgram.kt
@@ -0,0 +1,69 @@
+package com.ponzischeme89.memby.tvhome
+
+import com.ponzischeme89.memby.data.model.BaseItem
+import java.security.MessageDigest
+
+internal enum class TvHomeProgramKind { CONTINUE, NEXT }
+
+internal data class TvHomeProgram(
+ val providerId: String,
+ val itemId: String,
+ val profileKey: String,
+ val kind: TvHomeProgramKind,
+ val mediaType: String,
+ val title: String,
+ val episodeTitle: String? = null,
+ val description: String? = null,
+ val seasonNumber: Int? = null,
+ val episodeNumber: Int? = null,
+ val positionMs: Long = 0L,
+ val durationMs: Long = 0L,
+ val artworkUrl: String? = null,
+ val engagementTimeMs: Long,
+)
+
+internal fun tvHomeProfileKey(serverUrl: String, userId: String, viewerId: String): String {
+ val identity = "${serverUrl.trimEnd('/')}|$userId|$viewerId"
+ return MessageDigest.getInstance("SHA-256")
+ .digest(identity.toByteArray(Charsets.UTF_8))
+ .take(12)
+ .joinToString("") { "%02x".format(it) }
+}
+
+internal fun tvHomeProgram(
+ item: BaseItem,
+ profileKey: String,
+ artworkUrl: String?,
+ nowMs: Long,
+): TvHomeProgram? {
+ if (item.id.isBlank() || item.userData?.played == true) return null
+ if (!item.isMovie && !item.isEpisode) return null
+
+ val positionMs = ((item.userData?.playbackPositionTicks ?: 0L) / 10_000L).coerceAtLeast(0L)
+ val durationMs = ((item.runTimeTicks ?: 0L) / 10_000L).coerceAtLeast(0L)
+ val kind = when {
+ positionMs > 0L -> TvHomeProgramKind.CONTINUE
+ item.isEpisode -> TvHomeProgramKind.NEXT
+ else -> return null
+ }
+ if (kind == TvHomeProgramKind.CONTINUE && durationMs > 0L && positionMs >= durationMs * 95 / 100) {
+ return null
+ }
+
+ return TvHomeProgram(
+ providerId = "memby:$profileKey:${item.id}",
+ itemId = item.id,
+ profileKey = profileKey,
+ kind = kind,
+ mediaType = if (item.isMovie) "movie" else "episode",
+ title = if (item.isEpisode) item.seriesName?.takeIf(String::isNotBlank) ?: item.name else item.name,
+ episodeTitle = item.name.takeIf { item.isEpisode },
+ description = item.overview,
+ seasonNumber = item.parentIndexNumber,
+ episodeNumber = item.indexNumber,
+ positionMs = positionMs,
+ durationMs = durationMs,
+ artworkUrl = artworkUrl,
+ engagementTimeMs = nowMs,
+ )
+}
diff --git a/app/src/main/java/com/ponzischeme89/memby/tvhome/WatchNextPublisher.kt b/app/src/main/java/com/ponzischeme89/memby/tvhome/WatchNextPublisher.kt
new file mode 100644
index 0000000..3fe64be
--- /dev/null
+++ b/app/src/main/java/com/ponzischeme89/memby/tvhome/WatchNextPublisher.kt
@@ -0,0 +1,150 @@
+package com.ponzischeme89.memby.tvhome
+
+import android.content.ContentUris
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.provider.BaseColumns
+import androidx.tvprovider.media.tv.TvContractCompat
+import androidx.tvprovider.media.tv.WatchNextProgram
+
+internal class WatchNextPublisher(context: Context) {
+ private val resolver = context.applicationContext.contentResolver
+
+ val supported: Boolean get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+
+ fun reconcile(programmes: List) {
+ if (!supported) return
+ runCatching {
+ val existing = existingProgrammes()
+ val desired = programmes.associateBy(TvHomeProgram::providerId)
+
+ for ((providerId, rows) in existing) {
+ val programme = desired[providerId]
+ if (programme == null) {
+ rows.forEach(::delete)
+ } else {
+ val keeper = rows.first()
+ resolver.update(keeper.uri, programme.toWatchNext().toContentValues(), null, null)
+ rows.drop(1).forEach(::delete)
+ }
+ }
+ for ((providerId, programme) in desired) {
+ if (providerId !in existing) {
+ resolver.insert(
+ TvContractCompat.WatchNextPrograms.CONTENT_URI,
+ programme.toWatchNext().toContentValues(),
+ )
+ }
+ }
+ }
+ }
+
+ fun updateProgress(profileKey: String, itemId: String, positionMs: Long, durationMs: Long, completed: Boolean) {
+ if (!supported) return
+ runCatching {
+ val providerId = "memby:$profileKey:$itemId"
+ val rows = existingProgrammes()[providerId].orEmpty()
+ if (completed) {
+ rows.forEach(::delete)
+ return@runCatching
+ }
+ rows.firstOrNull()?.let { row ->
+ val values = android.content.ContentValues().apply {
+ put(TvContractCompat.WatchNextPrograms.COLUMN_LAST_PLAYBACK_POSITION_MILLIS, positionMs.toIntMillis())
+ if (durationMs > 0L) {
+ put(TvContractCompat.WatchNextPrograms.COLUMN_DURATION_MILLIS, durationMs.toIntMillis())
+ }
+ put(TvContractCompat.WatchNextPrograms.COLUMN_LAST_ENGAGEMENT_TIME_UTC_MILLIS, System.currentTimeMillis())
+ }
+ resolver.update(row.uri, values, null, null)
+ rows.drop(1).forEach(::delete)
+ }
+ }
+ }
+
+ fun clear() {
+ if (!supported) return
+ runCatching { existingProgrammes().values.flatten().forEach(::delete) }
+ }
+
+ fun clearOtherProfiles(profileKey: String) {
+ if (!supported) return
+ val ownPrefix = "memby:$profileKey:"
+ runCatching {
+ existingProgrammes()
+ .filterKeys { !it.startsWith(ownPrefix) }
+ .values
+ .flatten()
+ .forEach(::delete)
+ }
+ }
+
+ private fun existingProgrammes(): Map> {
+ val found = linkedMapOf>()
+ resolver.query(
+ TvContractCompat.WatchNextPrograms.CONTENT_URI,
+ arrayOf(BaseColumns._ID, TvContractCompat.WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID),
+ null,
+ null,
+ null,
+ )?.use { cursor ->
+ val idColumn = cursor.getColumnIndexOrThrow(BaseColumns._ID)
+ val providerColumn = cursor.getColumnIndexOrThrow(
+ TvContractCompat.WatchNextPrograms.COLUMN_INTERNAL_PROVIDER_ID,
+ )
+ while (cursor.moveToNext()) {
+ val providerId = cursor.getString(providerColumn) ?: continue
+ if (!providerId.startsWith("memby:")) continue
+ val uri = ContentUris.withAppendedId(
+ TvContractCompat.WatchNextPrograms.CONTENT_URI,
+ cursor.getLong(idColumn),
+ )
+ found.getOrPut(providerId) { mutableListOf() } += ExistingProgramme(uri)
+ }
+ }
+ return found
+ }
+
+ private fun delete(programme: ExistingProgramme) {
+ resolver.delete(programme.uri, null, null)
+ }
+
+ private fun TvHomeProgram.toWatchNext(): WatchNextProgram {
+ val builder = WatchNextProgram.Builder()
+ builder.setType(
+ if (mediaType == "movie") {
+ TvContractCompat.PreviewPrograms.TYPE_MOVIE
+ } else {
+ TvContractCompat.PreviewPrograms.TYPE_TV_EPISODE
+ },
+ )
+ builder.setWatchNextType(
+ if (kind == TvHomeProgramKind.CONTINUE) {
+ TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE
+ } else {
+ TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_NEXT
+ },
+ )
+ builder.setTitle(title)
+ builder.setInternalProviderId(providerId)
+ builder.setContentId(itemId)
+ builder.setIntentUri(TvDeepLinks.playback(itemId, profileKey))
+ builder.setLastEngagementTimeUtcMillis(engagementTimeMs)
+
+ description?.takeIf(String::isNotBlank)?.let(builder::setDescription)
+ artworkUrl?.let(Uri::parse)?.let(builder::setPosterArtUri)
+ if (kind == TvHomeProgramKind.CONTINUE) {
+ builder.setLastPlaybackPositionMillis(positionMs.toIntMillis())
+ builder.setDurationMillis(durationMs.toIntMillis())
+ }
+ episodeTitle?.let(builder::setEpisodeTitle)
+ seasonNumber?.let(builder::setSeasonNumber)
+ episodeNumber?.let(builder::setEpisodeNumber)
+ return builder.build()
+ }
+
+ private fun Long.toIntMillis(): Int = coerceIn(0L, Int.MAX_VALUE.toLong()).toInt()
+
+ private data class ExistingProgramme(val uri: Uri)
+}
diff --git a/app/src/main/java/com/ponzischeme89/memby/ui/HomeOverlays.kt b/app/src/main/java/com/ponzischeme89/memby/ui/HomeOverlays.kt
index a386093..eb02d11 100644
--- a/app/src/main/java/com/ponzischeme89/memby/ui/HomeOverlays.kt
+++ b/app/src/main/java/com/ponzischeme89/memby/ui/HomeOverlays.kt
@@ -854,7 +854,7 @@ internal fun ForYouNudgeBanner(
}
/**
- * Turns Emby account-style usernames into a friendlier home-screen name.
+ * Turns Memby account-style usernames into a friendlier home-screen name.
*
* Household accounts commonly use a trailing capital as a disambiguating surname
* initial (PeterC, PaulR). Only that clear camel-case shape is trimmed, so ordinary
diff --git a/app/src/main/java/com/ponzischeme89/memby/ui/HomeScreen.kt b/app/src/main/java/com/ponzischeme89/memby/ui/HomeScreen.kt
index 63c1789..b34970f 100644
--- a/app/src/main/java/com/ponzischeme89/memby/ui/HomeScreen.kt
+++ b/app/src/main/java/com/ponzischeme89/memby/ui/HomeScreen.kt
@@ -285,6 +285,9 @@ internal fun HomeScreen(
var quickMenuTrailerAvailable by remember { mutableStateOf(false) }
var quickMenuRowId by remember { mutableStateOf(null) }
var focusedHomeRowId by remember { mutableStateOf(null) }
+ var rowBrowseTarget by remember { mutableStateOf(null) }
+ var rowBrowseOriginRowId by remember { mutableStateOf(null) }
+ var rowBrowseOriginItemId by remember { mutableStateOf(null) }
var sectionHeroRows by remember(settings.userId) {
mutableStateOf