chore: update dependencies

🦀 integrations are gone! 🦀
This commit is contained in:
Ax333l 2024-11-13 22:11:36 +01:00
parent cf322147d5
commit 20c13ee71c
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
45 changed files with 223 additions and 316 deletions

View File

@ -3,21 +3,22 @@ import kotlin.random.Random
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.devtools) alias(libs.plugins.devtools)
alias(libs.plugins.about.libraries) alias(libs.plugins.about.libraries)
id("kotlin-parcelize")
kotlin("plugin.serialization") version "1.9.23"
} }
android { android {
namespace = "app.revanced.manager" namespace = "app.revanced.manager"
compileSdk = 34 compileSdk = 35
buildToolsVersion = "34.0.0" buildToolsVersion = "35.0.0"
defaultConfig { defaultConfig {
applicationId = "app.revanced.manager" applicationId = "app.revanced.manager"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 35
versionCode = 1 versionCode = 1
versionName = "0.0.1" versionName = "0.0.1"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -81,9 +82,11 @@ android {
jvmTarget = "17" jvmTarget = "17"
} }
buildFeatures.compose = true buildFeatures {
buildFeatures.aidl = true compose = true
buildFeatures.buildConfig=true aidl = true
buildConfig = true
}
android { android {
androidResources { androidResources {
@ -91,7 +94,6 @@ android {
} }
} }
composeOptions.kotlinCompilerExtensionVersion = "1.5.10"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path = file("src/main/cpp/CMakeLists.txt") path = file("src/main/cpp/CMakeLists.txt")
@ -112,7 +114,6 @@ dependencies {
implementation(libs.runtime.compose) implementation(libs.runtime.compose)
implementation(libs.splash.screen) implementation(libs.splash.screen)
implementation(libs.compose.activity) implementation(libs.compose.activity)
implementation(libs.paging.common.ktx)
implementation(libs.work.runtime.ktx) implementation(libs.work.runtime.ktx)
implementation(libs.preferences.datastore) implementation(libs.preferences.datastore)

View File

@ -2,11 +2,11 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "1dd9d5c0201fdf3cfef3ae669fd65e46", "identityHash": "c385297c07ea54804dc8526c388f706d",
"entities": [ "entities": [
{ {
"tableName": "patch_bundles", "tableName": "patch_bundles",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, `version` TEXT, `integrations_version` TEXT, PRIMARY KEY(`uid`))", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `version` TEXT, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, PRIMARY KEY(`uid`))",
"fields": [ "fields": [
{ {
"fieldPath": "uid", "fieldPath": "uid",
@ -20,6 +20,12 @@
"affinity": "TEXT", "affinity": "TEXT",
"notNull": true "notNull": true
}, },
{
"fieldPath": "version",
"columnName": "version",
"affinity": "TEXT",
"notNull": false
},
{ {
"fieldPath": "source", "fieldPath": "source",
"columnName": "source", "columnName": "source",
@ -31,18 +37,6 @@
"columnName": "auto_update", "columnName": "auto_update",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
},
{
"fieldPath": "versionInfo.patches",
"columnName": "version",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "versionInfo.integrations",
"columnName": "integrations_version",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -397,7 +391,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dd9d5c0201fdf3cfef3ae669fd65e46')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c385297c07ea54804dc8526c388f706d')"
] ]
} }
} }

View File

@ -8,11 +8,11 @@ interface PatchBundleDao {
@Query("SELECT * FROM patch_bundles") @Query("SELECT * FROM patch_bundles")
suspend fun all(): List<PatchBundleEntity> suspend fun all(): List<PatchBundleEntity>
@Query("SELECT version, integrations_version, auto_update FROM patch_bundles WHERE uid = :uid") @Query("SELECT version, auto_update FROM patch_bundles WHERE uid = :uid")
fun getPropsById(uid: Int): Flow<BundleProperties?> fun getPropsById(uid: Int): Flow<BundleProperties?>
@Query("UPDATE patch_bundles SET version = :patches, integrations_version = :integrations WHERE uid = :uid") @Query("UPDATE patch_bundles SET version = :patches WHERE uid = :uid")
suspend fun updateVersion(uid: Int, patches: String?, integrations: String?) suspend fun updateVersion(uid: Int, patches: String?)
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid") @Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
suspend fun setAutoUpdate(uid: Int, value: Boolean) suspend fun setAutoUpdate(uid: Int, value: Boolean)
@ -26,7 +26,7 @@ interface PatchBundleDao {
@Transaction @Transaction
suspend fun reset() { suspend fun reset() {
purgeCustomBundles() purgeCustomBundles()
updateVersion(0, null, null) // Reset the main source updateVersion(0, null) // Reset the main source
} }
@Query("DELETE FROM patch_bundles WHERE uid = :uid") @Query("DELETE FROM patch_bundles WHERE uid = :uid")

View File

@ -29,21 +29,16 @@ sealed class Source {
} }
} }
data class VersionInfo(
@ColumnInfo(name = "version") val patches: String? = null,
@ColumnInfo(name = "integrations_version") val integrations: String? = null,
)
@Entity(tableName = "patch_bundles") @Entity(tableName = "patch_bundles")
data class PatchBundleEntity( data class PatchBundleEntity(
@PrimaryKey val uid: Int, @PrimaryKey val uid: Int,
@ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "name") val name: String,
@Embedded val versionInfo: VersionInfo, @ColumnInfo(name = "version") val version: String? = null,
@ColumnInfo(name = "source") val source: Source, @ColumnInfo(name = "source") val source: Source,
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean @ColumnInfo(name = "auto_update") val autoUpdate: Boolean
) )
data class BundleProperties( data class BundleProperties(
@Embedded val versionInfo: VersionInfo, @ColumnInfo(name = "version") val version: String? = null,
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean @ColumnInfo(name = "auto_update") val autoUpdate: Boolean
) )

View File

@ -20,6 +20,8 @@ import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long import kotlinx.serialization.json.long
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@Entity( @Entity(
tableName = "options", tableName = "options",
@ -46,8 +48,8 @@ data class Option(
val errorMessage = "Cannot deserialize value as ${option.type}" val errorMessage = "Cannot deserialize value as ${option.type}"
try { try {
if (option.type.endsWith("Array")) { if (option.type.classifier == List::class) {
val elementType = option.type.removeSuffix("Array") val elementType = option.type.arguments.first().type!!
return raw.jsonArray.map { deserializeBasicType(elementType, it.jsonPrimitive) } return raw.jsonArray.map { deserializeBasicType(elementType, it.jsonPrimitive) }
} }
@ -67,12 +69,17 @@ data class Option(
allowSpecialFloatingPointValues = true allowSpecialFloatingPointValues = true
} }
private fun deserializeBasicType(type: String, value: JsonPrimitive) = when (type) { private fun deserializeBasicType(type: KType, value: JsonPrimitive) = when (type) {
"Boolean" -> value.boolean typeOf<Boolean>() -> value.boolean
"Int" -> value.int typeOf<Int>() -> value.int
"Long" -> value.long typeOf<Long>() -> value.long
"Float" -> value.float typeOf<Float>() -> value.float
"String" -> value.content.also { if (!value.isString) throw SerializationException("Expected value to be a string: $value") } typeOf<String>() -> value.content.also {
if (!value.isString) throw SerializationException(
"Expected value to be a string: $value"
)
}
else -> throw SerializationException("Unknown type: $type") else -> throw SerializationException("Unknown type: $type")
} }

View File

@ -4,29 +4,18 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.nio.file.Files
import java.nio.file.StandardCopyOption
class LocalPatchBundle(name: String, id: Int, directory: File) : class LocalPatchBundle(name: String, id: Int, directory: File) :
PatchBundleSource(name, id, directory) { PatchBundleSource(name, id, directory) {
suspend fun replace(patches: InputStream? = null, integrations: InputStream? = null) { suspend fun replace(patches: InputStream) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
patches?.let { inputStream ->
patchBundleOutputStream().use { outputStream -> patchBundleOutputStream().use { outputStream ->
inputStream.copyTo(outputStream) patches.copyTo(outputStream)
}
}
integrations?.let {
Files.copy(
it,
this@LocalPatchBundle.integrationsFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
} }
} }
reload()?.also { reload()?.also {
saveVersion(it.readManifestAttribute("Version"), null) saveVersion(it.readManifestAttribute("Version"))
} }
} }
} }

View File

@ -28,7 +28,6 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
protected val configRepository: PatchBundlePersistenceRepository by inject() protected val configRepository: PatchBundlePersistenceRepository by inject()
private val app: Application by inject() private val app: Application by inject()
protected val patchesFile = directory.resolve("patches.jar") protected val patchesFile = directory.resolve("patches.jar")
protected val integrationsFile = directory.resolve("integrations.apk")
private val _state = MutableStateFlow(load()) private val _state = MutableStateFlow(load())
val state = _state.asStateFlow() val state = _state.asStateFlow()
@ -58,7 +57,7 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
if (!hasInstalled()) return State.Missing if (!hasInstalled()) return State.Missing
return try { return try {
State.Loaded(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists))) State.Loaded(PatchBundle(patchesFile))
} catch (t: Throwable) { } catch (t: Throwable) {
Log.e(tag, "Failed to load patch bundle with UID $uid", t) Log.e(tag, "Failed to load patch bundle with UID $uid", t)
State.Failed(t) State.Failed(t)
@ -85,9 +84,9 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
fun propsFlow() = configRepository.getProps(uid).flowOn(Dispatchers.Default) fun propsFlow() = configRepository.getProps(uid).flowOn(Dispatchers.Default)
suspend fun getProps() = propsFlow().first()!! suspend fun getProps() = propsFlow().first()!!
suspend fun currentVersion() = getProps().versionInfo suspend fun currentVersion() = getProps().version
protected suspend fun saveVersion(patches: String?, integrations: String?) = protected suspend fun saveVersion(version: String?) =
configRepository.updateVersion(uid, patches, integrations) configRepository.updateVersion(uid, version)
suspend fun setName(name: String) { suspend fun setName(name: String) {
configRepository.setName(uid, name) configRepository.setName(uid, name)

View File

@ -1,20 +1,12 @@
package app.revanced.manager.domain.bundles package app.revanced.manager.domain.bundles
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import app.revanced.manager.data.room.bundles.VersionInfo
import app.revanced.manager.network.api.ReVancedAPI import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType import app.revanced.manager.network.dto.PatchBundleInfo
import app.revanced.manager.network.dto.BundleAsset
import app.revanced.manager.network.dto.BundleInfo
import app.revanced.manager.network.service.HttpService import app.revanced.manager.network.service.HttpService
import app.revanced.manager.network.utils.getOrThrow import app.revanced.manager.network.utils.getOrThrow
import app.revanced.manager.util.APK_MIMETYPE
import app.revanced.manager.util.JAR_MIMETYPE
import io.ktor.client.request.url import io.ktor.client.request.url
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.inject import org.koin.core.component.inject
import java.io.File import java.io.File
@ -24,27 +16,17 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
PatchBundleSource(name, id, directory) { PatchBundleSource(name, id, directory) {
protected val http: HttpService by inject() protected val http: HttpService by inject()
protected abstract suspend fun getLatestInfo(): BundleInfo protected abstract suspend fun getLatestInfo(): PatchBundleInfo
private suspend fun download(info: BundleInfo) = withContext(Dispatchers.IO) { private suspend fun download(info: PatchBundleInfo) = withContext(Dispatchers.IO) {
val (patches, integrations) = info val (version, url) = info
coroutineScope {
launch {
patchBundleOutputStream().use { patchBundleOutputStream().use {
http.streamTo(it) { http.streamTo(it) {
url(patches.url) url(url)
}
} }
} }
launch { saveVersion(version)
http.download(integrationsFile) {
url(integrations.url)
}
}
}
saveVersion(patches.version, integrations.version)
reload() reload()
} }
@ -54,20 +36,15 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
suspend fun update(): Boolean = withContext(Dispatchers.IO) { suspend fun update(): Boolean = withContext(Dispatchers.IO) {
val info = getLatestInfo() val info = getLatestInfo()
if (hasInstalled() && VersionInfo( if (hasInstalled() && info.version == currentVersion())
info.patches.version,
info.integrations.version
) == currentVersion()
) {
return@withContext false return@withContext false
}
download(info) download(info)
true true
} }
suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) { suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) {
arrayOf(patchesFile, integrationsFile).forEach(File::delete) patchesFile.delete()
reload() reload()
} }
@ -81,7 +58,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
class JsonPatchBundle(name: String, id: Int, directory: File, endpoint: String) : class JsonPatchBundle(name: String, id: Int, directory: File, endpoint: String) :
RemotePatchBundle(name, id, directory, endpoint) { RemotePatchBundle(name, id, directory, endpoint) {
override suspend fun getLatestInfo() = withContext(Dispatchers.IO) { override suspend fun getLatestInfo() = withContext(Dispatchers.IO) {
http.request<BundleInfo> { http.request<PatchBundleInfo> {
url(endpoint) url(endpoint)
}.getOrThrow() }.getOrThrow()
} }
@ -91,22 +68,10 @@ class APIPatchBundle(name: String, id: Int, directory: File, endpoint: String) :
RemotePatchBundle(name, id, directory, endpoint) { RemotePatchBundle(name, id, directory, endpoint) {
private val api: ReVancedAPI by inject() private val api: ReVancedAPI by inject()
override suspend fun getLatestInfo() = coroutineScope { override suspend fun getLatestInfo() = api
fun getAssetAsync(repo: String, mime: String) = async(Dispatchers.IO) { .getLatestRelease("revanced-patches")
api
.getLatestRelease(repo)
.getOrThrow() .getOrThrow()
.let { .let {
BundleAsset(it.version, it.findAssetByType(mime).downloadUrl) PatchBundleInfo(it.version, it.assets.first { it.name.endsWith(".rvp") }.downloadUrl)
}
}
val patches = getAssetAsync("revanced-patches", JAR_MIMETYPE)
val integrations = getAssetAsync("revanced-integrations", APK_MIMETYPE)
BundleInfo(
patches.await(),
integrations.await()
)
} }
} }

View File

@ -109,7 +109,12 @@ class RootInstaller(
stockAPK?.let { stockApp -> stockAPK?.let { stockApp ->
pm.getPackageInfo(packageName)?.let { packageInfo -> pm.getPackageInfo(packageName)?.let { packageInfo ->
if (packageInfo.versionName <= version) // TODO: get user id programmatically
if (pm.getVersionCode(packageInfo) <= pm.getVersionCode(
pm.getPackageInfo(patchedAPK)
?: error("Failed to get package info for patched app")
)
)
execute("pm uninstall -k --user 0 $packageName").assertSuccess("Failed to uninstall stock app") execute("pm uninstall -k --user 0 $packageName").assertSuccess("Failed to uninstall stock app")
} }

View File

@ -12,6 +12,8 @@ import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.nio.file.Files import java.nio.file.Files
import java.security.UnrecoverableKeyException import java.security.UnrecoverableKeyException
import java.util.Date
import kotlin.time.Duration.Companion.days
class KeystoreManager(app: Application, private val prefs: PreferencesManager) { class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
companion object Constants { companion object Constants {
@ -19,6 +21,7 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
* Default alias and password for the keystore. * Default alias and password for the keystore.
*/ */
const val DEFAULT = "ReVanced" const val DEFAULT = "ReVanced"
private val eightYearsFromNow get() = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24)
} }
private val keystorePath = private val keystorePath =
@ -29,23 +32,26 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
prefs.keystorePass.value = pass prefs.keystorePass.value = pass
} }
private suspend fun signingOptions(path: File = keystorePath) = ApkUtils.SigningOptions( private suspend fun signingDetails(path: File = keystorePath) = ApkUtils.KeyStoreDetails(
keyStore = path, keyStore = path,
keyStorePassword = null, keyStorePassword = null,
alias = prefs.keystoreCommonName.get(), alias = prefs.keystoreCommonName.get(),
signer = prefs.keystoreCommonName.get(),
password = prefs.keystorePass.get() password = prefs.keystorePass.get()
) )
suspend fun sign(input: File, output: File) = withContext(Dispatchers.Default) { suspend fun sign(input: File, output: File) = withContext(Dispatchers.Default) {
ApkUtils.sign(input, output, signingOptions()) ApkUtils.signApk(input, output, prefs.keystoreCommonName.get(), signingDetails())
} }
suspend fun regenerate() = withContext(Dispatchers.Default) { suspend fun regenerate() = withContext(Dispatchers.Default) {
val keyCertPair = ApkSigner.newPrivateKeyCertificatePair(
prefs.keystoreCommonName.get(),
eightYearsFromNow
)
val ks = ApkSigner.newKeyStore( val ks = ApkSigner.newKeyStore(
setOf( setOf(
ApkSigner.KeyStoreEntry( ApkSigner.KeyStoreEntry(
DEFAULT, DEFAULT DEFAULT, DEFAULT, keyCertPair
) )
) )
) )
@ -64,7 +70,7 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
try { try {
val ks = ApkSigner.readKeyStore(ByteArrayInputStream(keystoreData), null) val ks = ApkSigner.readKeyStore(ByteArrayInputStream(keystoreData), null)
ApkSigner.readKeyCertificatePair(ks, cn, pass) ApkSigner.readPrivateKeyCertificatePair(ks, cn, pass)
} catch (_: UnrecoverableKeyException) { } catch (_: UnrecoverableKeyException) {
return false return false
} catch (_: IllegalArgumentException) { } catch (_: IllegalArgumentException) {

View File

@ -12,7 +12,6 @@ class PreferencesManager(
val api = stringPreference("api_url", "https://api.revanced.app") val api = stringPreference("api_url", "https://api.revanced.app")
val multithreadingDexFileWriter = booleanPreference("multithreading_dex_file_writer", true)
val useProcessRuntime = booleanPreference("use_process_runtime", false) val useProcessRuntime = booleanPreference("use_process_runtime", false)
val patcherProcessMemoryLimit = intPreference("process_runtime_memory_limit", 700) val patcherProcessMemoryLimit = intPreference("process_runtime_memory_limit", 700)

View File

@ -4,7 +4,6 @@ import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.AppDatabase.Companion.generateUid import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
import app.revanced.manager.data.room.bundles.PatchBundleEntity import app.revanced.manager.data.room.bundles.PatchBundleEntity
import app.revanced.manager.data.room.bundles.Source import app.revanced.manager.data.room.bundles.Source
import app.revanced.manager.data.room.bundles.VersionInfo
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
class PatchBundlePersistenceRepository(db: AppDatabase) { class PatchBundlePersistenceRepository(db: AppDatabase) {
@ -26,7 +25,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
PatchBundleEntity( PatchBundleEntity(
uid = generateUid(), uid = generateUid(),
name = name, name = name,
versionInfo = VersionInfo(), version = null,
source = source, source = source,
autoUpdate = autoUpdate autoUpdate = autoUpdate
).also { ).also {
@ -35,8 +34,8 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
suspend fun delete(uid: Int) = dao.remove(uid) suspend fun delete(uid: Int) = dao.remove(uid)
suspend fun updateVersion(uid: Int, patches: String?, integrations: String?) = suspend fun updateVersion(uid: Int, version: String?) =
dao.updateVersion(uid, patches, integrations) dao.updateVersion(uid, version)
suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value) suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value)
@ -48,7 +47,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
val defaultSource = PatchBundleEntity( val defaultSource = PatchBundleEntity(
uid = 0, uid = 0,
name = "", name = "",
versionInfo = VersionInfo(), version = null,
source = Source.API, source = Source.API,
autoUpdate = false autoUpdate = false
) )

View File

@ -3,7 +3,7 @@ package app.revanced.manager.domain.repository
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import app.revanced.library.PatchUtils import app.revanced.library.mostCommonCompatibleVersions
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.data.platform.NetworkInfo import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.data.room.bundles.PatchBundleEntity import app.revanced.manager.data.room.bundles.PatchBundleEntity
@ -55,7 +55,7 @@ class PatchBundleRepository(
val allPatches = val allPatches =
it.values.flatMap { bundle -> bundle.patches.map(PatchInfo::toPatcherPatch) }.toSet() it.values.flatMap { bundle -> bundle.patches.map(PatchInfo::toPatcherPatch) }.toSet()
PatchUtils.getMostCommonCompatibleVersions(allPatches, countUnusedPatches = true) allPatches.mostCommonCompatibleVersions(countUnusedPatches = true)
.mapValues { (_, versions) -> .mapValues { (_, versions) ->
if (versions.keys.size < 2) if (versions.keys.size < 2)
return@mapValues versions.keys.firstOrNull() return@mapValues versions.keys.firstOrNull()
@ -137,11 +137,11 @@ class PatchBundleRepository(
private fun addBundle(patchBundle: PatchBundleSource) = private fun addBundle(patchBundle: PatchBundleSource) =
_sources.update { it.toMutableMap().apply { put(patchBundle.uid, patchBundle) } } _sources.update { it.toMutableMap().apply { put(patchBundle.uid, patchBundle) } }
suspend fun createLocal(patches: InputStream, integrations: InputStream?) = withContext(Dispatchers.Default) { suspend fun createLocal(patches: InputStream) = withContext(Dispatchers.Default) {
val uid = persistenceRepo.create("", SourceInfo.Local).uid val uid = persistenceRepo.create("", SourceInfo.Local).uid
val bundle = LocalPatchBundle("", uid, directoryOf(uid)) val bundle = LocalPatchBundle("", uid, directoryOf(uid))
bundle.replace(patches, integrations) bundle.replace(patches)
addBundle(bundle) addBundle(bundle)
} }

View File

@ -1,9 +0,0 @@
package app.revanced.manager.network.dto
import kotlinx.serialization.Serializable
@Serializable
data class BundleInfo(val patches: BundleAsset, val integrations: BundleAsset)
@Serializable
data class BundleAsset(val version: String, val url: String)

View File

@ -0,0 +1,7 @@
package app.revanced.manager.network.dto
import kotlinx.serialization.Serializable
@Serializable
// TODO: replace this
data class PatchBundleInfo(val version: String, val url: String)

View File

@ -22,7 +22,6 @@ class Session(
cacheDir: String, cacheDir: String,
frameworkDir: String, frameworkDir: String,
aaptPath: String, aaptPath: String,
multithreadingDexFileWriter: Boolean,
private val androidContext: Context, private val androidContext: Context,
private val logger: Logger, private val logger: Logger,
private val input: File, private val input: File,
@ -38,8 +37,7 @@ class Session(
apkFile = input, apkFile = input,
temporaryFilesPath = tempDir, temporaryFilesPath = tempDir,
frameworkFileDirectory = frameworkDir, frameworkFileDirectory = frameworkDir,
aaptBinaryPath = aaptPath, aaptBinaryPath = aaptPath
multithreadingDexFileWriter = multithreadingDexFileWriter,
) )
) )
@ -51,7 +49,7 @@ class Session(
state = State.RUNNING state = State.RUNNING
) )
this.apply(true).collect { (patch, exception) -> this().collect { (patch, exception) ->
if (patch !in selectedPatches) return@collect if (patch !in selectedPatches) return@collect
if (exception != null) { if (exception != null) {
@ -89,7 +87,7 @@ class Session(
) )
} }
suspend fun run(output: File, selectedPatches: PatchList, integrations: List<File>) { suspend fun run(output: File, selectedPatches: PatchList) {
updateProgress(state = State.COMPLETED) // Unpacking updateProgress(state = State.COMPLETED) // Unpacking
java.util.logging.Logger.getLogger("").apply { java.util.logging.Logger.getLogger("").apply {
@ -103,8 +101,7 @@ class Session(
with(patcher) { with(patcher) {
logger.info("Merging integrations") logger.info("Merging integrations")
acceptIntegrations(integrations.toSet()) this += selectedPatches.toSet()
acceptPatches(selectedPatches.toSet())
logger.info("Applying patches...") logger.info("Applying patches...")
applyPatchesVerbose(selectedPatches.sortedBy { it.name }) applyPatchesVerbose(selectedPatches.sortedBy { it.name })

View File

@ -2,17 +2,17 @@ package app.revanced.manager.patcher.patch
import android.util.Log import android.util.Log
import app.revanced.manager.util.tag import app.revanced.manager.util.tag
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.PatchLoader
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.jar.JarFile import java.util.jar.JarFile
class PatchBundle(val patchesJar: File, val integrations: File?) { class PatchBundle(val patchesJar: File) {
private val loader = object : Iterable<Patch<*>> { private val loader = object : Iterable<Patch<*>> {
private fun load(): Iterable<Patch<*>> { private fun load(): Iterable<Patch<*>> {
patchesJar.setReadOnly() patchesJar.setReadOnly()
return PatchBundleLoader.Dex(patchesJar, optimizedDexDirectory = null) return PatchLoader.Dex(setOf(patchesJar))
} }
override fun iterator(): Iterator<Patch<*>> = load().iterator() override fun iterator(): Iterator<Patch<*>> = load().iterator()
@ -41,12 +41,12 @@ class PatchBundle(val patchesJar: File, val integrations: File?) {
/** /**
* Load all patches compatible with the specified package. * Load all patches compatible with the specified package.
*/ */
fun patchClasses(packageName: String) = loader.filter { patch -> fun patches(packageName: String) = loader.filter { patch ->
val compatiblePackages = patch.compatiblePackages val compatiblePackages = patch.compatiblePackages
?: // The patch has no compatibility constraints, which means it is universal. ?: // The patch has no compatibility constraints, which means it is universal.
return@filter true return@filter true
if (!compatiblePackages.any { it.name == packageName }) { if (!compatiblePackages.any { (name, _) -> name == packageName }) {
// Patch is not compatible with this package. // Patch is not compatible with this package.
return@filter false return@filter false
} }

View File

@ -1,14 +1,14 @@
package app.revanced.manager.patcher.patch package app.revanced.manager.patcher.patch
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.Option as PatchOption
import app.revanced.patcher.patch.options.PatchOption import app.revanced.patcher.patch.resourcePatch
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet import kotlinx.collections.immutable.toImmutableSet
import kotlin.reflect.KType
data class PatchInfo( data class PatchInfo(
val name: String, val name: String,
@ -21,7 +21,12 @@ data class PatchInfo(
patch.name.orEmpty(), patch.name.orEmpty(),
patch.description, patch.description,
patch.use, patch.use,
patch.compatiblePackages?.map { CompatiblePackage(it) }?.toImmutableList(), patch.compatiblePackages?.map { (pkgName, versions) ->
CompatiblePackage(
pkgName,
versions?.toImmutableSet()
)
}?.toImmutableList(),
patch.options.map { (_, option) -> Option(option) }.ifEmpty { null }?.toImmutableList() patch.options.map { (_, option) -> Option(option) }.ifEmpty { null }?.toImmutableList()
) )
@ -45,16 +50,11 @@ data class PatchInfo(
* The resulting patch cannot be executed. * The resulting patch cannot be executed.
* This is necessary because some functions in ReVanced Library only accept full [Patch] objects. * This is necessary because some functions in ReVanced Library only accept full [Patch] objects.
*/ */
fun toPatcherPatch(): Patch<*> = object : ResourcePatch( fun toPatcherPatch(): Patch<*> =
name = name, resourcePatch(name = name, description = description, use = include) {
description = description, compatiblePackages?.let { pkgs ->
compatiblePackages = compatiblePackages compatibleWith(*pkgs.map { it.packageName to it.versions }.toTypedArray())
?.map(app.revanced.manager.patcher.patch.CompatiblePackage::toPatcherCompatiblePackage) }
?.toSet(),
use = include,
) {
override fun execute(context: ResourceContext) =
throw Exception("Metadata patches cannot be executed")
} }
} }
@ -62,28 +62,15 @@ data class PatchInfo(
data class CompatiblePackage( data class CompatiblePackage(
val packageName: String, val packageName: String,
val versions: ImmutableSet<String>? val versions: ImmutableSet<String>?
) {
constructor(pkg: Patch.CompatiblePackage) : this(
pkg.name,
pkg.versions?.toImmutableSet()
) )
/**
* Converts this [CompatiblePackage] into a [Patch.CompatiblePackage] from patcher.
*/
fun toPatcherCompatiblePackage() = Patch.CompatiblePackage(
name = packageName,
versions = versions,
)
}
@Immutable @Immutable
data class Option<T>( data class Option<T>(
val title: String, val title: String,
val key: String, val key: String,
val description: String, val description: String,
val required: Boolean, val required: Boolean,
val type: String, val type: KType,
val default: T?, val default: T?,
val presets: Map<String, T?>?, val presets: Map<String, T?>?,
val validator: (T?) -> Boolean, val validator: (T?) -> Boolean,
@ -93,7 +80,7 @@ data class Option<T>(
option.key, option.key,
option.description.orEmpty(), option.description.orEmpty(),
option.required, option.required,
option.valueType, option.type,
option.default, option.default,
option.values, option.values,
{ option.validator(option, it) }, { option.validator(option, it) },

View File

@ -27,15 +27,13 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) {
val selectedBundles = selectedPatches.keys val selectedBundles = selectedPatches.keys
val allPatches = bundles.filterKeys { selectedBundles.contains(it) } val allPatches = bundles.filterKeys { selectedBundles.contains(it) }
.mapValues { (_, bundle) -> bundle.patchClasses(packageName) } .mapValues { (_, bundle) -> bundle.patches(packageName) }
val patchList = selectedPatches.flatMap { (bundle, selected) -> val patchList = selectedPatches.flatMap { (bundle, selected) ->
allPatches[bundle]?.filter { selected.contains(it.name) } allPatches[bundle]?.filter { selected.contains(it.name) }
?: throw IllegalArgumentException("Patch bundle $bundle does not exist") ?: throw IllegalArgumentException("Patch bundle $bundle does not exist")
} }
val integrations = bundles.mapNotNull { (_, bundle) -> bundle.integrations }
// Set all patch options. // Set all patch options.
options.forEach { (bundle, bundlePatchOptions) -> options.forEach { (bundle, bundlePatchOptions) ->
val patches = allPatches[bundle] ?: return@forEach val patches = allPatches[bundle] ?: return@forEach
@ -53,7 +51,6 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) {
cacheDir, cacheDir,
frameworkPath, frameworkPath,
aaptPath, aaptPath,
enableMultithreadedDexWriter(),
context, context,
logger, logger,
File(inputFile), File(inputFile),
@ -62,8 +59,7 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) {
).use { session -> ).use { session ->
session.run( session.run(
File(outputFile), File(outputFile),
patchList, patchList
integrations
) )
} }
} }

View File

@ -70,7 +70,7 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
onProgress: ProgressEventHandler, onProgress: ProgressEventHandler,
) = coroutineScope { ) = coroutineScope {
// Get the location of our own Apk. // Get the location of our own Apk.
val managerBaseApk = pm.getPackageInfo(context.packageName)!!.applicationInfo.sourceDir val managerBaseApk = pm.getPackageInfo(context.packageName)!!.applicationInfo!!.sourceDir
val limit = "${prefs.patcherProcessMemoryLimit.get()}M" val limit = "${prefs.patcherProcessMemoryLimit.get()}M"
val propOverride = resolvePropOverride(context)?.absolutePath val propOverride = resolvePropOverride(context)?.absolutePath
@ -148,13 +148,11 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
packageName = packageName, packageName = packageName,
inputFile = inputFile, inputFile = inputFile,
outputFile = outputFile, outputFile = outputFile,
enableMultithrededDexWriter = enableMultithreadedDexWriter(),
configurations = selectedPatches.map { (id, patches) -> configurations = selectedPatches.map { (id, patches) ->
val bundle = bundles[id]!! val bundle = bundles[id]!!
PatchConfiguration( PatchConfiguration(
bundle.patchesJar.absolutePath, bundle.patchesJar.absolutePath,
bundle.integrations?.absolutePath,
patches, patches,
options[id].orEmpty() options[id].orEmpty()
) )

View File

@ -26,7 +26,6 @@ sealed class Runtime(context: Context) : KoinComponent {
context.cacheDir.resolve("framework").also { it.mkdirs() }.absolutePath context.cacheDir.resolve("framework").also { it.mkdirs() }.absolutePath
protected suspend fun bundles() = patchBundlesRepo.bundles.first() protected suspend fun bundles() = patchBundlesRepo.bundles.first()
protected suspend fun enableMultithreadedDexWriter() = prefs.multithreadingDexFileWriter.get()
abstract suspend fun execute( abstract suspend fun execute(
inputFile: String, inputFile: String,

View File

@ -12,14 +12,12 @@ data class Parameters(
val packageName: String, val packageName: String,
val inputFile: String, val inputFile: String,
val outputFile: String, val outputFile: String,
val enableMultithrededDexWriter: Boolean,
val configurations: List<PatchConfiguration>, val configurations: List<PatchConfiguration>,
) : Parcelable ) : Parcelable
@Parcelize @Parcelize
data class PatchConfiguration( data class PatchConfiguration(
val bundlePath: String, val bundlePath: String,
val integrationsPath: String?,
val patches: Set<String>, val patches: Set<String>,
val options: @RawValue Map<String, Map<String, Any?>> val options: @RawValue Map<String, Map<String, Any?>>
) : Parcelable ) : Parcelable

View File

@ -54,13 +54,11 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
logger.info("Memory limit: ${Runtime.getRuntime().maxMemory() / (1024 * 1024)}MB") logger.info("Memory limit: ${Runtime.getRuntime().maxMemory() / (1024 * 1024)}MB")
val integrations =
parameters.configurations.mapNotNull { it.integrationsPath?.let(::File) }
val patchList = parameters.configurations.flatMap { config -> val patchList = parameters.configurations.flatMap { config ->
val bundle = PatchBundle(File(config.bundlePath), null) val bundle = PatchBundle(File(config.bundlePath))
val patches = val patches =
bundle.patchClasses(parameters.packageName).filter { it.name in config.patches } bundle.patches(parameters.packageName).filter { it.name in config.patches }
.associateBy { it.name } .associateBy { it.name }
config.options.forEach { (patchName, opts) -> config.options.forEach { (patchName, opts) ->
@ -81,7 +79,6 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
cacheDir = parameters.cacheDir, cacheDir = parameters.cacheDir,
aaptPath = parameters.aaptPath, aaptPath = parameters.aaptPath,
frameworkDir = parameters.frameworkDir, frameworkDir = parameters.frameworkDir,
multithreadingDexFileWriter = parameters.enableMultithrededDexWriter,
androidContext = context, androidContext = context,
logger = logger, logger = logger,
input = File(parameters.inputFile), input = File(parameters.inputFile),
@ -90,7 +87,7 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
events.progress(name, state?.name, message) events.progress(name, state?.name, message)
} }
).use { ).use {
it.run(File(parameters.outputFile), patchList, integrations) it.run(File(parameters.outputFile), patchList)
} }
events.finished(null) events.finished(null)

View File

@ -154,7 +154,7 @@ class PatcherWorker(
} }
is SelectedApp.Local -> selectedApp.file.also { args.setInputFile(it) } is SelectedApp.Local -> selectedApp.file.also { args.setInputFile(it) }
is SelectedApp.Installed -> File(pm.getPackageInfo(selectedApp.packageName)!!.applicationInfo.sourceDir) is SelectedApp.Installed -> File(pm.getPackageInfo(selectedApp.packageName)!!.applicationInfo!!.sourceDir)
} }
val runtime = if (prefs.useProcessRuntime.get()) { val runtime = if (prefs.useProcessRuntime.get()) {

View File

@ -24,6 +24,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.haptics.HapticCheckbox import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.util.transparentListItemColors
@Composable @Composable
fun AutoUpdatesDialog(onSubmit: (Boolean, Boolean) -> Unit) { fun AutoUpdatesDialog(onSubmit: (Boolean, Boolean) -> Unit) {
@ -77,5 +78,6 @@ private fun AutoUpdatesItem(
leadingContent = { Icon(icon, null) }, leadingContent = { Icon(icon, null) },
headlineContent = { Text(stringResource(headline)) }, headlineContent = { Text(stringResource(headline)) },
trailingContent = { HapticCheckbox(checked = checked, onCheckedChange = null) }, trailingContent = { HapticCheckbox(checked = checked, onCheckedChange = null) },
modifier = Modifier.clickable { onCheckedChange(!checked) } modifier = Modifier.clickable { onCheckedChange(!checked) },
colors = transparentListItemColors
) )

View File

@ -104,7 +104,7 @@ fun BundleInformationDialog(
name = bundleName, name = bundleName,
remoteUrl = bundle.asRemoteOrNull?.endpoint, remoteUrl = bundle.asRemoteOrNull?.endpoint,
patchCount = patchCount, patchCount = patchCount,
version = props?.versionInfo?.patches, version = props?.version,
autoUpdate = props?.autoUpdate ?: false, autoUpdate = props?.autoUpdate ?: false,
onAutoUpdateChange = { onAutoUpdateChange = {
composableScope.launch { composableScope.launch {

View File

@ -45,7 +45,7 @@ fun BundleItem(
val state by bundle.state.collectAsStateWithLifecycle() val state by bundle.state.collectAsStateWithLifecycle()
val version by remember(bundle) { val version by remember(bundle) {
bundle.propsFlow().map { props -> props?.versionInfo?.patches } bundle.propsFlow().map { props -> props?.version }
}.collectAsStateWithLifecycle(null) }.collectAsStateWithLifecycle(null)
val name by bundle.nameState val name by bundle.nameState

View File

@ -23,19 +23,18 @@ import app.revanced.manager.ui.component.TextHorizontalPadding
import app.revanced.manager.ui.component.haptics.HapticCheckbox import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.ui.component.haptics.HapticRadioButton import app.revanced.manager.ui.component.haptics.HapticRadioButton
import app.revanced.manager.ui.model.BundleType import app.revanced.manager.ui.model.BundleType
import app.revanced.manager.util.APK_MIMETYPE import app.revanced.manager.util.BIN_MIMETYPE
import app.revanced.manager.util.JAR_MIMETYPE import app.revanced.manager.util.transparentListItemColors
@Composable @Composable
fun ImportPatchBundleDialog( fun ImportPatchBundleDialog(
onDismiss: () -> Unit, onDismiss: () -> Unit,
onRemoteSubmit: (String, Boolean) -> Unit, onRemoteSubmit: (String, Boolean) -> Unit,
onLocalSubmit: (Uri, Uri?) -> Unit onLocalSubmit: (Uri) -> Unit
) { ) {
var currentStep by rememberSaveable { mutableIntStateOf(0) } var currentStep by rememberSaveable { mutableIntStateOf(0) }
var bundleType by rememberSaveable { mutableStateOf(BundleType.Remote) } var bundleType by rememberSaveable { mutableStateOf(BundleType.Remote) }
var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) } var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) }
var integrations by rememberSaveable { mutableStateOf<Uri?>(null) }
var remoteUrl by rememberSaveable { mutableStateOf("") } var remoteUrl by rememberSaveable { mutableStateOf("") }
var autoUpdate by rememberSaveable { mutableStateOf(false) } var autoUpdate by rememberSaveable { mutableStateOf(false) }
@ -45,16 +44,7 @@ fun ImportPatchBundleDialog(
} }
fun launchPatchActivity() { fun launchPatchActivity() {
patchActivityLauncher.launch(JAR_MIMETYPE) patchActivityLauncher.launch(BIN_MIMETYPE)
}
val integrationsActivityLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
uri?.let { integrations = it }
}
fun launchIntegrationsActivity() {
integrationsActivityLauncher.launch(APK_MIMETYPE)
} }
val steps = listOf<@Composable () -> Unit>( val steps = listOf<@Composable () -> Unit>(
@ -67,11 +57,9 @@ fun ImportPatchBundleDialog(
ImportBundleStep( ImportBundleStep(
bundleType, bundleType,
patchBundle, patchBundle,
integrations,
remoteUrl, remoteUrl,
autoUpdate, autoUpdate,
{ launchPatchActivity() }, { launchPatchActivity() },
{ launchIntegrationsActivity() },
{ remoteUrl = it }, { remoteUrl = it },
{ autoUpdate = it } { autoUpdate = it }
) )
@ -99,13 +87,7 @@ fun ImportPatchBundleDialog(
enabled = inputsAreValid, enabled = inputsAreValid,
onClick = { onClick = {
when (bundleType) { when (bundleType) {
BundleType.Local -> patchBundle?.let { BundleType.Local -> patchBundle?.let(onLocalSubmit)
onLocalSubmit(
it,
integrations
)
}
BundleType.Remote -> onRemoteSubmit(remoteUrl, autoUpdate) BundleType.Remote -> onRemoteSubmit(remoteUrl, autoUpdate)
} }
} }
@ -159,7 +141,8 @@ fun SelectBundleTypeStep(
selected = bundleType == BundleType.Remote, selected = bundleType == BundleType.Remote,
onClick = null onClick = null
) )
} },
colors = transparentListItemColors
) )
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp)) HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
ListItem( ListItem(
@ -175,7 +158,8 @@ fun SelectBundleTypeStep(
selected = bundleType == BundleType.Local, selected = bundleType == BundleType.Local,
onClick = null onClick = null
) )
} },
colors = transparentListItemColors
) )
} }
} }
@ -186,11 +170,9 @@ fun SelectBundleTypeStep(
fun ImportBundleStep( fun ImportBundleStep(
bundleType: BundleType, bundleType: BundleType,
patchBundle: Uri?, patchBundle: Uri?,
integrations: Uri?,
remoteUrl: String, remoteUrl: String,
autoUpdate: Boolean, autoUpdate: Boolean,
launchPatchActivity: () -> Unit, launchPatchActivity: () -> Unit,
launchIntegrationsActivity: () -> Unit,
onRemoteUrlChange: (String) -> Unit, onRemoteUrlChange: (String) -> Unit,
onAutoUpdateChange: (Boolean) -> Unit onAutoUpdateChange: (Boolean) -> Unit
) { ) {
@ -210,19 +192,8 @@ fun ImportBundleStep(
Icon(imageVector = Icons.Default.Topic, contentDescription = null) Icon(imageVector = Icons.Default.Topic, contentDescription = null)
} }
}, },
modifier = Modifier.clickable { launchPatchActivity() } modifier = Modifier.clickable { launchPatchActivity() },
) colors = transparentListItemColors
ListItem(
headlineContent = {
Text(stringResource(R.string.integrations_field))
},
supportingContent = { Text(stringResource(if (integrations != null) R.string.file_field_set else R.string.file_field_not_set)) },
trailingContent = {
IconButton(onClick = launchIntegrationsActivity) {
Icon(imageVector = Icons.Default.Topic, contentDescription = null)
}
},
modifier = Modifier.clickable { launchIntegrationsActivity() }
) )
} }
} }
@ -256,6 +227,7 @@ fun ImportBundleStep(
) )
} }
}, },
colors = transparentListItemColors
) )
} }
} }

View File

@ -13,6 +13,7 @@ import androidx.compose.ui.res.stringResource
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstallType import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.ui.component.haptics.HapticRadioButton import app.revanced.manager.ui.component.haptics.HapticRadioButton
import app.revanced.manager.util.transparentListItemColors
@Composable @Composable
fun InstallPickerDialog( fun InstallPickerDialog(
@ -41,7 +42,7 @@ fun InstallPickerDialog(
title = { Text(stringResource(R.string.select_install_type)) }, title = { Text(stringResource(R.string.select_install_type)) },
text = { text = {
Column { Column {
InstallType.values().forEach { InstallType.entries.forEach {
ListItem( ListItem(
modifier = Modifier.clickable { selectedInstallType = it }, modifier = Modifier.clickable { selectedInstallType = it },
leadingContent = { leadingContent = {
@ -50,7 +51,8 @@ fun InstallPickerDialog(
onClick = null onClick = null
) )
}, },
headlineContent = { Text(stringResource(it.stringResource)) } headlineContent = { Text(stringResource(it.stringResource)) },
colors = transparentListItemColors
) )
} }
} }

View File

@ -50,6 +50,7 @@ import app.revanced.manager.util.mutableStateSetOf
import app.revanced.manager.util.saver.snapshotStateListSaver import app.revanced.manager.util.saver.snapshotStateListSaver
import app.revanced.manager.util.saver.snapshotStateSetSaver import app.revanced.manager.util.saver.snapshotStateSetSaver
import app.revanced.manager.util.toast import app.revanced.manager.util.toast
import app.revanced.manager.util.transparentListItemColors
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.koin.compose.koinInject import org.koin.compose.koinInject
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -58,6 +59,7 @@ import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyColumnState import sh.calvin.reorderable.rememberReorderableLazyColumnState
import java.io.Serializable import java.io.Serializable
import kotlin.random.Random import kotlin.random.Random
import kotlin.reflect.typeOf
import androidx.compose.ui.window.Dialog as ComposeDialog import androidx.compose.ui.window.Dialog as ComposeDialog
private class OptionEditorScope<T : Any>( private class OptionEditorScope<T : Any>(
@ -96,17 +98,17 @@ private interface OptionEditor<T : Any> {
fun Dialog(scope: OptionEditorScope<T>) fun Dialog(scope: OptionEditorScope<T>)
} }
private inline fun <reified T : Serializable> OptionEditor<T>.toMapEditorElements() = arrayOf(
typeOf<T>() to this,
typeOf<List<T>>() to ListOptionEditor(this)
)
private val optionEditors = mapOf( private val optionEditors = mapOf(
"Boolean" to BooleanOptionEditor, *BooleanOptionEditor.toMapEditorElements(),
"String" to StringOptionEditor, *StringOptionEditor.toMapEditorElements(),
"Int" to IntOptionEditor, *IntOptionEditor.toMapEditorElements(),
"Long" to LongOptionEditor, *LongOptionEditor.toMapEditorElements(),
"Float" to FloatOptionEditor, *FloatOptionEditor.toMapEditorElements()
"BooleanArray" to ListOptionEditor(BooleanOptionEditor),
"StringArray" to ListOptionEditor(StringOptionEditor),
"IntArray" to ListOptionEditor(IntOptionEditor),
"LongArray" to ListOptionEditor(LongOptionEditor),
"FloatArray" to ListOptionEditor(FloatOptionEditor),
) )
@Composable @Composable
@ -145,7 +147,7 @@ fun <T : Any> OptionItem(option: Option<T>, value: T?, setValue: (T?) -> Unit) {
val baseOptionEditor = val baseOptionEditor =
optionEditors.getOrDefault(option.type, UnknownTypeEditor) as OptionEditor<T> optionEditors.getOrDefault(option.type, UnknownTypeEditor) as OptionEditor<T>
if (option.type != "Boolean" && option.presets != null) PresetOptionEditor(baseOptionEditor) if (option.type != typeOf<Boolean>() && option.presets != null) PresetOptionEditor(baseOptionEditor)
else baseOptionEditor else baseOptionEditor
} }
@ -405,7 +407,8 @@ private class PresetOptionEditor<T : Any>(private val innerEditor: OptionEditor<
selected = selectedPreset == presetKey, selected = selectedPreset == presetKey,
onClick = { selectedPreset = presetKey } onClick = { selectedPreset = presetKey }
) )
} },
colors = transparentListItemColors
) )
} }
@ -430,7 +433,7 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
option.key, option.key,
option.description, option.description,
option.required, option.required,
option.type.removeSuffix("Array"), option.type.arguments.first().type!!,
null, null,
null null
) { true } ) { true }

View File

@ -33,6 +33,7 @@ import app.revanced.manager.ui.component.SearchView
import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel import app.revanced.manager.ui.viewmodel.AppSelectorViewModel
import app.revanced.manager.util.APK_MIMETYPE import app.revanced.manager.util.APK_MIMETYPE
import app.revanced.manager.util.transparentListItemColors
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -110,9 +111,9 @@ fun AppSelectorScreen(
) )
) )
} }
} },
colors = transparentListItemColors
) )
} }
} }
} else { } else {

View File

@ -79,9 +79,9 @@ fun DashboardScreen(
if (showAddBundleDialog) { if (showAddBundleDialog) {
ImportPatchBundleDialog( ImportPatchBundleDialog(
onDismiss = { showAddBundleDialog = false }, onDismiss = { showAddBundleDialog = false },
onLocalSubmit = { patches, integrations -> onLocalSubmit = { patches ->
showAddBundleDialog = false showAddBundleDialog = false
vm.createLocalSource(patches, integrations) vm.createLocalSource(patches)
}, },
onRemoteSubmit = { url, autoUpdate -> onRemoteSubmit = { url, autoUpdate ->
showAddBundleDialog = false showAddBundleDialog = false

View File

@ -46,6 +46,7 @@ import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW
import app.revanced.manager.util.Options import app.revanced.manager.util.Options
import app.revanced.manager.util.PatchSelection import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.isScrollingUp import app.revanced.manager.util.isScrollingUp
import app.revanced.manager.util.transparentListItemColors
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@ -480,7 +481,8 @@ private fun ListHeader(
) )
} }
} }
} },
colors = transparentListItemColors
) )
} }

