diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index ca200db..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-RevengeXposed
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 7643783..0def1f7 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,121 +1,10 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b589d56..b86273d 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
deleted file mode 100644
index 0c0c338..0000000
--- a/.idea/deploymentTargetDropDown.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 19ce6a7..381912e 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,14 +4,9 @@
-
-
-
-
-
-
-
-
+
+
+
diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml
new file mode 100644
index 0000000..91f9558
--- /dev/null
+++ b/.idea/deviceManager.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 7b3006b..639c779 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -13,7 +13,6 @@
-
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index e34606c..0000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index c22b6fa..0000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
deleted file mode 100644
index f8051a6..0000000
--- a/.idea/migrations.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 74dd639..2ccd954 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,10 +1,7 @@
-
-
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 35eb1dd..94a25f7 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ac52bda..7293f39 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -6,14 +6,14 @@ plugins {
android {
namespace = "io.github.revenge.xposed"
- compileSdk = 35
+ compileSdk = 36
defaultConfig {
applicationId = "io.github.revenge.xposed"
minSdk = 24
- targetSdk = 35
- versionCode = 1202
- versionName = "1.2.2"
+ targetSdk = 36
+ versionCode = 1400
+ versionName = "1.4.0"
}
buildTypes {
@@ -41,9 +41,9 @@ android {
dependencies {
compileOnly("de.robv.android.xposed:api:82")
- implementation("androidx.core:core-ktx:1.16.0")
- implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1")
- implementation("io.ktor:ktor-client-core:3.1.1")
- implementation("io.ktor:ktor-client-cio:3.1.1")
+ implementation("androidx.core:core-ktx:1.17.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
+ implementation("io.ktor:ktor-client-core:3.2.3")
+ implementation("io.ktor:ktor-client-cio:3.2.3")
}
diff --git a/app/src/main/kotlin/io/github/revenge/xposed/Constants.kt b/app/src/main/kotlin/io/github/revenge/xposed/Constants.kt
index c22a78d..a2f9a00 100644
--- a/app/src/main/kotlin/io/github/revenge/xposed/Constants.kt
+++ b/app/src/main/kotlin/io/github/revenge/xposed/Constants.kt
@@ -9,9 +9,11 @@ class Constants {
const val CACHE_DIR = "cache/pyoncord"
const val MAIN_SCRIPT_FILE = "bundle.js"
+
const val LOG_TAG = "Revenge"
const val LOADER_NAME = "RevengeXposed"
+
const val USER_AGENT = "RevengeXposed"
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/io/github/revenge/xposed/Main.kt b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt
index ea99f66..b7afc7f 100644
--- a/app/src/main/kotlin/io/github/revenge/xposed/Main.kt
+++ b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt
@@ -11,6 +11,7 @@ import io.github.revenge.xposed.modules.*
import io.github.revenge.xposed.modules.appearance.FontsModule
import io.github.revenge.xposed.modules.appearance.SysColorsModule
import io.github.revenge.xposed.modules.appearance.ThemeModule
+import io.github.revenge.xposed.modules.bridge.AdditionalBridgeMethodsModule
import io.github.revenge.xposed.modules.bridge.BridgeModule
class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
@@ -18,12 +19,15 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
private val modules = mutableListOf(
HookScriptLoaderModule(),
BridgeModule(),
+ AdditionalBridgeMethodsModule(),
+ PluginsModule(),
UpdaterModule(),
FixResourcesModule(),
LogBoxModule(),
+ CacheModule(),
FontsModule(),
ThemeModule(),
- SysColorsModule(),
+ SysColorsModule()
)
init {
diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt
new file mode 100644
index 0000000..54db659
--- /dev/null
+++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt
@@ -0,0 +1,270 @@
+package io.github.revenge.xposed.modules
+
+import android.content.Context
+import android.util.AtomicFile
+import io.github.revenge.xposed.Module
+import io.github.revenge.xposed.Utils.Log
+import io.github.revenge.xposed.modules.bridge.BridgeModule
+import java.io.*
+
+/**
+ * A module for caching versioned modules finds and blacklist, and assets mappings.
+ *
+ * See [ModulesCache] and [AssetsCache] for the cache structure.
+ *
+ * ## Methods
+ *
+ * ### Modules cache
+ *
+ * - `revenge.caches.modules.read() : { blacklist: number[], finds: { [filter: string]: { [id: string]: number } | null }, version: number } | null`
+ * - Reads the modules cache. Returns `null` if the cache file does not exist or is invalid.
+ *
+ * - `revenge.caches.modules.write(blacklist: number[], finds: { [filter: string]: { [id: string]: number } | null }) : void`
+ * - Writes the modules cache.
+ *
+ * ### Assets cache
+ *
+ * - `revenge.caches.assets.read() : { data: { [name: string]: { [type: string]: number } }, version: number } | null`
+ * - Reads the assets cache. Returns `null` if the cache file does not exist or is invalid.
+ *
+ * - `revenge.caches.assets.write(data: { [name: string]: { [type: string]: number } }) : void`
+ * - Writes the assets cache.
+ *
+ * The caches are versioned and tied to the app version code. When the app is updated,
+ * the caches are invalidated and new cache files are created.
+ */
+class CacheModule : Module() {
+ private companion object {
+ const val CACHE_DIR = "revenge"
+
+ const val MODULES_CACHE_PREFIX = "modules"
+ const val ASSETS_CACHE_PREFIX = "assets"
+ }
+
+ private lateinit var modulesCache: ModulesCache
+ private lateinit var assetsCache: AssetsCache
+
+ override fun onContext(context: Context) = with(context) {
+ val revengeCacheDir = File(cacheDir, CACHE_DIR).apply { asDir() }
+ val (_, _, _, versionCode) = getAppInfo()
+
+ val modulesCacheFile = File(
+ revengeCacheDir, "$MODULES_CACHE_PREFIX.$versionCode"
+ ).apply { asFile() }
+
+ val assetsCacheFile = File(
+ revengeCacheDir, "$ASSETS_CACHE_PREFIX.$versionCode"
+ ).apply { asFile() }
+
+ BridgeModule.registerMethod("revenge.caches.modules.read") {
+ if (::modulesCache.isInitialized) modulesCache.toMap() else ModulesCache.loadFromFileOrNull(modulesCacheFile)
+ ?.let {
+ modulesCache = it
+ it.toMap()
+ }
+ }
+
+ BridgeModule.registerMethod("revenge.caches.modules.write") {
+ val (blacklist, finds) = it
+ @Suppress("UNCHECKED_CAST")
+ modulesCache = ModulesCache(
+ blacklist as ArrayList, finds as HashMap?>
+ ).apply { saveToFile(modulesCacheFile) }
+ Log.i("Modules cache saved: ${modulesCacheFile.absolutePath} (blacklisted: ${blacklist.size}, finds: ${finds.size})")
+ }
+
+ BridgeModule.registerMethod("revenge.caches.assets.read") {
+ if (::assetsCache.isInitialized) assetsCache.toMap() else AssetsCache.loadFromFileOrNull(assetsCacheFile)
+ ?.let {
+ assetsCache = it
+ it.toMap()
+ }
+ }
+
+ BridgeModule.registerMethod("revenge.caches.assets.write") {
+ val (data) = it
+ @Suppress("UNCHECKED_CAST")
+ assetsCache =
+ AssetsCache(data as HashMap>).apply { saveToFile(assetsCacheFile) }
+ Log.i("Assets cache saved: ${assetsCacheFile.absolutePath} (count: ${data.size})")
+ }
+ }
+}
+
+private class CacheVersionMismatchException(expected: Int, actual: Int) :
+ Throwable("Expected cache version: $expected, but got version: $actual")
+
+data class ModulesCache(
+ val blacklist: List, val finds: Map?>
+) {
+ fun saveToFile(file: File) {
+ val atomic = AtomicFile(file)
+ var fos: FileOutputStream? = null
+ try {
+ fos = atomic.startWrite()
+ val out = DataOutputStream(BufferedOutputStream(fos))
+
+ out.writeInt(VERSION)
+
+ // Write blacklist
+ out.writeInt(blacklist.size)
+ for (num in blacklist) out.writeInt(num.toInt())
+
+ // Write finds
+ out.writeInt(finds.size)
+ for ((filter, matches) in finds) {
+ out.writeUTF(filter)
+ if (matches == null) {
+ out.writeBoolean(false) // null marker
+ } else {
+ out.writeBoolean(true) // not null
+ out.writeInt(matches.size)
+ for ((id, flag) in matches) {
+ out.writeInt(id.toInt())
+ out.writeInt(flag.toInt())
+ }
+ }
+ }
+
+ out.flush()
+ atomic.finishWrite(fos)
+ } catch (t: Throwable) {
+ if (fos != null) atomic.failWrite(fos)
+ throw t
+ }
+ }
+
+ fun toMap(): Map {
+ val findsMap = finds.mapValues { (_, matches) -> matches?.map { (k, v) -> k to v }?.toMap() }
+
+ return mapOf("blacklist" to blacklist, "finds" to findsMap, "version" to VERSION)
+ }
+
+ companion object {
+ const val VERSION = 3
+
+ fun loadFromFileOrNull(file: File): ModulesCache? {
+ if (!file.exists() || file.length() <= 0L) return null
+ val atomic = AtomicFile(file)
+ try {
+ DataInputStream(BufferedInputStream(atomic.openRead())).use { input ->
+ val version = input.readInt()
+ if (version != VERSION) throw CacheVersionMismatchException(VERSION, version)
+
+ val blacklistSize = input.readInt()
+ val blacklist = MutableList(blacklistSize) { input.readInt().toDouble() }
+
+ val findsSize = input.readInt()
+ val finds = mutableMapOf?>()
+ repeat(findsSize) {
+ val filter = input.readUTF()
+ val hasMatches = input.readBoolean()
+ if (!hasMatches) {
+ finds[filter] = null
+ } else {
+ val mapSize = input.readInt()
+ val matches = mutableMapOf()
+ repeat(mapSize) {
+ val id = input.readInt().toString()
+ val flag = input.readInt().toDouble()
+ matches[id] = flag
+ }
+ finds[filter] = matches
+ }
+ }
+
+ return ModulesCache(blacklist, finds)
+ }
+ } catch (e: CacheVersionMismatchException) {
+ Log.i("Modules cache version mismatch: ${e.message}")
+ } catch (e: EOFException) {
+ Log.e("Modules cache corrupt: ${e.message}")
+ } catch (e: IOException) {
+ Log.e("Failed to read modules cache: ${e.message}")
+ }
+ runCatching { file.delete() }
+ return null
+ }
+ }
+}
+
+data class AssetsCache(
+ val data: Map>
+) {
+ fun saveToFile(file: File) {
+ val atomic = AtomicFile(file)
+ var fos: FileOutputStream? = null
+ try {
+ fos = atomic.startWrite()
+ val out = DataOutputStream(BufferedOutputStream(fos))
+
+ out.writeInt(VERSION)
+
+ out.writeInt(data.size)
+ for ((name, mappings) in data) {
+ out.writeUTF(name)
+ out.writeInt(mappings.size)
+ for ((type, id) in mappings) {
+ out.writeUTF(type)
+ out.writeInt(id.toInt())
+ }
+ }
+
+ out.flush()
+ atomic.finishWrite(fos)
+ } catch (t: Throwable) {
+ if (fos != null) atomic.failWrite(fos)
+ throw t
+ }
+ }
+
+ fun toMap(): Map {
+ return mapOf(
+ "data" to data.mapValues { (_, mappings) ->
+ mappings.map { (type, id) -> type to id }.toMap()
+ }, "version" to VERSION
+ )
+ }
+
+ companion object {
+ const val VERSION = 2
+
+ fun loadFromFileOrNull(file: File): AssetsCache? {
+ if (!file.exists() || file.length() <= 0L) return null
+ val atomic = AtomicFile(file)
+ try {
+ DataInputStream(BufferedInputStream(atomic.openRead())).use { input ->
+ val version = input.readInt()
+ if (version != VERSION) throw CacheVersionMismatchException(VERSION, version)
+
+ val dataSize = input.readInt()
+ val data = mutableMapOf>()
+
+ repeat(dataSize) {
+ val name = input.readUTF()
+ val mappingsSize = input.readInt()
+ val mappings = mutableMapOf()
+
+ repeat(mappingsSize) {
+ val type = input.readUTF()
+ val id = input.readInt().toDouble()
+ mappings[type] = id
+ }
+
+ data[name] = mappings
+ }
+
+ return AssetsCache(data)
+ }
+ } catch (e: CacheVersionMismatchException) {
+ Log.i("Assets cache version mismatch: ${e.message}")
+ } catch (e: EOFException) {
+ Log.e("Assets cache corrupt: ${e.message}")
+ } catch (e: IOException) {
+ Log.e("Failed to read assets cache: ${e.message}")
+ }
+ runCatching { file.delete() }
+ return null
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/PluginsModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/PluginsModule.kt
new file mode 100644
index 0000000..5948b43
--- /dev/null
+++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/PluginsModule.kt
@@ -0,0 +1,124 @@
+package io.github.revenge.xposed.modules
+
+import android.content.Context
+import android.util.AtomicFile
+import io.github.revenge.xposed.Module
+import io.github.revenge.xposed.Utils.Log
+import io.github.revenge.xposed.modules.bridge.BridgeModule
+import java.io.*
+
+/**
+ * Manages the states of plugins, allowing reading and writing of plugin states to a file.
+ *
+ * ## Methods
+ *
+ * - `revenge.plugins.states.read(): { flags: { [pluginId: string]: number } }`
+ * - Reads the current plugin states from the file and returns them as a map.
+ *
+ * - `revenge.plugins.states.write(flags: { [pluginId: string]: number }): void`
+ * - Writes the provided plugin states to the file.
+ */
+class PluginsModule : Module() {
+ private companion object {
+ const val DATA_DIR = "revenge/plugins"
+ const val STATES_FILE = "states"
+ }
+
+ private lateinit var states: PluginStates
+
+ override fun onContext(context: Context) = with(context) {
+ val dataDir = File(filesDir, DATA_DIR)
+
+ val statesFile = File(
+ dataDir, STATES_FILE
+ ).apply { asFile() }
+
+ BridgeModule.registerMethod("revenge.plugins.states.read") {
+ if (::states.isInitialized) states.toMap() else
+ PluginStates.loadFromFileOrNull(statesFile)?.let {
+ states = it
+ it.toMap()
+ }
+ }
+
+ BridgeModule.registerMethod("revenge.plugins.states.write") {
+ val (flags) = it
+ @Suppress("UNCHECKED_CAST")
+ states = PluginStates(
+ flags as Map
+ ).apply { saveToFile(statesFile) }
+ Log.i("Plugin states saved: ${statesFile.absolutePath}")
+ }
+ }
+}
+
+private class UnsupportedPluginStatesVersionException(version: Int) :
+ Throwable("Unsupported plugin states version: $version")
+
+data class PluginStates(
+ val flags: Map
+) {
+ fun saveToFile(file: File) {
+ val atomic = AtomicFile(file)
+ var fos: FileOutputStream? = null
+ try {
+ fos = atomic.startWrite()
+ val out = DataOutputStream(BufferedOutputStream(fos))
+
+ out.writeInt(CURRENT_VERSION)
+
+ // Write flags
+ out.writeInt(flags.size)
+ for ((id, flags) in flags) {
+ out.writeUTF(id)
+ out.writeInt(flags.toInt())
+ }
+
+ out.flush()
+ atomic.finishWrite(fos)
+ } catch (t: Throwable) {
+ if (fos != null) atomic.failWrite(fos)
+ throw t
+ }
+ }
+
+ fun toMap(): Map = mapOf("flags" to flags)
+
+ companion object {
+ const val CURRENT_VERSION = 1
+
+ fun loadFromFileOrNull(file: File): PluginStates? {
+ if (!file.exists() || file.length() <= 0L) return null
+ val atomic = AtomicFile(file)
+
+ try {
+ DataInputStream(BufferedInputStream(atomic.openRead())).use { input ->
+ when (val version = input.readInt()) {
+ 1 -> return loadV1FromFileOrNull(input)
+ else -> throw UnsupportedPluginStatesVersionException(version)
+ }
+ }
+ } catch (e: UnsupportedPluginStatesVersionException) {
+ Log.i(e.message!!)
+ } catch (e: EOFException) {
+ Log.e("Plugin states corrupt: ${e.message}")
+ } catch (e: IOException) {
+ Log.e("Failed to read plugin states: ${e.message}")
+ }
+
+ runCatching { file.delete() }
+ return null
+ }
+
+ private fun loadV1FromFileOrNull(input: DataInputStream): PluginStates {
+ val flagsSize = input.readInt()
+ val flags = mutableMapOf()
+
+ repeat(flagsSize) {
+ flags[input.readUTF()] = input.readInt().toDouble()
+ }
+
+ return PluginStates(flags)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt
index 59df27d..5356f45 100644
--- a/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt
+++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt
@@ -47,9 +47,7 @@ class UpdaterModule : Module() {
private lateinit var etag: File
companion object {
- @Volatile
var job: Job? = null
- private set
private const val TIMEOUT_CACHED = 5000L
private const val TIMEOUT = 10000L
diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt
new file mode 100644
index 0000000..eb252f1
--- /dev/null
+++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt
@@ -0,0 +1,45 @@
+package io.github.revenge.xposed.modules.bridge
+
+import android.app.AlertDialog
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.os.Build
+import android.widget.Toast
+import io.github.revenge.xposed.Module
+
+class AdditionalBridgeMethodsModule : Module() {
+ override fun onContext(context: Context) = with(context) {
+ BridgeModule.registerMethod("revenge.alertError") {
+ val (error, version) = it
+ val app = getAppInfo()
+ val errorString = "$error"
+
+ val clipboard = applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ val clip = ClipData.newPlainText("Stack Trace", errorString)
+
+ AlertDialog.Builder(this)
+ .setTitle("Revenge Error")
+ .setMessage(
+ """
+ Revenge: $version
+ ${app.name}: ${app.version} (${app.versionCode})
+ Device: ${Build.MANUFACTURER} ${Build.MODEL}
+
+
+ """.trimIndent() + errorString
+ )
+ .setPositiveButton("OK") { dialog, _ ->
+ dialog.dismiss()
+ }
+ .setNeutralButton("Copy") { dialog, _ ->
+ clipboard.setPrimaryClip(clip)
+ Toast.makeText(applicationContext, "Copied stack trace", Toast.LENGTH_SHORT).show()
+ dialog.dismiss()
+ }
+ .show()
+
+ null
+ }
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index bfc2823..d421a94 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,7 +5,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath("com.android.tools.build:gradle:8.9.2")
+ classpath("com.android.tools.build:gradle:8.11.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.10")
// NOTE: Do not place your application dependencies here; they belong
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e2847c8..37f853b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME