diff --git a/application/app/build.gradle b/application/app/build.gradle
index 212c3be4ed0a26f943adcd8d4e3176b1cbfddac2..12d2fb90cacf5ef1e1278a7289bb38967df584fa 100644
--- a/application/app/build.gradle
+++ b/application/app/build.gradle
@@ -1,5 +1,7 @@
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
+plugins {
+    id 'com.android.application'
+    id 'kotlin-android'
+}
 
 android {
     compileOptions {
diff --git a/application/donations/build.gradle b/application/donations/build.gradle
index 2d7d1bf3f4d41243b48d70ad932ae29600834160..be14b361533681f48eb75821bf78b9bbd7fd7fa5 100644
--- a/application/donations/build.gradle
+++ b/application/donations/build.gradle
@@ -22,8 +22,10 @@
  *
  */
 
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+}
 
 android {
 
diff --git a/application/live-plot-graph/build.gradle b/application/live-plot-graph/build.gradle
index 32396eec91542eea81cfc11a3650d47f38298a78..97d6f27eca63b8cf16a2da5592977c835b16101a 100644
--- a/application/live-plot-graph/build.gradle
+++ b/application/live-plot-graph/build.gradle
@@ -22,8 +22,10 @@
  *
  */
 
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+}
 
 android {
 
diff --git a/application/mediadb/build.gradle b/application/mediadb/build.gradle
index 02ef2569e16c8db15ce35bdac02e2ae5e0e89fd8..408de4e0aebcc0f115d17576c0f9e6bb65428f20 100644
--- a/application/mediadb/build.gradle
+++ b/application/mediadb/build.gradle
@@ -21,10 +21,11 @@
  *
  *
  */
-
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-kapt'
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+    id 'com.google.devtools.ksp'
+}
 
 android {
 
@@ -72,8 +73,8 @@ dependencies {
     androidTestImplementation "androidx.test.espresso:espresso-core:$rootProject.ext.espressoVersion"
     //Room
     implementation "androidx.room:room-ktx:$rootProject.ext.roomVersion"
-    kapt ('org.xerial:sqlite-jdbc:3.36.0')
-    kapt "androidx.room:room-compiler:$rootProject.ext.roomVersion"
+    ksp ('org.xerial:sqlite-jdbc:3.36.0')
+    ksp "androidx.room:room-compiler:$rootProject.ext.roomVersion"
     implementation project(':application:tools')
     implementation project(':application:resources')
 
diff --git a/application/moviepedia/build.gradle b/application/moviepedia/build.gradle
index 5dc140849511fdaf994d1e7c69be95c0726a0743..749c13eeecfe18487b6dc03a5a9727a914f95fb3 100644
--- a/application/moviepedia/build.gradle
+++ b/application/moviepedia/build.gradle
@@ -1,6 +1,9 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-kapt'
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+    id 'kotlin-kapt'
+    id 'com.google.devtools.ksp'
+}
 
 android {
 
@@ -64,8 +67,8 @@ dependencies {
     //Room
     implementation "androidx.room:room-runtime:$rootProject.ext.roomVersion"
     // Provide proper JDBC version, see https://issuetracker.google.com/issues/174695268
-    kapt ('org.xerial:sqlite-jdbc:3.36.0')
-    kapt "androidx.room:room-compiler:$rootProject.ext.roomVersion"
+    ksp ('org.xerial:sqlite-jdbc:3.36.0')
+    ksp "androidx.room:room-compiler:$rootProject.ext.roomVersion"
 
 
     // Retrofit
diff --git a/application/resources/build.gradle b/application/resources/build.gradle
index 344c304e50f3a29b55c52e2e230bc69bde7e9011..8187d3b544087d3a75c77cc120e6d36de54770dc 100644
--- a/application/resources/build.gradle
+++ b/application/resources/build.gradle
@@ -1,5 +1,7 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+}
 
 android {
 
diff --git a/application/television/build.gradle b/application/television/build.gradle
index 5b5c58c2f7fe2efa57811aaca7de66355b5fbabc..fa2a84e6399f88c00f841e8adce194417754de1d 100644
--- a/application/television/build.gradle
+++ b/application/television/build.gradle
@@ -1,7 +1,9 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-kapt'
-apply plugin: 'kotlin-parcelize'
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+    id 'com.google.devtools.ksp'
+    id 'kotlin-parcelize'
+}
 
 android {
 
diff --git a/application/tools/build.gradle b/application/tools/build.gradle
index 3ae05a583be33238f00962aa0820d96467aeb1e9..25f11569d4521acc013dfbc7bd48b1dfca5cf271 100644
--- a/application/tools/build.gradle
+++ b/application/tools/build.gradle
@@ -1,5 +1,7 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
+plugins {
+    id 'com.android.library'
+    id 'kotlin-android'
+}
 
 android {
 
diff --git a/application/vlc-android/build.gradle b/application/vlc-android/build.gradle
index e37071da8aa9fe1c01b33cfd7a6efd381351dd29..b49d7cd5f36de0a4d47256f2e4fd8da8b569ac80 100644
--- a/application/vlc-android/build.gradle
+++ b/application/vlc-android/build.gradle
@@ -1,7 +1,10 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-kapt'
-apply plugin: 'kotlin-parcelize'
+plugins {
+    id 'com.android.library'
+    id 'org.jetbrains.kotlin.android'
+    id 'kotlin-kapt'
+    id 'com.google.devtools.ksp'
+    id 'kotlin-parcelize'
+}
 
 android {
 
@@ -181,8 +184,8 @@ dependencies {
     api 'androidx.gridlayout:gridlayout:1.0.0'
     api "androidx.car.app:app:$rootProject.ext.carVersion"
 
-    kapt ("org.xerial:sqlite-jdbc:jdbcVersion")
-    kapt("androidx.room:room-compiler:$rootProject.ext.roomVersion")
+    ksp ("org.xerial:sqlite-jdbc:jdbcVersion")
+    ksp("androidx.room:room-compiler:$rootProject.ext.roomVersion")
 
     api "androidx.paging:paging-runtime-ktx:$rootProject.ext.pagingVersion"
 
@@ -287,12 +290,3 @@ def isBeta() {
     def versionNameLower = versionName.toLowerCase()
     return (versionNameLower.contains("beta") || versionNameLower.contains("rc") || versionNameLower.contains("alpha") || versionNameLower.contains("dev")).toString()
 }
-
-
-kapt {
-    javacOptions {
-        // Increase the max count of errors from annotation processors.
-        // Default is 100.
-        option("-Xmaxerrs", 500)
-    }
-}
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/DebugLogActivity.kt b/application/vlc-android/src/org/videolan/vlc/gui/DebugLogActivity.kt
index 4d59c3147591c63a276fa4986df2c1e8b7953028..0aeaaa37f0e12d6102735602d7d5bca3ff3d78c1 100644
--- a/application/vlc-android/src/org/videolan/vlc/gui/DebugLogActivity.kt
+++ b/application/vlc-android/src/org/videolan/vlc/gui/DebugLogActivity.kt
@@ -157,7 +157,7 @@ class DebugLogActivity : FragmentActivity(), DebugLogService.Client.Callback {
                 share(File(path))
             }
         } else {
-            UiTools.snacker(window.decorView.findViewById(android.R.id.content), R.string.dump_logcat_failure)
+            UiTools.snacker(this, R.string.dump_logcat_failure)
         }
     }
 
diff --git a/application/vlc-android/src/org/videolan/vlc/util/FilterDelegate.kt b/application/vlc-android/src/org/videolan/vlc/util/FilterDelegate.kt
index 8b173c9735f8055e1a8be582cca1d9b1ed12d517..99a5e2f71fbd1a726faadb345179dfb18387c795 100644
--- a/application/vlc-android/src/org/videolan/vlc/util/FilterDelegate.kt
+++ b/application/vlc-android/src/org/videolan/vlc/util/FilterDelegate.kt
@@ -1,15 +1,15 @@
 package org.videolan.vlc.util
 
-import androidx.lifecycle.MutableLiveData
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import org.videolan.medialibrary.interfaces.media.MediaWrapper
 import org.videolan.medialibrary.media.MediaLibraryItem
 import org.videolan.resources.AppContextProvider
+import org.videolan.tools.livedata.LiveDataset
 import org.videolan.vlc.media.MediaUtils
 import java.util.Locale
 
-open class FilterDelegate<T : MediaLibraryItem>(val dataset: MutableLiveData<out List<T>>) {
+open class FilterDelegate<T : MediaLibraryItem>(val dataset: LiveDataset<T>) {
     var sourceSet: List<T>? = null
 
     protected fun initSource() : List<T>? {
@@ -21,13 +21,14 @@ open class FilterDelegate<T : MediaLibraryItem>(val dataset: MutableLiveData<out
 
     protected open suspend fun filteringJob(charSequence: CharSequence?) : MutableList<T>? {
         if (charSequence !== null) initSource()?.let {
-            return withContext(Dispatchers.Default) { mutableListOf<T>().apply {
-                val queryStrings = charSequence.trim().toString().split(" ")
-                for (item in it) for (query in queryStrings)
-                    if (item.title.contains(query, true)) {
-                        this.add(item)
-                        break
-                    }
+            return withContext(Dispatchers.Default) {
+                mutableListOf<T>().apply {
+                    val queryStrings = charSequence.trim().toString().split(" ")
+                    for (item in it) for (query in queryStrings)
+                        if (item.title.contains(query, true)) {
+                            this.add(item)
+                            break
+                        }
                 }
             }
         }
@@ -36,17 +37,17 @@ open class FilterDelegate<T : MediaLibraryItem>(val dataset: MutableLiveData<out
 
     private fun publish(list: MutableList<T>?) {
         sourceSet?.let {
-            if (list !== null)
-                dataset.value = list
-            else {
+            list?.let {
                 dataset.value = it
+            } ?: run {
+                dataset.value = it.toMutableList()
                 sourceSet = null
             }
         }
     }
 }
 
-class PlaylistFilterDelegate(dataset: MutableLiveData<out List<MediaWrapper>>) : FilterDelegate<MediaWrapper>(dataset) {
+class PlaylistFilterDelegate(dataset: LiveDataset<MediaWrapper>) : FilterDelegate<MediaWrapper>(dataset) {
 
     override suspend fun filteringJob(charSequence: CharSequence?): MutableList<MediaWrapper>? {
         if (charSequence !== null) initSource()?.let { list ->
@@ -61,11 +62,11 @@ class PlaylistFilterDelegate(dataset: MutableLiveData<out List<MediaWrapper>>) :
                     val genre = MediaUtils.getMediaGenre(AppContextProvider.appContext, media).lowercase(Locale.getDefault())
                     for (queryString in queryStrings) {
                         if (title.contains(queryString) ||
-                                location.contains(queryString) ||
-                                artist.contains(queryString) ||
-                                albumArtist.contains(queryString) ||
-                                album.contains(queryString) ||
-                                genre.contains(queryString)) {
+                            location.contains(queryString) ||
+                            artist.contains(queryString) ||
+                            albumArtist.contains(queryString) ||
+                            album.contains(queryString) ||
+                            genre.contains(queryString)) {
                             this.add(media)
                             break
                         }
diff --git a/application/vlc-android/src/org/videolan/vlc/viewmodels/BaseModel.kt b/application/vlc-android/src/org/videolan/vlc/viewmodels/BaseModel.kt
index 8acb78d443582847f770ca0e024fe72ee825e460..702bfce9b4e7c8ab72968ad317724692baf08fdf 100644
--- a/application/vlc-android/src/org/videolan/vlc/viewmodels/BaseModel.kt
+++ b/application/vlc-android/src/org/videolan/vlc/viewmodels/BaseModel.kt
@@ -40,7 +40,7 @@ private const val TAG = "VLC/BaseModel"
 
 abstract class BaseModel<T : MediaLibraryItem>(context: Context, val coroutineContextProvider: CoroutineContextProvider) : SortableModel(context) {
 
-    private val filter by lazy(LazyThreadSafetyMode.NONE) { FilterDelegate(dataset) }
+    private val filter by lazy(LazyThreadSafetyMode.NONE) { FilterDelegate<T>(dataset) }
 
     val dataset = LiveDataset<T>()
     open val loading = MutableLiveData<Boolean>().apply { value = false }
diff --git a/build.gradle b/build.gradle
index 65f5e7485a6508f7dd2126e411b66913bdae5000..e84b88f9fbeaea1689c0f375134b648de8eb2cfc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 buildscript {
-    ext.android_plugin_version = '8.5.0'
-    ext.kotlin_version = '1.9.22'
+    ext.android_plugin_version = '8.9.1'
+    ext.kotlin_version = '2.1.20'
     ext.kotlinx_version = '1.7.1'
     repositories {
         flatDir dirs: "gradle/plugins"
@@ -17,6 +17,10 @@ buildscript {
     }
 }
 
+plugins {
+    id 'com.google.devtools.ksp' version '2.1.20-1.0.32' apply false
+}
+
 allprojects {
     repositories {
         google()
diff --git a/buildsystem/compile.sh b/buildsystem/compile.sh
index 862110d8c6c1fd992cbf677bed81a376832271fe..84ff66c576b4ac33997c98cd4ca09930d2bbb984 100755
--- a/buildsystem/compile.sh
+++ b/buildsystem/compile.sh
@@ -259,8 +259,8 @@ fi
 
 if [ ! -d "gradle/wrapper" ]; then
     diagnostic "Downloading gradle"
-    GRADLE_VERSION=8.7
-    GRADLE_SHA256=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
+    GRADLE_VERSION=8.13
+    GRADLE_SHA256=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
     GRADLE_URL=https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip
     wget ${GRADLE_URL} 2>/dev/null || curl -O ${GRADLE_URL} || fail "gradle: download failed"
     echo $GRADLE_SHA256 gradle-${GRADLE_VERSION}-bin.zip | sha256sum -c || fail "gradle: hash mismatch"
diff --git a/buildsystem/gitlab/.gitlab-ci.yml b/buildsystem/gitlab/.gitlab-ci.yml
index d37a88926399b4079959305571c6b4da3ba73dce..c9ca2093624dbf83d59bb16e41467b4ab89ed0e4 100644
--- a/buildsystem/gitlab/.gitlab-ci.yml
+++ b/buildsystem/gitlab/.gitlab-ci.yml
@@ -1,7 +1,7 @@
 cache:
     paths:
         - .gradle/
-        - gradle-8.7/
+        - gradle-8.13/
         - gradle/
         - gradlew