mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-04-30 05:54:26 +02:00
chore: update dependencies
🦀 integrations are gone! 🦀
This commit is contained in:
parent
cf322147d5
commit
20c13ee71c
@ -3,21 +3,22 @@ import kotlin.random.Random
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
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.about.libraries)
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "1.9.23"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.manager"
|
||||
compileSdk = 34
|
||||
buildToolsVersion = "34.0.0"
|
||||
compileSdk = 35
|
||||
buildToolsVersion = "35.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "app.revanced.manager"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "0.0.1"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@ -81,9 +82,11 @@ android {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildFeatures.compose = true
|
||||
buildFeatures.aidl = true
|
||||
buildFeatures.buildConfig=true
|
||||
buildFeatures {
|
||||
compose = true
|
||||
aidl = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
android {
|
||||
androidResources {
|
||||
@ -91,7 +94,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
composeOptions.kotlinCompilerExtensionVersion = "1.5.10"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
@ -112,7 +114,6 @@ dependencies {
|
||||
implementation(libs.runtime.compose)
|
||||
implementation(libs.splash.screen)
|
||||
implementation(libs.compose.activity)
|
||||
implementation(libs.paging.common.ktx)
|
||||
implementation(libs.work.runtime.ktx)
|
||||
implementation(libs.preferences.datastore)
|
||||
|
||||
|
@ -2,11 +2,11 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "1dd9d5c0201fdf3cfef3ae669fd65e46",
|
||||
"identityHash": "c385297c07ea54804dc8526c388f706d",
|
||||
"entities": [
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
@ -20,6 +20,12 @@
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "version",
|
||||
"columnName": "version",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "source",
|
||||
"columnName": "source",
|
||||
@ -31,18 +37,6 @@
|
||||
"columnName": "auto_update",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionInfo.patches",
|
||||
"columnName": "version",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionInfo.integrations",
|
||||
"columnName": "integrations_version",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
@ -397,7 +391,7 @@
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"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')"
|
||||
]
|
||||
}
|
||||
}
|
@ -8,11 +8,11 @@ interface PatchBundleDao {
|
||||
@Query("SELECT * FROM patch_bundles")
|
||||
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?>
|
||||
|
||||
@Query("UPDATE patch_bundles SET version = :patches, integrations_version = :integrations WHERE uid = :uid")
|
||||
suspend fun updateVersion(uid: Int, patches: String?, integrations: String?)
|
||||
@Query("UPDATE patch_bundles SET version = :patches WHERE uid = :uid")
|
||||
suspend fun updateVersion(uid: Int, patches: String?)
|
||||
|
||||
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
|
||||
suspend fun setAutoUpdate(uid: Int, value: Boolean)
|
||||
@ -26,7 +26,7 @@ interface PatchBundleDao {
|
||||
@Transaction
|
||||
suspend fun reset() {
|
||||
purgeCustomBundles()
|
||||
updateVersion(0, null, null) // Reset the main source
|
||||
updateVersion(0, null) // Reset the main source
|
||||
}
|
||||
|
||||
@Query("DELETE FROM patch_bundles WHERE uid = :uid")
|
||||
|
@ -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")
|
||||
data class PatchBundleEntity(
|
||||
@PrimaryKey val uid: Int,
|
||||
@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 = "auto_update") val autoUpdate: Boolean
|
||||
)
|
||||
|
||||
data class BundleProperties(
|
||||
@Embedded val versionInfo: VersionInfo,
|
||||
@ColumnInfo(name = "version") val version: String? = null,
|
||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||
)
|
@ -20,6 +20,8 @@ import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.long
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
@Entity(
|
||||
tableName = "options",
|
||||
@ -46,8 +48,8 @@ data class Option(
|
||||
|
||||
val errorMessage = "Cannot deserialize value as ${option.type}"
|
||||
try {
|
||||
if (option.type.endsWith("Array")) {
|
||||
val elementType = option.type.removeSuffix("Array")
|
||||
if (option.type.classifier == List::class) {
|
||||
val elementType = option.type.arguments.first().type!!
|
||||
return raw.jsonArray.map { deserializeBasicType(elementType, it.jsonPrimitive) }
|
||||
}
|
||||
|
||||
@ -67,12 +69,17 @@ data class Option(
|
||||
allowSpecialFloatingPointValues = true
|
||||
}
|
||||
|
||||
private fun deserializeBasicType(type: String, value: JsonPrimitive) = when (type) {
|
||||
"Boolean" -> value.boolean
|
||||
"Int" -> value.int
|
||||
"Long" -> value.long
|
||||
"Float" -> value.float
|
||||
"String" -> value.content.also { if (!value.isString) throw SerializationException("Expected value to be a string: $value") }
|
||||
private fun deserializeBasicType(type: KType, value: JsonPrimitive) = when (type) {
|
||||
typeOf<Boolean>() -> value.boolean
|
||||
typeOf<Int>() -> value.int
|
||||
typeOf<Long>() -> value.long
|
||||
typeOf<Float>() -> value.float
|
||||
typeOf<String>() -> value.content.also {
|
||||
if (!value.isString) throw SerializationException(
|
||||
"Expected value to be a string: $value"
|
||||
)
|
||||
}
|
||||
|
||||
else -> throw SerializationException("Unknown type: $type")
|
||||
}
|
||||
|
||||
|
@ -4,29 +4,18 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
class LocalPatchBundle(name: String, id: Int, directory: File) :
|
||||
PatchBundleSource(name, id, directory) {
|
||||
suspend fun replace(patches: InputStream? = null, integrations: InputStream? = null) {
|
||||
suspend fun replace(patches: InputStream) {
|
||||
withContext(Dispatchers.IO) {
|
||||
patches?.let { inputStream ->
|
||||
patchBundleOutputStream().use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
integrations?.let {
|
||||
Files.copy(
|
||||
it,
|
||||
this@LocalPatchBundle.integrationsFile.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
patches.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
|
||||
reload()?.also {
|
||||
saveVersion(it.readManifestAttribute("Version"), null)
|
||||
saveVersion(it.readManifestAttribute("Version"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
|
||||
protected val configRepository: PatchBundlePersistenceRepository by inject()
|
||||
private val app: Application by inject()
|
||||
protected val patchesFile = directory.resolve("patches.jar")
|
||||
protected val integrationsFile = directory.resolve("integrations.apk")
|
||||
|
||||
private val _state = MutableStateFlow(load())
|
||||
val state = _state.asStateFlow()
|
||||
@ -58,7 +57,7 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
|
||||
if (!hasInstalled()) return State.Missing
|
||||
|
||||
return try {
|
||||
State.Loaded(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists)))
|
||||
State.Loaded(PatchBundle(patchesFile))
|
||||
} catch (t: Throwable) {
|
||||
Log.e(tag, "Failed to load patch bundle with UID $uid", 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)
|
||||
suspend fun getProps() = propsFlow().first()!!
|
||||
|
||||
suspend fun currentVersion() = getProps().versionInfo
|
||||
protected suspend fun saveVersion(patches: String?, integrations: String?) =
|
||||
configRepository.updateVersion(uid, patches, integrations)
|
||||
suspend fun currentVersion() = getProps().version
|
||||
protected suspend fun saveVersion(version: String?) =
|
||||
configRepository.updateVersion(uid, version)
|
||||
|
||||
suspend fun setName(name: String) {
|
||||
configRepository.setName(uid, name)
|
||||
|
@ -1,20 +1,12 @@
|
||||
package app.revanced.manager.domain.bundles
|
||||
|
||||
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.Extensions.findAssetByType
|
||||
import app.revanced.manager.network.dto.BundleAsset
|
||||
import app.revanced.manager.network.dto.BundleInfo
|
||||
import app.revanced.manager.network.dto.PatchBundleInfo
|
||||
import app.revanced.manager.network.service.HttpService
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
@ -24,27 +16,17 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
|
||||
PatchBundleSource(name, id, directory) {
|
||||
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) {
|
||||
val (patches, integrations) = info
|
||||
coroutineScope {
|
||||
launch {
|
||||
private suspend fun download(info: PatchBundleInfo) = withContext(Dispatchers.IO) {
|
||||
val (version, url) = info
|
||||
patchBundleOutputStream().use {
|
||||
http.streamTo(it) {
|
||||
url(patches.url)
|
||||
}
|
||||
url(url)
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
http.download(integrationsFile) {
|
||||
url(integrations.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveVersion(patches.version, integrations.version)
|
||||
saveVersion(version)
|
||||
reload()
|
||||
}
|
||||
|
||||
@ -54,20 +36,15 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
|
||||
|
||||
suspend fun update(): Boolean = withContext(Dispatchers.IO) {
|
||||
val info = getLatestInfo()
|
||||
if (hasInstalled() && VersionInfo(
|
||||
info.patches.version,
|
||||
info.integrations.version
|
||||
) == currentVersion()
|
||||
) {
|
||||
if (hasInstalled() && info.version == currentVersion())
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
download(info)
|
||||
true
|
||||
}
|
||||
|
||||
suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) {
|
||||
arrayOf(patchesFile, integrationsFile).forEach(File::delete)
|
||||
patchesFile.delete()
|
||||
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) :
|
||||
RemotePatchBundle(name, id, directory, endpoint) {
|
||||
override suspend fun getLatestInfo() = withContext(Dispatchers.IO) {
|
||||
http.request<BundleInfo> {
|
||||
http.request<PatchBundleInfo> {
|
||||
url(endpoint)
|
||||
}.getOrThrow()
|
||||
}
|
||||
@ -91,22 +68,10 @@ class APIPatchBundle(name: String, id: Int, directory: File, endpoint: String) :
|
||||
RemotePatchBundle(name, id, directory, endpoint) {
|
||||
private val api: ReVancedAPI by inject()
|
||||
|
||||
override suspend fun getLatestInfo() = coroutineScope {
|
||||
fun getAssetAsync(repo: String, mime: String) = async(Dispatchers.IO) {
|
||||
api
|
||||
.getLatestRelease(repo)
|
||||
override suspend fun getLatestInfo() = api
|
||||
.getLatestRelease("revanced-patches")
|
||||
.getOrThrow()
|
||||
.let {
|
||||
BundleAsset(it.version, it.findAssetByType(mime).downloadUrl)
|
||||
}
|
||||
}
|
||||
|
||||
val patches = getAssetAsync("revanced-patches", JAR_MIMETYPE)
|
||||
val integrations = getAssetAsync("revanced-integrations", APK_MIMETYPE)
|
||||
|
||||
BundleInfo(
|
||||
patches.await(),
|
||||
integrations.await()
|
||||
)
|
||||
PatchBundleInfo(it.version, it.assets.first { it.name.endsWith(".rvp") }.downloadUrl)
|
||||
}
|
||||
}
|
@ -109,7 +109,12 @@ class RootInstaller(
|
||||
|
||||
stockAPK?.let { stockApp ->
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
import java.security.UnrecoverableKeyException
|
||||
import java.util.Date
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
|
||||
companion object Constants {
|
||||
@ -19,6 +21,7 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
|
||||
* Default alias and password for the keystore.
|
||||
*/
|
||||
const val DEFAULT = "ReVanced"
|
||||
private val eightYearsFromNow get() = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24)
|
||||
}
|
||||
|
||||
private val keystorePath =
|
||||
@ -29,23 +32,26 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
|
||||
prefs.keystorePass.value = pass
|
||||
}
|
||||
|
||||
private suspend fun signingOptions(path: File = keystorePath) = ApkUtils.SigningOptions(
|
||||
private suspend fun signingDetails(path: File = keystorePath) = ApkUtils.KeyStoreDetails(
|
||||
keyStore = path,
|
||||
keyStorePassword = null,
|
||||
alias = prefs.keystoreCommonName.get(),
|
||||
signer = prefs.keystoreCommonName.get(),
|
||||
password = prefs.keystorePass.get()
|
||||
)
|
||||
|
||||
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) {
|
||||
val keyCertPair = ApkSigner.newPrivateKeyCertificatePair(
|
||||
prefs.keystoreCommonName.get(),
|
||||
eightYearsFromNow
|
||||
)
|
||||
val ks = ApkSigner.newKeyStore(
|
||||
setOf(
|
||||
ApkSigner.KeyStoreEntry(
|
||||
DEFAULT, DEFAULT
|
||||
DEFAULT, DEFAULT, keyCertPair
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -64,7 +70,7 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) {
|
||||
try {
|
||||
val ks = ApkSigner.readKeyStore(ByteArrayInputStream(keystoreData), null)
|
||||
|
||||
ApkSigner.readKeyCertificatePair(ks, cn, pass)
|
||||
ApkSigner.readPrivateKeyCertificatePair(ks, cn, pass)
|
||||
} catch (_: UnrecoverableKeyException) {
|
||||
return false
|
||||
} catch (_: IllegalArgumentException) {
|
||||
|
@ -12,7 +12,6 @@ class PreferencesManager(
|
||||
|
||||
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 patcherProcessMemoryLimit = intPreference("process_runtime_memory_limit", 700)
|
||||
|
||||
|
@ -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.bundles.PatchBundleEntity
|
||||
import app.revanced.manager.data.room.bundles.Source
|
||||
import app.revanced.manager.data.room.bundles.VersionInfo
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
||||
class PatchBundlePersistenceRepository(db: AppDatabase) {
|
||||
@ -26,7 +25,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
||||
PatchBundleEntity(
|
||||
uid = generateUid(),
|
||||
name = name,
|
||||
versionInfo = VersionInfo(),
|
||||
version = null,
|
||||
source = source,
|
||||
autoUpdate = autoUpdate
|
||||
).also {
|
||||
@ -35,8 +34,8 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
||||
|
||||
suspend fun delete(uid: Int) = dao.remove(uid)
|
||||
|
||||
suspend fun updateVersion(uid: Int, patches: String?, integrations: String?) =
|
||||
dao.updateVersion(uid, patches, integrations)
|
||||
suspend fun updateVersion(uid: Int, version: String?) =
|
||||
dao.updateVersion(uid, version)
|
||||
|
||||
suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value)
|
||||
|
||||
@ -48,7 +47,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
||||
val defaultSource = PatchBundleEntity(
|
||||
uid = 0,
|
||||
name = "",
|
||||
versionInfo = VersionInfo(),
|
||||
version = null,
|
||||
source = Source.API,
|
||||
autoUpdate = false
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ package app.revanced.manager.domain.repository
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import app.revanced.library.PatchUtils
|
||||
import app.revanced.library.mostCommonCompatibleVersions
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.data.platform.NetworkInfo
|
||||
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
||||
@ -55,7 +55,7 @@ class PatchBundleRepository(
|
||||
val allPatches =
|
||||
it.values.flatMap { bundle -> bundle.patches.map(PatchInfo::toPatcherPatch) }.toSet()
|
||||
|
||||
PatchUtils.getMostCommonCompatibleVersions(allPatches, countUnusedPatches = true)
|
||||
allPatches.mostCommonCompatibleVersions(countUnusedPatches = true)
|
||||
.mapValues { (_, versions) ->
|
||||
if (versions.keys.size < 2)
|
||||
return@mapValues versions.keys.firstOrNull()
|
||||
@ -137,11 +137,11 @@ class PatchBundleRepository(
|
||||
private fun addBundle(patchBundle: PatchBundleSource) =
|
||||
_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 bundle = LocalPatchBundle("", uid, directoryOf(uid))
|
||||
|
||||
bundle.replace(patches, integrations)
|
||||
bundle.replace(patches)
|
||||
addBundle(bundle)
|
||||
}
|
||||
|
||||
|
@ -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)
|
@ -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)
|
@ -22,7 +22,6 @@ class Session(
|
||||
cacheDir: String,
|
||||
frameworkDir: String,
|
||||
aaptPath: String,
|
||||
multithreadingDexFileWriter: Boolean,
|
||||
private val androidContext: Context,
|
||||
private val logger: Logger,
|
||||
private val input: File,
|
||||
@ -38,8 +37,7 @@ class Session(
|
||||
apkFile = input,
|
||||
temporaryFilesPath = tempDir,
|
||||
frameworkFileDirectory = frameworkDir,
|
||||
aaptBinaryPath = aaptPath,
|
||||
multithreadingDexFileWriter = multithreadingDexFileWriter,
|
||||
aaptBinaryPath = aaptPath
|
||||
)
|
||||
)
|
||||
|
||||
@ -51,7 +49,7 @@ class Session(
|
||||
state = State.RUNNING
|
||||
)
|
||||
|
||||
this.apply(true).collect { (patch, exception) ->
|
||||
this().collect { (patch, exception) ->
|
||||
if (patch !in selectedPatches) return@collect
|
||||
|
||||
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
|
||||
|
||||
java.util.logging.Logger.getLogger("").apply {
|
||||
@ -103,8 +101,7 @@ class Session(
|
||||
|
||||
with(patcher) {
|
||||
logger.info("Merging integrations")
|
||||
acceptIntegrations(integrations.toSet())
|
||||
acceptPatches(selectedPatches.toSet())
|
||||
this += selectedPatches.toSet()
|
||||
|
||||
logger.info("Applying patches...")
|
||||
applyPatchesVerbose(selectedPatches.sortedBy { it.name })
|
||||
|
@ -2,17 +2,17 @@ package app.revanced.manager.patcher.patch
|
||||
|
||||
import android.util.Log
|
||||
import app.revanced.manager.util.tag
|
||||
import app.revanced.patcher.PatchBundleLoader
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchLoader
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
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 fun load(): Iterable<Patch<*>> {
|
||||
patchesJar.setReadOnly()
|
||||
return PatchBundleLoader.Dex(patchesJar, optimizedDexDirectory = null)
|
||||
return PatchLoader.Dex(setOf(patchesJar))
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
fun patchClasses(packageName: String) = loader.filter { patch ->
|
||||
fun patches(packageName: String) = loader.filter { patch ->
|
||||
val compatiblePackages = patch.compatiblePackages
|
||||
?: // The patch has no compatibility constraints, which means it is universal.
|
||||
return@filter true
|
||||
|
||||
if (!compatiblePackages.any { it.name == packageName }) {
|
||||
if (!compatiblePackages.any { (name, _) -> name == packageName }) {
|
||||
// Patch is not compatible with this package.
|
||||
return@filter false
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
package app.revanced.manager.patcher.patch
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.options.PatchOption
|
||||
import app.revanced.patcher.patch.Option as PatchOption
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlin.reflect.KType
|
||||
|
||||
data class PatchInfo(
|
||||
val name: String,
|
||||
@ -21,7 +21,12 @@ data class PatchInfo(
|
||||
patch.name.orEmpty(),
|
||||
patch.description,
|
||||
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()
|
||||
)
|
||||
|
||||
@ -45,16 +50,11 @@ data class PatchInfo(
|
||||
* The resulting patch cannot be executed.
|
||||
* This is necessary because some functions in ReVanced Library only accept full [Patch] objects.
|
||||
*/
|
||||
fun toPatcherPatch(): Patch<*> = object : ResourcePatch(
|
||||
name = name,
|
||||
description = description,
|
||||
compatiblePackages = compatiblePackages
|
||||
?.map(app.revanced.manager.patcher.patch.CompatiblePackage::toPatcherCompatiblePackage)
|
||||
?.toSet(),
|
||||
use = include,
|
||||
) {
|
||||
override fun execute(context: ResourceContext) =
|
||||
throw Exception("Metadata patches cannot be executed")
|
||||
fun toPatcherPatch(): Patch<*> =
|
||||
resourcePatch(name = name, description = description, use = include) {
|
||||
compatiblePackages?.let { pkgs ->
|
||||
compatibleWith(*pkgs.map { it.packageName to it.versions }.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,20 +62,7 @@ data class PatchInfo(
|
||||
data class CompatiblePackage(
|
||||
val packageName: 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
|
||||
data class Option<T>(
|
||||
@ -83,7 +70,7 @@ data class Option<T>(
|
||||
val key: String,
|
||||
val description: String,
|
||||
val required: Boolean,
|
||||
val type: String,
|
||||
val type: KType,
|
||||
val default: T?,
|
||||
val presets: Map<String, T?>?,
|
||||
val validator: (T?) -> Boolean,
|
||||
@ -93,7 +80,7 @@ data class Option<T>(
|
||||
option.key,
|
||||
option.description.orEmpty(),
|
||||
option.required,
|
||||
option.valueType,
|
||||
option.type,
|
||||
option.default,
|
||||
option.values,
|
||||
{ option.validator(option, it) },
|
||||
|
@ -27,15 +27,13 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) {
|
||||
|
||||
val selectedBundles = selectedPatches.keys
|
||||
val allPatches = bundles.filterKeys { selectedBundles.contains(it) }
|
||||
.mapValues { (_, bundle) -> bundle.patchClasses(packageName) }
|
||||
.mapValues { (_, bundle) -> bundle.patches(packageName) }
|
||||
|
||||
val patchList = selectedPatches.flatMap { (bundle, selected) ->
|
||||
allPatches[bundle]?.filter { selected.contains(it.name) }
|
||||
?: throw IllegalArgumentException("Patch bundle $bundle does not exist")
|
||||
}
|
||||
|
||||
val integrations = bundles.mapNotNull { (_, bundle) -> bundle.integrations }
|
||||
|
||||
// Set all patch options.
|
||||
options.forEach { (bundle, bundlePatchOptions) ->
|
||||
val patches = allPatches[bundle] ?: return@forEach
|
||||
@ -53,7 +51,6 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) {
|
||||
cacheDir,
|
||||
frameworkPath,
|
||||
aaptPath,
|
||||
enableMultithreadedDexWriter(),
|
||||
context,
|
||||
logger,
|
||||
File(inputFile),
|
||||
@ -62,8 +59,7 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) {
|
||||
).use { session ->
|
||||
session.run(
|
||||
File(outputFile),
|
||||
patchList,
|
||||
integrations
|
||||
patchList
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
|
||||
onProgress: ProgressEventHandler,
|
||||
) = coroutineScope {
|
||||
// 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 propOverride = resolvePropOverride(context)?.absolutePath
|
||||
@ -148,13 +148,11 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
|
||||
packageName = packageName,
|
||||
inputFile = inputFile,
|
||||
outputFile = outputFile,
|
||||
enableMultithrededDexWriter = enableMultithreadedDexWriter(),
|
||||
configurations = selectedPatches.map { (id, patches) ->
|
||||
val bundle = bundles[id]!!
|
||||
|
||||
PatchConfiguration(
|
||||
bundle.patchesJar.absolutePath,
|
||||
bundle.integrations?.absolutePath,
|
||||
patches,
|
||||
options[id].orEmpty()
|
||||
)
|
||||
|
@ -26,7 +26,6 @@ sealed class Runtime(context: Context) : KoinComponent {
|
||||
context.cacheDir.resolve("framework").also { it.mkdirs() }.absolutePath
|
||||
|
||||
protected suspend fun bundles() = patchBundlesRepo.bundles.first()
|
||||
protected suspend fun enableMultithreadedDexWriter() = prefs.multithreadingDexFileWriter.get()
|
||||
|
||||
abstract suspend fun execute(
|
||||
inputFile: String,
|
||||
|
@ -12,14 +12,12 @@ data class Parameters(
|
||||
val packageName: String,
|
||||
val inputFile: String,
|
||||
val outputFile: String,
|
||||
val enableMultithrededDexWriter: Boolean,
|
||||
val configurations: List<PatchConfiguration>,
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class PatchConfiguration(
|
||||
val bundlePath: String,
|
||||
val integrationsPath: String?,
|
||||
val patches: Set<String>,
|
||||
val options: @RawValue Map<String, Map<String, Any?>>
|
||||
) : Parcelable
|
@ -54,13 +54,11 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
|
||||
|
||||
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 bundle = PatchBundle(File(config.bundlePath), null)
|
||||
val bundle = PatchBundle(File(config.bundlePath))
|
||||
|
||||
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 }
|
||||
|
||||
config.options.forEach { (patchName, opts) ->
|
||||
@ -81,7 +79,6 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
|
||||
cacheDir = parameters.cacheDir,
|
||||
aaptPath = parameters.aaptPath,
|
||||
frameworkDir = parameters.frameworkDir,
|
||||
multithreadingDexFileWriter = parameters.enableMultithrededDexWriter,
|
||||
androidContext = context,
|
||||
logger = logger,
|
||||
input = File(parameters.inputFile),
|
||||
@ -90,7 +87,7 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() {
|
||||
events.progress(name, state?.name, message)
|
||||
}
|
||||
).use {
|
||||
it.run(File(parameters.outputFile), patchList, integrations)
|
||||
it.run(File(parameters.outputFile), patchList)
|
||||
}
|
||||
|
||||
events.finished(null)
|
||||
|
@ -154,7 +154,7 @@ class PatcherWorker(
|
||||
}
|
||||
|
||||
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()) {
|
||||
|
@ -24,6 +24,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.haptics.HapticCheckbox
|
||||
import app.revanced.manager.util.transparentListItemColors
|
||||
|
||||
@Composable
|
||||
fun AutoUpdatesDialog(onSubmit: (Boolean, Boolean) -> Unit) {
|
||||
@ -77,5 +78,6 @@ private fun AutoUpdatesItem(
|
||||
leadingContent = { Icon(icon, null) },
|
||||
headlineContent = { Text(stringResource(headline)) },
|
||||
trailingContent = { HapticCheckbox(checked = checked, onCheckedChange = null) },
|
||||
modifier = Modifier.clickable { onCheckedChange(!checked) }
|
||||
modifier = Modifier.clickable { onCheckedChange(!checked) },
|
||||
colors = transparentListItemColors
|
||||
)
|
||||
|
@ -104,7 +104,7 @@ fun BundleInformationDialog(
|
||||
name = bundleName,
|
||||
remoteUrl = bundle.asRemoteOrNull?.endpoint,
|
||||
patchCount = patchCount,
|
||||
version = props?.versionInfo?.patches,
|
||||
version = props?.version,
|
||||
autoUpdate = props?.autoUpdate ?: false,
|
||||
onAutoUpdateChange = {
|
||||
composableScope.launch {
|
||||
|
@ -45,7 +45,7 @@ fun BundleItem(
|
||||
val state by bundle.state.collectAsStateWithLifecycle()
|
||||
|
||||
val version by remember(bundle) {
|
||||
bundle.propsFlow().map { props -> props?.versionInfo?.patches }
|
||||
bundle.propsFlow().map { props -> props?.version }
|
||||
}.collectAsStateWithLifecycle(null)
|
||||
val name by bundle.nameState
|
||||
|
||||
|
@ -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.HapticRadioButton
|
||||
import app.revanced.manager.ui.model.BundleType
|
||||
import app.revanced.manager.util.APK_MIMETYPE
|
||||
import app.revanced.manager.util.JAR_MIMETYPE
|
||||
import app.revanced.manager.util.BIN_MIMETYPE
|
||||
import app.revanced.manager.util.transparentListItemColors
|
||||
|
||||
@Composable
|
||||
fun ImportPatchBundleDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onRemoteSubmit: (String, Boolean) -> Unit,
|
||||
onLocalSubmit: (Uri, Uri?) -> Unit
|
||||
onLocalSubmit: (Uri) -> Unit
|
||||
) {
|
||||
var currentStep by rememberSaveable { mutableIntStateOf(0) }
|
||||
var bundleType by rememberSaveable { mutableStateOf(BundleType.Remote) }
|
||||
var patchBundle by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||
var integrations by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||
var remoteUrl by rememberSaveable { mutableStateOf("") }
|
||||
var autoUpdate by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
@ -45,16 +44,7 @@ fun ImportPatchBundleDialog(
|
||||
}
|
||||
|
||||
fun launchPatchActivity() {
|
||||
patchActivityLauncher.launch(JAR_MIMETYPE)
|
||||
}
|
||||
|
||||
val integrationsActivityLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
uri?.let { integrations = it }
|
||||
}
|
||||
|
||||
fun launchIntegrationsActivity() {
|
||||
integrationsActivityLauncher.launch(APK_MIMETYPE)
|
||||
patchActivityLauncher.launch(BIN_MIMETYPE)
|
||||
}
|
||||
|
||||
val steps = listOf<@Composable () -> Unit>(
|
||||
@ -67,11 +57,9 @@ fun ImportPatchBundleDialog(
|
||||
ImportBundleStep(
|
||||
bundleType,
|
||||
patchBundle,
|
||||
integrations,
|
||||
remoteUrl,
|
||||
autoUpdate,
|
||||
{ launchPatchActivity() },
|
||||
{ launchIntegrationsActivity() },
|
||||
{ remoteUrl = it },
|
||||
{ autoUpdate = it }
|
||||
)
|
||||
@ -99,13 +87,7 @@ fun ImportPatchBundleDialog(
|
||||
enabled = inputsAreValid,
|
||||
onClick = {
|
||||
when (bundleType) {
|
||||
BundleType.Local -> patchBundle?.let {
|
||||
onLocalSubmit(
|
||||
it,
|
||||
integrations
|
||||
)
|
||||
}
|
||||
|
||||
BundleType.Local -> patchBundle?.let(onLocalSubmit)
|
||||
BundleType.Remote -> onRemoteSubmit(remoteUrl, autoUpdate)
|
||||
}
|
||||
}
|
||||
@ -159,7 +141,8 @@ fun SelectBundleTypeStep(
|
||||
selected = bundleType == BundleType.Remote,
|
||||
onClick = null
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = transparentListItemColors
|
||||
)
|
||||
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
ListItem(
|
||||
@ -175,7 +158,8 @@ fun SelectBundleTypeStep(
|
||||
selected = bundleType == BundleType.Local,
|
||||
onClick = null
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = transparentListItemColors
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -186,11 +170,9 @@ fun SelectBundleTypeStep(
|
||||
fun ImportBundleStep(
|
||||
bundleType: BundleType,
|
||||
patchBundle: Uri?,
|
||||
integrations: Uri?,
|
||||
remoteUrl: String,
|
||||
autoUpdate: Boolean,
|
||||
launchPatchActivity: () -> Unit,
|
||||
launchIntegrationsActivity: () -> Unit,
|
||||
onRemoteUrlChange: (String) -> Unit,
|
||||
onAutoUpdateChange: (Boolean) -> Unit
|
||||
) {
|
||||
@ -210,19 +192,8 @@ fun ImportBundleStep(
|
||||
Icon(imageVector = Icons.Default.Topic, contentDescription = null)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.clickable { launchPatchActivity() }
|
||||
)
|
||||
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() }
|
||||
modifier = Modifier.clickable { launchPatchActivity() },
|
||||
colors = transparentListItemColors
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -256,6 +227,7 @@ fun ImportBundleStep(
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = transparentListItemColors
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.data.room.apps.installed.InstallType
|
||||
import app.revanced.manager.ui.component.haptics.HapticRadioButton
|
||||
import app.revanced.manager.util.transparentListItemColors
|
||||
|
||||
@Composable
|
||||
fun InstallPickerDialog(
|
||||
@ -41,7 +42,7 @@ fun InstallPickerDialog(
|
||||
title = { Text(stringResource(R.string.select_install_type)) },
|
||||
text = {
|
||||
Column {
|
||||
InstallType.values().forEach {
|
||||
InstallType.entries.forEach {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable { selectedInstallType = it },
|
||||
leadingContent = {
|
||||
@ -50,7 +51,8 @@ fun InstallPickerDialog(
|
||||
onClick = null
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(stringResource(it.stringResource)) }
|
||||
headlineContent = { Text(stringResource(it.stringResource)) },
|
||||
colors = transparentListItemColors
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ import app.revanced.manager.util.mutableStateSetOf
|
||||
import app.revanced.manager.util.saver.snapshotStateListSaver
|
||||
import app.revanced.manager.util.saver.snapshotStateSetSaver
|
||||
import app.revanced.manager.util.toast
|
||||
import app.revanced.manager.util.transparentListItemColors
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -58,6 +59,7 @@ import sh.calvin.reorderable.ReorderableItem
|
||||
import sh.calvin.reorderable.rememberReorderableLazyColumnState
|
||||
import java.io.Serializable
|
||||
import kotlin.random.Random
|
||||
import kotlin.reflect.typeOf
|
||||
import androidx.compose.ui.window.Dialog as ComposeDialog
|
||||
|
||||
private class OptionEditorScope<T : Any>(
|
||||
@ -96,17 +98,17 @@ private interface OptionEditor<T : Any> {
|
||||
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(
|
||||
"Boolean" to BooleanOptionEditor,
|
||||
"String" to StringOptionEditor,
|
||||
"Int" to IntOptionEditor,
|
||||
"Long" to LongOptionEditor,
|
||||
"Float" to FloatOptionEditor,
|
||||
"BooleanArray" to ListOptionEditor(BooleanOptionEditor),
|
||||
"StringArray" to ListOptionEditor(StringOptionEditor),
|
||||
"IntArray" to ListOptionEditor(IntOptionEditor),
|
||||
"LongArray" to ListOptionEditor(LongOptionEditor),
|
||||
"FloatArray" to ListOptionEditor(FloatOptionEditor),
|
||||
*BooleanOptionEditor.toMapEditorElements(),
|
||||
*StringOptionEditor.toMapEditorElements(),
|
||||
*IntOptionEditor.toMapEditorElements(),
|
||||
*LongOptionEditor.toMapEditorElements(),
|
||||
*FloatOptionEditor.toMapEditorElements()
|
||||
)
|
||||
|
||||
@Composable
|
||||
@ -145,7 +147,7 @@ fun <T : Any> OptionItem(option: Option<T>, value: T?, setValue: (T?) -> Unit) {
|
||||
val baseOptionEditor =
|
||||
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
|
||||
}
|
||||
|
||||
@ -405,7 +407,8 @@ private class PresetOptionEditor<T : Any>(private val innerEditor: OptionEditor<
|
||||
selected = selectedPreset == presetKey,
|
||||
onClick = { selectedPreset = presetKey }
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = transparentListItemColors
|
||||
)
|
||||
}
|
||||
|
||||
@ -430,7 +433,7 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
|
||||
option.key,
|
||||
option.description,
|
||||
option.required,
|
||||
option.type.removeSuffix("Array"),
|
||||
option.type.arguments.first().type!!,
|
||||
null,
|
||||
null
|
||||
) { true }
|
||||
|
@ -33,6 +33,7 @@ import app.revanced.manager.ui.component.SearchView
|
||||
import app.revanced.manager.ui.model.SelectedApp
|
||||
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel
|
||||
import app.revanced.manager.util.APK_MIMETYPE
|
||||
import app.revanced.manager.util.transparentListItemColors
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -110,9 +111,9 @@ fun AppSelectorScreen(
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = transparentListItemColors
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -79,9 +79,9 @@ fun DashboardScreen(
|
||||
if (showAddBundleDialog) {
|
||||
ImportPatchBundleDialog(
|
||||
onDismiss = { showAddBundleDialog = false },
|
||||
onLocalSubmit = { patches, integrations ->
|
||||
onLocalSubmit = { patches ->
|
||||
showAddBundleDialog = false
|
||||
vm.createLocalSource(patches, integrations)
|
||||
vm.createLocalSource(patches)
|
||||
},
|
||||
onRemoteSubmit = { url, autoUpdate ->
|
||||
showAddBundleDialog = false
|
||||
|
@ -46,6 +46,7 @@ import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW
|
||||
import app.revanced.manager.util.Options
|
||||
import app.revanced.manager.util.PatchSelection
|
||||
import app.revanced.manager.util.isScrollingUp
|
||||
import app.revanced.manager.util.transparentListItemColors
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@ -480,7 +481,8 @@ private fun ListHeader(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = transparentListItemColors
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ fun VersionSelectorScreen(
|
||||
viewModel.installedApp?.let { (packageInfo, installedApp) ->
|
||||
SelectedApp.Installed(
|
||||
packageName = viewModel.packageName,
|
||||
version = packageInfo.versionName
|
||||
version = packageInfo.versionName!!
|
||||
).let {
|
||||
item {
|
||||
SelectedAppItem(
|
||||
|
@ -102,12 +102,6 @@ fun AdvancedSettingsScreen(
|
||||
headline = R.string.process_runtime_memory_limit,
|
||||
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))
|
||||
BooleanItem(
|
||||
|
@ -69,7 +69,7 @@ class AppSelectorViewModel(
|
||||
pm.getPackageInfo(this)?.let { packageInfo ->
|
||||
SelectedApp.Local(
|
||||
packageName = packageInfo.packageName,
|
||||
version = packageInfo.versionName,
|
||||
version = packageInfo.versionName!!,
|
||||
file = this,
|
||||
temporary = true
|
||||
)
|
||||
|
@ -95,13 +95,10 @@ class DashboardViewModel(
|
||||
selectedSources.clear()
|
||||
}
|
||||
|
||||
fun createLocalSource(patchBundle: Uri, integrations: Uri?) =
|
||||
fun createLocalSource(patchBundle: Uri) =
|
||||
viewModelScope.launch {
|
||||
contentResolver.openInputStream(patchBundle)!!.use { patchesStream ->
|
||||
integrations?.let { contentResolver.openInputStream(it) }
|
||||
.use { integrationsStream ->
|
||||
patchBundleRepository.createLocal(patchesStream, integrationsStream)
|
||||
}
|
||||
patchBundleRepository.createLocal(patchesStream)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,8 +152,8 @@ class PatcherViewModel(
|
||||
)
|
||||
|
||||
val patcherSucceeded =
|
||||
workManager.getWorkInfoByIdLiveData(patcherWorkerId).map { workInfo: WorkInfo ->
|
||||
when (workInfo.state) {
|
||||
workManager.getWorkInfoByIdLiveData(patcherWorkerId).map { workInfo: WorkInfo? ->
|
||||
when (workInfo?.state) {
|
||||
WorkInfo.State.SUCCEEDED -> true
|
||||
WorkInfo.State.FAILED -> false
|
||||
else -> null
|
||||
@ -308,7 +308,7 @@ class PatcherViewModel(
|
||||
// Check for base APK, first check if the app is already installed
|
||||
if (existingPackageInfo == null) {
|
||||
// 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
|
||||
installerStatusDialogModel.packageInstallerStatus =
|
||||
PackageInstaller.STATUS_FAILURE_INVALID
|
||||
|
@ -1,14 +1,8 @@
|
||||
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 JAR_MIMETYPE = "application/java-archive"
|
||||
const val APK_MIMETYPE = "application/vnd.android.package-archive"
|
||||
const val JSON_MIMETYPE = "application/json"
|
||||
const val BIN_MIMETYPE = "application/octet-stream"
|
@ -106,7 +106,7 @@ class PM(
|
||||
val pkgInfo = app.packageManager.getPackageArchiveInfo(path, 0) ?: return null
|
||||
|
||||
// This is needed in order to load label and icon.
|
||||
pkgInfo.applicationInfo.apply {
|
||||
pkgInfo.applicationInfo!!.apply {
|
||||
sourceDir = path
|
||||
publicSourceDir = path
|
||||
}
|
||||
@ -114,7 +114,7 @@ class PM(
|
||||
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)
|
||||
|
||||
|
@ -13,6 +13,8 @@ import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.ScrollState
|
||||
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.ReadOnlyComposable
|
||||
import androidx.compose.runtime.State
|
||||
@ -241,3 +243,13 @@ fun <T, R> ((T) -> R).withHapticFeedback(constant: Int): (T) -> R {
|
||||
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 }
|
@ -2,7 +2,6 @@
|
||||
<string name="app_name">ReVanced Manager</string>
|
||||
<string name="patcher">Patcher</string>
|
||||
<string name="patches">Patches</string>
|
||||
<string name="integrations">Integrations</string>
|
||||
<string name="cli">CLI</string>
|
||||
<string name="manager">Manager</string>
|
||||
|
||||
@ -20,7 +19,6 @@
|
||||
<string name="import_bundle">Import patch bundle</string>
|
||||
<string name="bundle_patches">Bundle patches</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_not_set">Not selected</string>
|
||||
|
||||
@ -71,8 +69,6 @@
|
||||
<string name="dynamic_color_description">Adapt colors to the wallpaper</string>
|
||||
<string name="theme">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="patch_compat_check">Disable version compatibility check</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="loading_body">Loading…</string>
|
||||
<string name="downloading_patches">Downloading patch bundle…</string>
|
||||
<string name="downloading_integrations">Downloading Integrations…</string>
|
||||
|
||||
<string name="options">Options</string>
|
||||
<string name="ok">OK</string>
|
||||
@ -186,7 +181,6 @@
|
||||
<string name="download_apk">Download APK file</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_integrations_fail">Failed to update integrations: %s</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="bundles_selected">%s selected</string>
|
||||
|
@ -2,5 +2,8 @@ plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.devtools) 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
|
||||
}
|
||||
|
@ -1,31 +1,30 @@
|
||||
[versions]
|
||||
ktx = "1.13.1"
|
||||
material3 = "1.2.1"
|
||||
ui-tooling = "1.6.8"
|
||||
viewmodel-lifecycle = "2.8.3"
|
||||
ktx = "1.15.0"
|
||||
material3 = "1.3.1"
|
||||
ui-tooling = "1.7.5"
|
||||
viewmodel-lifecycle = "2.8.7"
|
||||
splash-screen = "1.0.1"
|
||||
compose-activity = "1.9.0"
|
||||
paging = "3.3.0"
|
||||
compose-activity = "1.9.3"
|
||||
preferences-datastore = "1.1.1"
|
||||
work-runtime = "2.9.0"
|
||||
compose-bom = "2024.06.00"
|
||||
work-runtime = "2.10.0"
|
||||
compose-bom = "2024.10.01"
|
||||
accompanist = "0.34.0"
|
||||
placeholder = "1.1.2"
|
||||
reorderable = "1.5.2"
|
||||
serialization = "1.6.3"
|
||||
collection = "0.3.7"
|
||||
serialization = "1.7.3"
|
||||
collection = "0.3.8"
|
||||
room-version = "2.6.1"
|
||||
revanced-patcher = "19.3.1"
|
||||
revanced-library = "2.2.1"
|
||||
revanced-patcher = "21.0.0"
|
||||
revanced-library = "3.0.2"
|
||||
koin-version = "3.5.3"
|
||||
koin-version-compose = "3.5.3"
|
||||
reimagined-navigation = "1.5.0"
|
||||
ktor = "2.3.9"
|
||||
markdown-renderer = "0.22.0"
|
||||
fading-edges = "1.0.4"
|
||||
android-gradle-plugin = "8.3.2"
|
||||
kotlin-gradle-plugin = "1.9.22"
|
||||
dev-tools-gradle-plugin = "1.9.22-1.0.17"
|
||||
kotlin = "2.0.21"
|
||||
android-gradle-plugin = "8.7.2"
|
||||
dev-tools-gradle-plugin = "2.0.21-1.0.27"
|
||||
about-libraries-gradle-plugin = "11.1.1"
|
||||
coil = "2.6.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" }
|
||||
splash-screen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splash-screen" }
|
||||
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" }
|
||||
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]
|
||||
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" }
|
||||
about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "about-libraries-gradle-plugin" }
|
||||
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,7 @@
|
||||
#Tue Nov 12 21:36:50 CET 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
Loading…
x
Reference in New Issue
Block a user