View File

@ -104,7 +104,7 @@ fun VersionSelectorScreen(
viewModel.installedApp?.let { (packageInfo, installedApp) -> viewModel.installedApp?.let { (packageInfo, installedApp) ->
SelectedApp.Installed( SelectedApp.Installed(
packageName = viewModel.packageName, packageName = viewModel.packageName,
version = packageInfo.versionName version = packageInfo.versionName!!
).let { ).let {
item { item {
SelectedAppItem( SelectedAppItem(

View File

@ -102,12 +102,6 @@ fun AdvancedSettingsScreen(
headline = R.string.process_runtime_memory_limit, headline = R.string.process_runtime_memory_limit,
description = R.string.process_runtime_memory_limit_description, description = R.string.process_runtime_memory_limit_description,
) )
BooleanItem(
preference = vm.prefs.multithreadingDexFileWriter,
coroutineScope = vm.viewModelScope,
headline = R.string.multithreaded_dex_file_writer,
description = R.string.multithreaded_dex_file_writer_description,
)
GroupHeader(stringResource(R.string.safeguards)) GroupHeader(stringResource(R.string.safeguards))
BooleanItem( BooleanItem(

View File

@ -69,7 +69,7 @@ class AppSelectorViewModel(
pm.getPackageInfo(this)?.let { packageInfo -> pm.getPackageInfo(this)?.let { packageInfo ->
SelectedApp.Local( SelectedApp.Local(
packageName = packageInfo.packageName, packageName = packageInfo.packageName,
version = packageInfo.versionName, version = packageInfo.versionName!!,
file = this, file = this,
temporary = true temporary = true
) )

View File

@ -95,13 +95,10 @@ class DashboardViewModel(
selectedSources.clear() selectedSources.clear()
} }
fun createLocalSource(patchBundle: Uri, integrations: Uri?) = fun createLocalSource(patchBundle: Uri) =
viewModelScope.launch { viewModelScope.launch {
contentResolver.openInputStream(patchBundle)!!.use { patchesStream -> contentResolver.openInputStream(patchBundle)!!.use { patchesStream ->
integrations?.let { contentResolver.openInputStream(it) } patchBundleRepository.createLocal(patchesStream)
.use { integrationsStream ->
patchBundleRepository.createLocal(patchesStream, integrationsStream)
}
} }
} }

View File

@ -152,8 +152,8 @@ class PatcherViewModel(
) )
val patcherSucceeded = val patcherSucceeded =
workManager.getWorkInfoByIdLiveData(patcherWorkerId).map { workInfo: WorkInfo -> workManager.getWorkInfoByIdLiveData(patcherWorkerId).map { workInfo: WorkInfo? ->
when (workInfo.state) { when (workInfo?.state) {
WorkInfo.State.SUCCEEDED -> true WorkInfo.State.SUCCEEDED -> true
WorkInfo.State.FAILED -> false WorkInfo.State.FAILED -> false
else -> null else -> null
@ -308,7 +308,7 @@ class PatcherViewModel(
// Check for base APK, first check if the app is already installed // Check for base APK, first check if the app is already installed
if (existingPackageInfo == null) { if (existingPackageInfo == null) {
// If the app is not installed, check if the output file is a base apk // If the app is not installed, check if the output file is a base apk
if (currentPackageInfo.splitNames != null) { if (currentPackageInfo.splitNames.isNotEmpty()) {
// Exit if there is no base APK package // Exit if there is no base APK package
installerStatusDialogModel.packageInstallerStatus = installerStatusDialogModel.packageInstallerStatus =
PackageInstaller.STATUS_FAILURE_INVALID PackageInstaller.STATUS_FAILURE_INVALID

View File

@ -1,14 +1,8 @@
package app.revanced.manager.util package app.revanced.manager.util
private const val team = "revanced"
const val ghOrganization = "https://github.com/$team"
const val ghCli = "$team/revanced-cli"
const val ghPatches = "$team/revanced-patches"
const val ghPatcher = "$team/revanced-patcher"
const val ghManager = "$team/revanced-manager"
const val ghIntegrations = "$team/revanced-integrations"
const val tag = "ReVanced Manager" const val tag = "ReVanced Manager"
const val JAR_MIMETYPE = "application/java-archive" const val JAR_MIMETYPE = "application/java-archive"
const val APK_MIMETYPE = "application/vnd.android.package-archive" const val APK_MIMETYPE = "application/vnd.android.package-archive"
const val JSON_MIMETYPE = "application/json" const val JSON_MIMETYPE = "application/json"
const val BIN_MIMETYPE = "application/octet-stream"

View File

@ -106,7 +106,7 @@ class PM(
val pkgInfo = app.packageManager.getPackageArchiveInfo(path, 0) ?: return null val pkgInfo = app.packageManager.getPackageArchiveInfo(path, 0) ?: return null
// This is needed in order to load label and icon. // This is needed in order to load label and icon.
pkgInfo.applicationInfo.apply { pkgInfo.applicationInfo!!.apply {
sourceDir = path sourceDir = path
publicSourceDir = path publicSourceDir = path
} }
@ -114,7 +114,7 @@ class PM(
return pkgInfo return pkgInfo
} }
fun PackageInfo.label() = this.applicationInfo.loadLabel(app.packageManager).toString() fun PackageInfo.label() = this.applicationInfo!!.loadLabel(app.packageManager).toString()
fun getVersionCode(packageInfo: PackageInfo) = PackageInfoCompat.getLongVersionCode(packageInfo) fun getVersionCode(packageInfo: PackageInfo) = PackageInfoCompat.getLongVersionCode(packageInfo)

View File

@ -13,6 +13,8 @@ import android.widget.Toast
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material3.ListItemColors
import androidx.compose.material3.ListItemDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.State import androidx.compose.runtime.State
@ -241,3 +243,13 @@ fun <T, R> ((T) -> R).withHapticFeedback(constant: Int): (T) -> R {
this(it) this(it)
} }
} }
private var transparentListItemColorsCached: ListItemColors? = null
/**
* The default ListItem colors, but with [ListItemColors.containerColor] set to [Color.Transparent].
*/
val transparentListItemColors
@Composable get() = transparentListItemColorsCached
?: ListItemDefaults.colors(containerColor = Color.Transparent)
.also { transparentListItemColorsCached = it }

View File

@ -2,7 +2,6 @@
<string name="app_name">ReVanced Manager</string> <string name="app_name">ReVanced Manager</string>
<string name="patcher">Patcher</string> <string name="patcher">Patcher</string>
<string name="patches">Patches</string> <string name="patches">Patches</string>
<string name="integrations">Integrations</string>
<string name="cli">CLI</string> <string name="cli">CLI</string>
<string name="manager">Manager</string> <string name="manager">Manager</string>
@ -20,7 +19,6 @@
<string name="import_bundle">Import patch bundle</string> <string name="import_bundle">Import patch bundle</string>
<string name="bundle_patches">Bundle patches</string> <string name="bundle_patches">Bundle patches</string>
<string name="patch_bundle_field">Patch bundle</string> <string name="patch_bundle_field">Patch bundle</string>
<string name="integrations_field">Integrations</string>
<string name="file_field_set">Selected</string> <string name="file_field_set">Selected</string>
<string name="file_field_not_set">Not selected</string> <string name="file_field_not_set">Not selected</string>
@ -71,8 +69,6 @@
<string name="dynamic_color_description">Adapt colors to the wallpaper</string> <string name="dynamic_color_description">Adapt colors to the wallpaper</string>
<string name="theme">Theme</string> <string name="theme">Theme</string>
<string name="theme_description">Choose between light or dark theme</string> <string name="theme_description">Choose between light or dark theme</string>
<string name="multithreaded_dex_file_writer">Multi-threaded DEX file writer</string>
<string name="multithreaded_dex_file_writer_description">Use multiple cores to write DEX files. This is faster, but uses more memory</string>
<string name="safeguards">Safeguards</string> <string name="safeguards">Safeguards</string>
<string name="patch_compat_check">Disable version compatibility check</string> <string name="patch_compat_check">Disable version compatibility check</string>
<string name="patch_compat_check_description">The check restricts patches to supported app versions</string> <string name="patch_compat_check_description">The check restricts patches to supported app versions</string>
@ -123,7 +119,6 @@
<string name="search_apps">Search apps…</string> <string name="search_apps">Search apps…</string>
<string name="loading_body">Loading…</string> <string name="loading_body">Loading…</string>
<string name="downloading_patches">Downloading patch bundle…</string> <string name="downloading_patches">Downloading patch bundle…</string>
<string name="downloading_integrations">Downloading Integrations…</string>
<string name="options">Options</string> <string name="options">Options</string>
<string name="ok">OK</string> <string name="ok">OK</string>
@ -186,7 +181,6 @@
<string name="download_apk">Download APK file</string> <string name="download_apk">Download APK file</string>
<string name="source_download_fail">Failed to download patch bundle: %s</string> <string name="source_download_fail">Failed to download patch bundle: %s</string>
<string name="source_replace_fail">Failed to load updated patch bundle: %s</string> <string name="source_replace_fail">Failed to load updated patch bundle: %s</string>
<string name="source_replace_integrations_fail">Failed to update integrations: %s</string>
<string name="no_patched_apps_found">No patched apps found</string> <string name="no_patched_apps_found">No patched apps found</string>
<string name="tap_on_patches">Tap on the patches to get more information about them</string> <string name="tap_on_patches">Tap on the patches to get more information about them</string>
<string name="bundles_selected">%s selected</string> <string name="bundles_selected">%s selected</string>

View File

@ -2,5 +2,8 @@ plugins {
alias(libs.plugins.android.application) apply false alias(libs.plugins.android.application) apply false
alias(libs.plugins.devtools) apply false alias(libs.plugins.devtools) apply false
alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.kotlin.parcelize) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.about.libraries) apply false alias(libs.plugins.about.libraries) apply false
} }

View File

@ -1,31 +1,30 @@
[versions] [versions]
ktx = "1.13.1" ktx = "1.15.0"
material3 = "1.2.1" material3 = "1.3.1"
ui-tooling = "1.6.8" ui-tooling = "1.7.5"
viewmodel-lifecycle = "2.8.3" viewmodel-lifecycle = "2.8.7"
splash-screen = "1.0.1" splash-screen = "1.0.1"
compose-activity = "1.9.0" compose-activity = "1.9.3"
paging = "3.3.0"
preferences-datastore = "1.1.1" preferences-datastore = "1.1.1"
work-runtime = "2.9.0" work-runtime = "2.10.0"
compose-bom = "2024.06.00" compose-bom = "2024.10.01"
accompanist = "0.34.0" accompanist = "0.34.0"
placeholder = "1.1.2" placeholder = "1.1.2"
reorderable = "1.5.2" reorderable = "1.5.2"
serialization = "1.6.3" serialization = "1.7.3"
collection = "0.3.7" collection = "0.3.8"
room-version = "2.6.1" room-version = "2.6.1"
revanced-patcher = "19.3.1" revanced-patcher = "21.0.0"
revanced-library = "2.2.1" revanced-library = "3.0.2"
koin-version = "3.5.3" koin-version = "3.5.3"
koin-version-compose = "3.5.3" koin-version-compose = "3.5.3"
reimagined-navigation = "1.5.0" reimagined-navigation = "1.5.0"
ktor = "2.3.9" ktor = "2.3.9"
markdown-renderer = "0.22.0" markdown-renderer = "0.22.0"
fading-edges = "1.0.4" fading-edges = "1.0.4"
android-gradle-plugin = "8.3.2" kotlin = "2.0.21"
kotlin-gradle-plugin = "1.9.22" android-gradle-plugin = "8.7.2"
dev-tools-gradle-plugin = "1.9.22-1.0.17" dev-tools-gradle-plugin = "2.0.21-1.0.27"
about-libraries-gradle-plugin = "11.1.1" about-libraries-gradle-plugin = "11.1.1"
coil = "2.6.0" coil = "2.6.0"
app-icon-loader-coil = "1.5.0" app-icon-loader-coil = "1.5.0"
@ -44,7 +43,6 @@ runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", ve
runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "viewmodel-lifecycle" } runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "viewmodel-lifecycle" }
splash-screen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splash-screen" } splash-screen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splash-screen" }
compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" } compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" }
paging-common-ktx = { group = "androidx.paging", name = "paging-common-ktx", version.ref = "paging" }
work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work-runtime" } work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work-runtime" }
preferences-datastore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "preferences-datastore" } preferences-datastore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "preferences-datastore" }
@ -135,6 +133,9 @@ compose-icons-fontawesome = { group = "com.github.BenjaminHalko.compose-icons",
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle-plugin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
devtools = { id = "com.google.devtools.ksp", version.ref = "dev-tools-gradle-plugin" } devtools = { id = "com.google.devtools.ksp", version.ref = "dev-tools-gradle-plugin" }
about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "about-libraries-gradle-plugin" } about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "about-libraries-gradle-plugin" }

View File

@ -1,6 +1,7 @@
#Tue Nov 12 21:36:50 CET 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME