mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2024-11-10 01:01:56 +01:00
feat: allow bundles to use classes from other bundles
This commit is contained in:
parent
413083c58d
commit
8b9314078c
26 changed files with 393 additions and 268 deletions
|
@ -149,6 +149,7 @@ dependencies {
|
||||||
// ReVanced
|
// ReVanced
|
||||||
implementation(libs.revanced.patcher)
|
implementation(libs.revanced.patcher)
|
||||||
implementation(libs.revanced.library)
|
implementation(libs.revanced.library)
|
||||||
|
implementation(libs.revanced.multidexlib2)
|
||||||
|
|
||||||
// Native processes
|
// Native processes
|
||||||
implementation(libs.kotlin.process)
|
implementation(libs.kotlin.process)
|
||||||
|
|
|
@ -20,6 +20,6 @@ class LocalPatchBundle(name: String, id: Int, directory: File) : PatchBundleSour
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reload()
|
refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package app.revanced.manager.domain.bundles
|
package app.revanced.manager.domain.bundles
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import app.revanced.manager.patcher.patch.PatchBundle
|
import app.revanced.manager.patcher.patch.PatchBundle
|
||||||
import app.revanced.manager.util.tag
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
@ -18,7 +16,7 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File)
|
||||||
protected val patchesFile = directory.resolve("patches.jar")
|
protected val patchesFile = directory.resolve("patches.jar")
|
||||||
protected val integrationsFile = directory.resolve("integrations.apk")
|
protected val integrationsFile = directory.resolve("integrations.apk")
|
||||||
|
|
||||||
private val _state = MutableStateFlow(load())
|
private val _state = MutableStateFlow(getPatchBundle())
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,19 +34,16 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun load(): State {
|
private fun getPatchBundle() =
|
||||||
if (!hasInstalled()) return State.Missing
|
if (!hasInstalled()) State.Missing
|
||||||
|
else State.Available(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists)))
|
||||||
|
|
||||||
return try {
|
fun refresh() {
|
||||||
State.Loaded(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists)))
|
_state.value = getPatchBundle()
|
||||||
} catch (t: Throwable) {
|
|
||||||
Log.e(tag, "Failed to load patch bundle $name", t)
|
|
||||||
State.Failed(t)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reload() {
|
fun markAsFailed(e: Throwable) {
|
||||||
_state.value = load()
|
_state.value = State.Failed(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface State {
|
sealed interface State {
|
||||||
|
@ -56,7 +51,7 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File)
|
||||||
|
|
||||||
data object Missing : State
|
data object Missing : State
|
||||||
data class Failed(val throwable: Throwable) : State
|
data class Failed(val throwable: Throwable) : State
|
||||||
data class Loaded(val bundle: PatchBundle) : State {
|
data class Available(val bundle: PatchBundle) : State {
|
||||||
override fun patchBundleOrNull() = bundle
|
override fun patchBundleOrNull() = bundle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
|
||||||
}
|
}
|
||||||
|
|
||||||
saveVersion(patches.version, integrations.version)
|
saveVersion(patches.version, integrations.version)
|
||||||
reload()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadLatest() {
|
suspend fun downloadLatest() {
|
||||||
|
@ -76,7 +76,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
|
||||||
|
|
||||||
suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) {
|
suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) {
|
||||||
arrayOf(patchesFile, integrationsFile).forEach(File::delete)
|
arrayOf(patchesFile, integrationsFile).forEach(File::delete)
|
||||||
reload()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun propsFlow() = configRepository.getProps(uid)
|
fun propsFlow() = configRepository.getProps(uid)
|
||||||
|
|
|
@ -15,6 +15,8 @@ import app.revanced.manager.domain.bundles.RemotePatchBundle
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.patcher.patch.PatchInfo
|
import app.revanced.manager.patcher.patch.PatchInfo
|
||||||
|
import app.revanced.manager.patcher.patch.PatchBundleInfo
|
||||||
|
import app.revanced.manager.patcher.patch.PatchBundleLoader
|
||||||
import app.revanced.manager.util.flatMapLatestAndCombine
|
import app.revanced.manager.util.flatMapLatestAndCombine
|
||||||
import app.revanced.manager.util.tag
|
import app.revanced.manager.util.tag
|
||||||
import app.revanced.manager.util.uiSafe
|
import app.revanced.manager.util.uiSafe
|
||||||
|
@ -22,6 +24,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -51,7 +54,46 @@ class PatchBundleRepository(
|
||||||
it.state.map { state -> it.uid to state }
|
it.state.map { state -> it.uid to state }
|
||||||
}
|
}
|
||||||
|
|
||||||
val suggestedVersions = bundles.map {
|
val bundleInfoFlow = sources.flatMapLatestAndCombine(
|
||||||
|
transformer = { source ->
|
||||||
|
source.state.map {
|
||||||
|
source to it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
combiner = { states ->
|
||||||
|
val patchBundleLoader by lazy {
|
||||||
|
PatchBundleLoader(states.mapNotNull { (_, state) -> state.patchBundleOrNull() })
|
||||||
|
}
|
||||||
|
|
||||||
|
states.mapNotNull { (source, state) ->
|
||||||
|
val bundle = state.patchBundleOrNull() ?: return@mapNotNull null
|
||||||
|
|
||||||
|
try {
|
||||||
|
source.uid to PatchBundleInfo.Global(
|
||||||
|
source.name,
|
||||||
|
source.uid,
|
||||||
|
patchBundleLoader.loadMetadata(bundle)
|
||||||
|
)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.e(tag, "Failed to load patches from ${source.name}", t)
|
||||||
|
source.markAsFailed(t)
|
||||||
|
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
}
|
||||||
|
).flowOn(Dispatchers.Default)
|
||||||
|
|
||||||
|
fun scopedBundleInfoFlow(packageName: String, version: String) = bundleInfoFlow.map {
|
||||||
|
it.map { (_, bundle) ->
|
||||||
|
bundle.forPackage(
|
||||||
|
packageName,
|
||||||
|
version
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val suggestedVersions = bundleInfoFlow.map {
|
||||||
val allPatches =
|
val allPatches =
|
||||||
it.values.flatMap { bundle -> bundle.patches.map(PatchInfo::toPatcherPatch) }.toSet()
|
it.values.flatMap { bundle -> bundle.patches.map(PatchInfo::toPatcherPatch) }.toSet()
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,8 @@
|
||||||
package app.revanced.manager.patcher.patch
|
package app.revanced.manager.patcher.patch
|
||||||
|
|
||||||
import android.util.Log
|
import android.os.Parcelable
|
||||||
import app.revanced.manager.util.tag
|
import kotlinx.parcelize.Parcelize
|
||||||
import app.revanced.patcher.PatchBundleLoader
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class PatchBundle(val patchesJar: File, val integrations: File?) {
|
@Parcelize
|
||||||
private val loader = object : Iterable<Patch<*>> {
|
data class PatchBundle(val patchesJar: File, val integrations: File?) : Parcelable
|
||||||
private fun load(): Iterable<Patch<*>> {
|
|
||||||
patchesJar.setReadOnly()
|
|
||||||
return PatchBundleLoader.Dex(patchesJar, optimizedDexDirectory = null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun iterator(): Iterator<Patch<*>> = load().iterator()
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
Log.d(tag, "Loaded patch bundle: $patchesJar")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list containing the metadata of every patch inside this bundle.
|
|
||||||
*/
|
|
||||||
val patches = loader.map(::PatchInfo)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all patches compatible with the specified package.
|
|
||||||
*/
|
|
||||||
fun patchClasses(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 }) {
|
|
||||||
// Patch is not compatible with this package.
|
|
||||||
return@filter false
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package app.revanced.manager.patcher.patch
|
||||||
|
|
||||||
|
import app.revanced.manager.util.PatchSelection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base class for storing [PatchBundle] metadata.
|
||||||
|
*
|
||||||
|
* @param name The name of the bundle.
|
||||||
|
* @param uid The unique ID of the bundle.
|
||||||
|
* @param patches The patch list.
|
||||||
|
*/
|
||||||
|
sealed class PatchBundleInfo(val name: String, val uid: Int, val patches: List<PatchInfo>) {
|
||||||
|
/**
|
||||||
|
* Information about a bundle and all the patches it contains.
|
||||||
|
*
|
||||||
|
* @see [PatchBundleInfo]
|
||||||
|
*/
|
||||||
|
class Global(name: String, uid: Int, patches: List<PatchInfo>) :
|
||||||
|
PatchBundleInfo(name, uid, patches) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [PatchBundleInfo.Scoped] that only contains information about patches that are relevant for a specific [packageName].
|
||||||
|
*/
|
||||||
|
fun forPackage(packageName: String, version: String): Scoped {
|
||||||
|
val relevantPatches = patches.filter { it.compatibleWith(packageName) }
|
||||||
|
val supported = mutableListOf<PatchInfo>()
|
||||||
|
val unsupported = mutableListOf<PatchInfo>()
|
||||||
|
val universal = mutableListOf<PatchInfo>()
|
||||||
|
|
||||||
|
relevantPatches.forEach {
|
||||||
|
val targetList = when {
|
||||||
|
it.compatiblePackages == null -> universal
|
||||||
|
it.supportsVersion(
|
||||||
|
packageName,
|
||||||
|
version
|
||||||
|
) -> supported
|
||||||
|
|
||||||
|
else -> unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
targetList.add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scoped(name, uid, relevantPatches, supported, unsupported, universal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains information about a bundle that is relevant for a specific package name.
|
||||||
|
*
|
||||||
|
* @param supportedPatches Patches that are compatible with the specified package name and version.
|
||||||
|
* @param unsupportedPatches Patches that are compatible with the specified package name but not version.
|
||||||
|
* @param universalPatches Patches that are compatible with all packages.
|
||||||
|
* @see [PatchBundleInfo.Global.forPackage]
|
||||||
|
* @see [PatchBundleInfo]
|
||||||
|
*/
|
||||||
|
class Scoped(
|
||||||
|
name: String,
|
||||||
|
uid: Int,
|
||||||
|
patches: List<PatchInfo>,
|
||||||
|
val supportedPatches: List<PatchInfo>,
|
||||||
|
val unsupportedPatches: List<PatchInfo>,
|
||||||
|
val universalPatches: List<PatchInfo>
|
||||||
|
) : PatchBundleInfo(name, uid, patches) {
|
||||||
|
fun patchSequence(allowUnsupported: Boolean) = if (allowUnsupported) {
|
||||||
|
patches.asSequence()
|
||||||
|
} else {
|
||||||
|
sequence {
|
||||||
|
yieldAll(supportedPatches)
|
||||||
|
yieldAll(universalPatches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Extensions {
|
||||||
|
inline fun Iterable<Scoped>.toPatchSelection(
|
||||||
|
allowUnsupported: Boolean,
|
||||||
|
condition: (Int, PatchInfo) -> Boolean
|
||||||
|
): PatchSelection = this.associate { bundle ->
|
||||||
|
val patches =
|
||||||
|
bundle.patchSequence(allowUnsupported)
|
||||||
|
.mapNotNullTo(mutableSetOf()) { patch ->
|
||||||
|
patch.name.takeIf {
|
||||||
|
condition(
|
||||||
|
bundle.uid,
|
||||||
|
patch
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bundle.uid to patches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package app.revanced.manager.patcher.patch
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import dalvik.system.DelegateLastClassLoader
|
||||||
|
import dalvik.system.PathClassLoader
|
||||||
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
|
import lanchon.multidexlib2.MultiDexIO
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class PatchBundleLoader() : ClassLoader(Patch::class.java.classLoader) {
|
||||||
|
private val registry = mutableMapOf<PatchBundle, Entry>()
|
||||||
|
|
||||||
|
constructor(bundles: Iterable<PatchBundle>) : this() {
|
||||||
|
bundles.forEach(::register)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findClass(name: String?): Class<*> {
|
||||||
|
registry.values.find { entry -> name in entry.classes }?.let {
|
||||||
|
return it.classLoader.loadClass(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.findClass(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from: https://github.com/ReVanced/revanced-patcher/blob/f57e571a147d33eed189b533eee3aa62388fb354/src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt#L127-L130
|
||||||
|
private fun readClassNames(bundlePath: File): Set<String> = MultiDexIO.readDexFile(
|
||||||
|
true,
|
||||||
|
bundlePath,
|
||||||
|
BasicDexFileNamer(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
).classes.map { classDef ->
|
||||||
|
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
|
private fun createClassLoader(bundlePath: File) =
|
||||||
|
bundlePath.also(File::setReadOnly).absolutePath.let {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
|
// We need the delegate last policy for cross-bundle dependencies.
|
||||||
|
DelegateLastClassLoader(it, this)
|
||||||
|
} else {
|
||||||
|
PathClassLoader(it, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun register(bundle: PatchBundle) {
|
||||||
|
registry[bundle] =
|
||||||
|
Entry(readClassNames(bundle.patchesJar), createClassLoader(bundle.patchesJar))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadPatches(bundle: PatchBundle): List<Patch<*>> {
|
||||||
|
val entry = registry[bundle]
|
||||||
|
?: throw Exception("Attempted to load classes from a patch bundle that has not been registered.")
|
||||||
|
|
||||||
|
// Taken from: https://github.com/ReVanced/revanced-patcher/blob/f57e571a147d33eed189b533eee3aa62388fb354/src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt#L48-L54
|
||||||
|
return entry.classes
|
||||||
|
.map { entry.classLoader.loadClass(it) }
|
||||||
|
.filter { Patch::class.java.isAssignableFrom(it) }
|
||||||
|
.mapNotNull { it.getInstance() }
|
||||||
|
.filter { it.name != null }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadPatches(bundle: PatchBundle, packageName: String) =
|
||||||
|
loadPatches(bundle).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 }) {
|
||||||
|
// Patch is not compatible with this package.
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadMetadata(bundle: PatchBundle) = loadPatches(bundle).map(::PatchInfo)
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
fun Class<*>.getInstance(): Patch<*>? {
|
||||||
|
try {
|
||||||
|
// Get the Kotlin singleton instance.
|
||||||
|
return getField("INSTANCE").get(null) as Patch<*>
|
||||||
|
} catch (_: NoSuchFieldException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to instantiate the class.
|
||||||
|
return getDeclaredConstructor().newInstance() as Patch<*>
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Entry(val classes: Set<String>, val classLoader: ClassLoader)
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package app.revanced.manager.patcher.runtime
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.revanced.manager.patcher.Session
|
import app.revanced.manager.patcher.Session
|
||||||
import app.revanced.manager.patcher.logger.Logger
|
import app.revanced.manager.patcher.logger.Logger
|
||||||
|
import app.revanced.manager.patcher.patch.PatchBundleLoader
|
||||||
import app.revanced.manager.patcher.worker.ProgressEventHandler
|
import app.revanced.manager.patcher.worker.ProgressEventHandler
|
||||||
import app.revanced.manager.ui.model.State
|
import app.revanced.manager.ui.model.State
|
||||||
import app.revanced.manager.util.Options
|
import app.revanced.manager.util.Options
|
||||||
|
@ -26,8 +27,11 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) {
|
||||||
val bundles = bundles()
|
val bundles = bundles()
|
||||||
|
|
||||||
val selectedBundles = selectedPatches.keys
|
val selectedBundles = selectedPatches.keys
|
||||||
val allPatches = bundles.filterKeys { selectedBundles.contains(it) }
|
val allPatches = with(PatchBundleLoader(bundles.values)) {
|
||||||
.mapValues { (_, bundle) -> bundle.patchClasses(packageName) }
|
bundles
|
||||||
|
.filterKeys { selectedBundles.contains(it) }
|
||||||
|
.mapValues { (_, bundle) -> loadPatches(bundle, 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) }
|
||||||
|
|
|
@ -150,11 +150,8 @@ class ProcessRuntime(private val context: Context) : Runtime(context) {
|
||||||
outputFile = outputFile,
|
outputFile = outputFile,
|
||||||
enableMultithrededDexWriter = enableMultithreadedDexWriter(),
|
enableMultithrededDexWriter = enableMultithreadedDexWriter(),
|
||||||
configurations = selectedPatches.map { (id, patches) ->
|
configurations = selectedPatches.map { (id, patches) ->
|
||||||
val bundle = bundles[id]!!
|
|
||||||
|
|
||||||
PatchConfiguration(
|
PatchConfiguration(
|
||||||
bundle.patchesJar.absolutePath,
|
bundles[id]!!,
|
||||||
bundle.integrations?.absolutePath,
|
|
||||||
patches,
|
patches,
|
||||||
options[id].orEmpty()
|
options[id].orEmpty()
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package app.revanced.manager.patcher.runtime.process
|
package app.revanced.manager.patcher.runtime.process
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import app.revanced.manager.patcher.patch.PatchBundle
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import kotlinx.parcelize.RawValue
|
import kotlinx.parcelize.RawValue
|
||||||
|
|
||||||
|
@ -18,8 +19,7 @@ data class Parameters(
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class PatchConfiguration(
|
data class PatchConfiguration(
|
||||||
val bundlePath: String,
|
val bundle: PatchBundle,
|
||||||
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
|
|
@ -9,7 +9,7 @@ import app.revanced.manager.BuildConfig
|
||||||
import app.revanced.manager.patcher.Session
|
import app.revanced.manager.patcher.Session
|
||||||
import app.revanced.manager.patcher.logger.LogLevel
|
import app.revanced.manager.patcher.logger.LogLevel
|
||||||
import app.revanced.manager.patcher.logger.Logger
|
import app.revanced.manager.patcher.logger.Logger
|
||||||
import app.revanced.manager.patcher.patch.PatchBundle
|
import app.revanced.manager.patcher.patch.PatchBundleLoader
|
||||||
import app.revanced.manager.patcher.runtime.ProcessRuntime
|
import app.revanced.manager.patcher.runtime.ProcessRuntime
|
||||||
import app.revanced.manager.ui.model.State
|
import app.revanced.manager.ui.model.State
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
@ -55,12 +55,14 @@ 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 =
|
val integrations =
|
||||||
parameters.configurations.mapNotNull { it.integrationsPath?.let(::File) }
|
parameters.configurations.mapNotNull { it.bundle.integrations }
|
||||||
val patchList = parameters.configurations.flatMap { config ->
|
val patchBundleLoader = PatchBundleLoader(parameters.configurations.map { it.bundle })
|
||||||
val bundle = PatchBundle(File(config.bundlePath), null)
|
|
||||||
|
|
||||||
|
val patchList = parameters.configurations.flatMap { config ->
|
||||||
val patches =
|
val patches =
|
||||||
bundle.patchClasses(parameters.packageName).filter { it.name in config.patches }
|
patchBundleLoader
|
||||||
|
.loadPatches(config.bundle, parameters.packageName)
|
||||||
|
.filter { it.name in config.patches }
|
||||||
.associateBy { it.name }
|
.associateBy { it.name }
|
||||||
|
|
||||||
config.options.forEach { (patchName, opts) ->
|
config.options.forEach { (patchName, opts) ->
|
||||||
|
|
|
@ -32,27 +32,25 @@ import kotlinx.coroutines.launch
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BundleInformationDialog(
|
fun BundleInformationDialog(
|
||||||
|
bundle: PatchBundleSource,
|
||||||
|
patchCount: Int,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onDeleteRequest: () -> Unit,
|
onDeleteRequest: () -> Unit,
|
||||||
bundle: PatchBundleSource,
|
|
||||||
onRefreshButton: () -> Unit,
|
onRefreshButton: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val composableScope = rememberCoroutineScope()
|
val composableScope = rememberCoroutineScope()
|
||||||
var viewCurrentBundlePatches by remember { mutableStateOf(false) }
|
var viewCurrentBundlePatches by remember { mutableStateOf(false) }
|
||||||
val isLocal = bundle is LocalPatchBundle
|
val isLocal = bundle is LocalPatchBundle
|
||||||
val patchCount by remember(bundle) {
|
|
||||||
bundle.state.map { it.patchBundleOrNull()?.patches?.size ?: 0 }
|
|
||||||
}.collectAsStateWithLifecycle(0)
|
|
||||||
val props by remember(bundle) {
|
val props by remember(bundle) {
|
||||||
bundle.propsOrNullFlow()
|
bundle.propsOrNullFlow()
|
||||||
}.collectAsStateWithLifecycle(null)
|
}.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
if (viewCurrentBundlePatches) {
|
if (viewCurrentBundlePatches) {
|
||||||
BundlePatchesDialog(
|
BundlePatchesDialog(
|
||||||
|
bundle = bundle,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
viewCurrentBundlePatches = false
|
viewCurrentBundlePatches = false
|
||||||
},
|
}
|
||||||
bundle = bundle,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +108,7 @@ fun BundleInformationDialog(
|
||||||
},
|
},
|
||||||
onPatchesClick = {
|
onPatchesClick = {
|
||||||
viewCurrentBundlePatches = true
|
viewCurrentBundlePatches = true
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,12 +34,13 @@ import kotlinx.coroutines.flow.map
|
||||||
@Composable
|
@Composable
|
||||||
fun BundleItem(
|
fun BundleItem(
|
||||||
bundle: PatchBundleSource,
|
bundle: PatchBundleSource,
|
||||||
onDelete: () -> Unit,
|
patchCount: Int,
|
||||||
onUpdate: () -> Unit,
|
|
||||||
selectable: Boolean,
|
selectable: Boolean,
|
||||||
onSelect: () -> Unit,
|
|
||||||
isBundleSelected: Boolean,
|
isBundleSelected: Boolean,
|
||||||
toggleSelection: (Boolean) -> Unit,
|
toggleSelection: (Boolean) -> Unit,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
onUpdate: () -> Unit,
|
||||||
|
onSelect: () -> Unit,
|
||||||
) {
|
) {
|
||||||
var viewBundleDialogPage by rememberSaveable { mutableStateOf(false) }
|
var viewBundleDialogPage by rememberSaveable { mutableStateOf(false) }
|
||||||
val state by bundle.state.collectAsStateWithLifecycle()
|
val state by bundle.state.collectAsStateWithLifecycle()
|
||||||
|
@ -50,12 +51,13 @@ fun BundleItem(
|
||||||
|
|
||||||
if (viewBundleDialogPage) {
|
if (viewBundleDialogPage) {
|
||||||
BundleInformationDialog(
|
BundleInformationDialog(
|
||||||
|
bundle = bundle,
|
||||||
|
patchCount = patchCount,
|
||||||
onDismissRequest = { viewBundleDialogPage = false },
|
onDismissRequest = { viewBundleDialogPage = false },
|
||||||
onDeleteRequest = {
|
onDeleteRequest = {
|
||||||
viewBundleDialogPage = false
|
viewBundleDialogPage = false
|
||||||
onDelete()
|
onDelete()
|
||||||
},
|
},
|
||||||
bundle = bundle,
|
|
||||||
onRefreshButton = onUpdate,
|
onRefreshButton = onUpdate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -79,9 +81,7 @@ fun BundleItem(
|
||||||
|
|
||||||
headlineContent = { Text(text = bundle.name) },
|
headlineContent = { Text(text = bundle.name) },
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
state.patchBundleOrNull()?.patches?.size?.let { patchCount ->
|
|
||||||
Text(text = pluralStringResource(R.plurals.patch_count, patchCount, patchCount))
|
Text(text = pluralStringResource(R.plurals.patch_count, patchCount, patchCount))
|
||||||
}
|
|
||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Row {
|
Row {
|
||||||
|
@ -89,7 +89,7 @@ fun BundleItem(
|
||||||
when (state) {
|
when (state) {
|
||||||
is PatchBundleSource.State.Failed -> Icons.Outlined.ErrorOutline to R.string.bundle_error
|
is PatchBundleSource.State.Failed -> Icons.Outlined.ErrorOutline to R.string.bundle_error
|
||||||
is PatchBundleSource.State.Missing -> Icons.Outlined.Warning to R.string.bundle_missing
|
is PatchBundleSource.State.Missing -> Icons.Outlined.Warning to R.string.bundle_missing
|
||||||
is PatchBundleSource.State.Loaded -> null
|
is PatchBundleSource.State.Available -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,17 +26,23 @@ import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||||
|
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||||
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
||||||
import app.revanced.manager.ui.component.NotificationCard
|
import app.revanced.manager.ui.component.NotificationCard
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun BundlePatchesDialog(
|
fun BundlePatchesDialog(
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
bundle: PatchBundleSource,
|
bundle: PatchBundleSource,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
var informationCardVisible by remember { mutableStateOf(true) }
|
var informationCardVisible by remember { mutableStateOf(true) }
|
||||||
val state by bundle.state.collectAsStateWithLifecycle()
|
val patchBundleRepository: PatchBundleRepository = koinInject()
|
||||||
|
val patches by remember(bundle.uid) {
|
||||||
|
patchBundleRepository.bundleInfoFlow.mapNotNull { it[bundle.uid]?.patches }
|
||||||
|
}.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
@ -75,9 +81,9 @@ fun BundlePatchesDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.patchBundleOrNull()?.let { bundle ->
|
items(patches.size) { index ->
|
||||||
items(bundle.patches.size) { bundleIndex ->
|
val patch = patches[index]
|
||||||
val patch = bundle.patches[bundleIndex]
|
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(
|
Text(
|
||||||
|
@ -101,5 +107,4 @@ fun BundlePatchesDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
package app.revanced.manager.ui.model
|
|
||||||
|
|
||||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
|
||||||
import app.revanced.manager.patcher.patch.PatchInfo
|
|
||||||
import app.revanced.manager.util.PatchSelection
|
|
||||||
import app.revanced.manager.util.flatMapLatestAndCombine
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A data class that contains patch bundle metadata for use by UI code.
|
|
||||||
*/
|
|
||||||
data class BundleInfo(
|
|
||||||
val name: String,
|
|
||||||
val uid: Int,
|
|
||||||
val supported: List<PatchInfo>,
|
|
||||||
val unsupported: List<PatchInfo>,
|
|
||||||
val universal: List<PatchInfo>
|
|
||||||
) {
|
|
||||||
val all = sequence {
|
|
||||||
yieldAll(supported)
|
|
||||||
yieldAll(unsupported)
|
|
||||||
yieldAll(universal)
|
|
||||||
}
|
|
||||||
|
|
||||||
val patchCount get() = supported.size + unsupported.size + universal.size
|
|
||||||
|
|
||||||
fun patchSequence(allowUnsupported: Boolean) = if (allowUnsupported) {
|
|
||||||
all
|
|
||||||
} else {
|
|
||||||
sequence {
|
|
||||||
yieldAll(supported)
|
|
||||||
yieldAll(universal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object Extensions {
|
|
||||||
inline fun Iterable<BundleInfo>.toPatchSelection(allowUnsupported: Boolean, condition: (Int, PatchInfo) -> Boolean): PatchSelection = this.associate { bundle ->
|
|
||||||
val patches =
|
|
||||||
bundle.patchSequence(allowUnsupported)
|
|
||||||
.mapNotNullTo(mutableSetOf()) { patch ->
|
|
||||||
patch.name.takeIf {
|
|
||||||
condition(
|
|
||||||
bundle.uid,
|
|
||||||
patch
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bundle.uid to patches
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PatchBundleRepository.bundleInfoFlow(packageName: String, version: String) =
|
|
||||||
sources.flatMapLatestAndCombine(
|
|
||||||
combiner = { it.filterNotNull() }
|
|
||||||
) { source ->
|
|
||||||
// Regenerate bundle information whenever this source updates.
|
|
||||||
source.state.map { state ->
|
|
||||||
val bundle = state.patchBundleOrNull() ?: return@map null
|
|
||||||
|
|
||||||
val supported = mutableListOf<PatchInfo>()
|
|
||||||
val unsupported = mutableListOf<PatchInfo>()
|
|
||||||
val universal = mutableListOf<PatchInfo>()
|
|
||||||
|
|
||||||
bundle.patches.filter { it.compatibleWith(packageName) }.forEach {
|
|
||||||
val targetList = when {
|
|
||||||
it.compatiblePackages == null -> universal
|
|
||||||
it.supportsVersion(
|
|
||||||
packageName,
|
|
||||||
version
|
|
||||||
) -> supported
|
|
||||||
|
|
||||||
else -> unsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
targetList.add(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
BundleInfo(source.name, source.uid, supported, unsupported, universal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class BundleType {
|
|
||||||
Local,
|
|
||||||
Remote
|
|
||||||
}
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package app.revanced.manager.ui.model
|
||||||
|
|
||||||
|
enum class BundleType {
|
||||||
|
Local,
|
||||||
|
Remote
|
||||||
|
}
|
|
@ -79,7 +79,7 @@ fun DashboardScreen(
|
||||||
onAppClick: (InstalledApp) -> Unit
|
onAppClick: (InstalledApp) -> Unit
|
||||||
) {
|
) {
|
||||||
val bundlesSelectable by remember { derivedStateOf { vm.selectedSources.size > 0 } }
|
val bundlesSelectable by remember { derivedStateOf { vm.selectedSources.size > 0 } }
|
||||||
val availablePatches by vm.availablePatches.collectAsStateWithLifecycle(0)
|
val availablePatches by vm.availablePatchesCountFlow.collectAsStateWithLifecycle(0)
|
||||||
val androidContext = LocalContext.current
|
val androidContext = LocalContext.current
|
||||||
val composableScope = rememberCoroutineScope()
|
val composableScope = rememberCoroutineScope()
|
||||||
val pagerState = rememberPagerState(
|
val pagerState = rememberPagerState(
|
||||||
|
@ -269,6 +269,9 @@ fun DashboardScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
val sources by vm.sources.collectAsStateWithLifecycle(initialValue = emptyList())
|
val sources by vm.sources.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||||
|
val patchCounts by vm.bundlePatchCountsFlow.collectAsStateWithLifecycle(
|
||||||
|
initialValue = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
@ -276,23 +279,24 @@ fun DashboardScreen(
|
||||||
sources.forEach {
|
sources.forEach {
|
||||||
BundleItem(
|
BundleItem(
|
||||||
bundle = it,
|
bundle = it,
|
||||||
onDelete = {
|
patchCount = patchCounts[it.uid] ?: 0,
|
||||||
vm.delete(it)
|
isBundleSelected = vm.selectedSources.contains(it),
|
||||||
},
|
|
||||||
onUpdate = {
|
|
||||||
vm.update(it)
|
|
||||||
},
|
|
||||||
selectable = bundlesSelectable,
|
selectable = bundlesSelectable,
|
||||||
onSelect = {
|
onSelect = {
|
||||||
vm.selectedSources.add(it)
|
vm.selectedSources.add(it)
|
||||||
},
|
},
|
||||||
isBundleSelected = vm.selectedSources.contains(it),
|
|
||||||
toggleSelection = { bundleIsNotSelected ->
|
toggleSelection = { bundleIsNotSelected ->
|
||||||
if (bundleIsNotSelected) {
|
if (bundleIsNotSelected) {
|
||||||
vm.selectedSources.add(it)
|
vm.selectedSources.add(it)
|
||||||
} else {
|
} else {
|
||||||
vm.selectedSources.remove(it)
|
vm.selectedSources.remove(it)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onDelete = {
|
||||||
|
vm.delete(it)
|
||||||
|
},
|
||||||
|
onUpdate = {
|
||||||
|
vm.update(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,13 +224,13 @@ fun PatchesSelectorScreen(
|
||||||
|
|
||||||
patchList(
|
patchList(
|
||||||
uid = bundle.uid,
|
uid = bundle.uid,
|
||||||
patches = bundle.supported.searched(),
|
patches = bundle.supportedPatches.searched(),
|
||||||
filterFlag = SHOW_SUPPORTED,
|
filterFlag = SHOW_SUPPORTED,
|
||||||
supported = true
|
supported = true
|
||||||
)
|
)
|
||||||
patchList(
|
patchList(
|
||||||
uid = bundle.uid,
|
uid = bundle.uid,
|
||||||
patches = bundle.universal.searched(),
|
patches = bundle.universalPatches.searched(),
|
||||||
filterFlag = SHOW_UNIVERSAL,
|
filterFlag = SHOW_UNIVERSAL,
|
||||||
supported = true
|
supported = true
|
||||||
) {
|
) {
|
||||||
|
@ -242,13 +242,13 @@ fun PatchesSelectorScreen(
|
||||||
if (!vm.allowIncompatiblePatches) return@LazyColumnWithScrollbar
|
if (!vm.allowIncompatiblePatches) return@LazyColumnWithScrollbar
|
||||||
patchList(
|
patchList(
|
||||||
uid = bundle.uid,
|
uid = bundle.uid,
|
||||||
patches = bundle.unsupported.searched(),
|
patches = bundle.unsupportedPatches.searched(),
|
||||||
filterFlag = SHOW_UNSUPPORTED,
|
filterFlag = SHOW_UNSUPPORTED,
|
||||||
supported = true
|
supported = true
|
||||||
) {
|
) {
|
||||||
ListHeader(
|
ListHeader(
|
||||||
title = stringResource(R.string.unsupported_patches),
|
title = stringResource(R.string.unsupported_patches),
|
||||||
onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) }
|
onHelpClick = { vm.openUnsupportedDialog(bundle.unsupportedPatches) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,13 +332,13 @@ fun PatchesSelectorScreen(
|
||||||
) {
|
) {
|
||||||
patchList(
|
patchList(
|
||||||
uid = bundle.uid,
|
uid = bundle.uid,
|
||||||
patches = bundle.supported,
|
patches = bundle.supportedPatches,
|
||||||
filterFlag = SHOW_SUPPORTED,
|
filterFlag = SHOW_SUPPORTED,
|
||||||
supported = true
|
supported = true
|
||||||
)
|
)
|
||||||
patchList(
|
patchList(
|
||||||
uid = bundle.uid,
|
uid = bundle.uid,
|
||||||
patches = bundle.universal,
|
patches = bundle.universalPatches,
|
||||||
filterFlag = SHOW_UNIVERSAL,
|
filterFlag = SHOW_UNIVERSAL,
|
||||||
supported = true
|
supported = true
|
||||||
) {
|
) {
|
||||||
|
@ -348,13 +348,13 @@ fun PatchesSelectorScreen(
|
||||||
}
|
}
|
||||||
patchList(
|
patchList(
|
||||||
uid = bundle.uid,
|
uid = bundle.uid,
|
||||||
patches = bundle.unsupported,
|
patches = bundle.unsupportedPatches,
|
||||||
filterFlag = SHOW_UNSUPPORTED,
|
filterFlag = SHOW_UNSUPPORTED,
|
||||||
supported = vm.allowIncompatiblePatches
|
supported = vm.allowIncompatiblePatches
|
||||||
) {
|
) {
|
||||||
ListHeader(
|
ListHeader(
|
||||||
title = stringResource(R.string.unsupported_patches),
|
title = stringResource(R.string.unsupported_patches),
|
||||||
onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) }
|
onHelpClick = { vm.openUnsupportedDialog(bundle.unsupportedPatches) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ import app.revanced.manager.R
|
||||||
import app.revanced.manager.ui.component.AppInfo
|
import app.revanced.manager.ui.component.AppInfo
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.destination.SelectedAppInfoDestination
|
import app.revanced.manager.ui.destination.SelectedAppInfoDestination
|
||||||
import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow
|
|
||||||
import app.revanced.manager.ui.model.SelectedApp
|
import app.revanced.manager.ui.model.SelectedApp
|
||||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
||||||
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
|
import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
|
||||||
|
@ -53,7 +52,7 @@ fun SelectedAppInfoScreen(
|
||||||
val packageName = vm.selectedApp.packageName
|
val packageName = vm.selectedApp.packageName
|
||||||
val version = vm.selectedApp.version
|
val version = vm.selectedApp.version
|
||||||
val bundles by remember(packageName, version) {
|
val bundles by remember(packageName, version) {
|
||||||
vm.bundlesRepo.bundleInfoFlow(packageName, version)
|
vm.bundlesRepo.scopedBundleInfoFlow(packageName, version)
|
||||||
}.collectAsStateWithLifecycle(initialValue = emptyList())
|
}.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||||
|
|
||||||
val allowIncompatiblePatches by vm.prefs.disablePatchVersionCompatCheck.getAsState()
|
val allowIncompatiblePatches by vm.prefs.disablePatchVersionCompatCheck.getAsState()
|
||||||
|
@ -69,7 +68,7 @@ fun SelectedAppInfoScreen(
|
||||||
}
|
}
|
||||||
val availablePatchCount by remember {
|
val availablePatchCount by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
bundles.sumOf { it.patchCount }
|
bundles.sumOf { it.patches.size }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,13 +30,12 @@ class DashboardViewModel(
|
||||||
private val networkInfo: NetworkInfo,
|
private val networkInfo: NetworkInfo,
|
||||||
val prefs: PreferencesManager
|
val prefs: PreferencesManager
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val availablePatches =
|
val bundlePatchCountsFlow = patchBundleRepository.bundleInfoFlow.map { it.mapValues { (_, bundle) -> bundle.patches.size } }
|
||||||
patchBundleRepository.bundles.map { it.values.sumOf { bundle -> bundle.patches.size } }
|
val availablePatchesCountFlow = bundlePatchCountsFlow.map { it.values.sum() }
|
||||||
private val contentResolver: ContentResolver = app.contentResolver
|
private val contentResolver: ContentResolver = app.contentResolver
|
||||||
val sources = patchBundleRepository.sources
|
val sources = patchBundleRepository.sources
|
||||||
val selectedSources = mutableStateListOf<PatchBundleSource>()
|
val selectedSources = mutableStateListOf<PatchBundleSource>()
|
||||||
|
|
||||||
|
|
||||||
var updatedManagerVersion: String? by mutableStateOf(null)
|
var updatedManagerVersion: String? by mutableStateOf(null)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
@ -76,10 +75,10 @@ class DashboardViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun cancelSourceSelection() {
|
fun cancelSourceSelection() {
|
||||||
selectedSources.clear()
|
selectedSources.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createLocalSource(name: String, patchBundle: Uri, integrations: Uri?) =
|
fun createLocalSource(name: String, patchBundle: Uri, integrations: Uri?) =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
contentResolver.openInputStream(patchBundle)!!.use { patchesStream ->
|
contentResolver.openInputStream(patchBundle)!!.use { patchesStream ->
|
||||||
|
|
|
@ -18,10 +18,9 @@ import androidx.lifecycle.viewmodel.compose.saveable
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||||
|
import app.revanced.manager.patcher.patch.PatchBundleInfo
|
||||||
|
import app.revanced.manager.patcher.patch.PatchBundleInfo.Extensions.toPatchSelection
|
||||||
import app.revanced.manager.patcher.patch.PatchInfo
|
import app.revanced.manager.patcher.patch.PatchInfo
|
||||||
import app.revanced.manager.ui.model.BundleInfo
|
|
||||||
import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow
|
|
||||||
import app.revanced.manager.ui.model.BundleInfo.Extensions.toPatchSelection
|
|
||||||
import app.revanced.manager.ui.model.SelectedApp
|
import app.revanced.manager.ui.model.SelectedApp
|
||||||
import app.revanced.manager.util.Options
|
import app.revanced.manager.util.Options
|
||||||
import app.revanced.manager.util.PatchSelection
|
import app.revanced.manager.util.PatchSelection
|
||||||
|
@ -55,7 +54,7 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
||||||
val allowIncompatiblePatches =
|
val allowIncompatiblePatches =
|
||||||
get<PreferencesManager>().disablePatchVersionCompatCheck.getBlocking()
|
get<PreferencesManager>().disablePatchVersionCompatCheck.getBlocking()
|
||||||
val bundlesFlow =
|
val bundlesFlow =
|
||||||
get<PatchBundleRepository>().bundleInfoFlow(packageName, input.app.version)
|
get<PatchBundleRepository>().scopedBundleInfoFlow(packageName, input.app.version)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
@ -64,11 +63,11 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BundleInfo.hasDefaultPatches() =
|
fun PatchBundleInfo.Scoped.hasDefaultPatches() =
|
||||||
patchSequence(allowIncompatiblePatches).any { it.include }
|
patchSequence(allowIncompatiblePatches).any { it.include }
|
||||||
|
|
||||||
// Don't show the warning if there are no default patches.
|
// Don't show the warning if there are no default patches.
|
||||||
selectionWarningEnabled = bundlesFlow.first().any(BundleInfo::hasDefaultPatches)
|
selectionWarningEnabled = bundlesFlow.first().any(PatchBundleInfo.Scoped::hasDefaultPatches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +106,7 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
||||||
return generatedSelection.toPersistentPatchSelection()
|
return generatedSelection.toPersistentPatchSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectionIsValid(bundles: List<BundleInfo>) = bundles.any { bundle ->
|
fun selectionIsValid(bundles: List<PatchBundleInfo.Scoped>) = bundles.any { bundle ->
|
||||||
bundle.patchSequence(allowIncompatiblePatches).any { patch ->
|
bundle.patchSequence(allowIncompatiblePatches).any { patch ->
|
||||||
isSelected(bundle.uid, patch)
|
isSelected(bundle.uid, patch)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,8 @@ import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||||
import app.revanced.manager.domain.repository.PatchOptionsRepository
|
import app.revanced.manager.domain.repository.PatchOptionsRepository
|
||||||
import app.revanced.manager.domain.repository.PatchSelectionRepository
|
import app.revanced.manager.domain.repository.PatchSelectionRepository
|
||||||
import app.revanced.manager.ui.model.BundleInfo
|
import app.revanced.manager.patcher.patch.PatchBundleInfo
|
||||||
import app.revanced.manager.ui.model.BundleInfo.Extensions.toPatchSelection
|
import app.revanced.manager.patcher.patch.PatchBundleInfo.Extensions.toPatchSelection
|
||||||
import app.revanced.manager.ui.model.SelectedApp
|
import app.revanced.manager.ui.model.SelectedApp
|
||||||
import app.revanced.manager.util.Options
|
import app.revanced.manager.util.Options
|
||||||
import app.revanced.manager.util.PM
|
import app.revanced.manager.util.PM
|
||||||
|
@ -101,13 +101,13 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
|
||||||
selectedAppInfo = info
|
selectedAppInfo = info
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOptionsFiltered(bundles: List<BundleInfo>) = options.filtered(bundles)
|
fun getOptionsFiltered(bundles: List<PatchBundleInfo.Scoped>) = options.filtered(bundles)
|
||||||
|
|
||||||
fun getPatches(bundles: List<BundleInfo>, allowUnsupported: Boolean) =
|
fun getPatches(bundles: List<PatchBundleInfo.Scoped>, allowUnsupported: Boolean) =
|
||||||
selectionState.patches(bundles, allowUnsupported)
|
selectionState.patches(bundles, allowUnsupported)
|
||||||
|
|
||||||
fun getCustomPatches(
|
fun getCustomPatches(
|
||||||
bundles: List<BundleInfo>,
|
bundles: List<PatchBundleInfo.Scoped>,
|
||||||
allowUnsupported: Boolean
|
allowUnsupported: Boolean
|
||||||
): PatchSelection? =
|
): PatchSelection? =
|
||||||
(selectionState as? SelectionState.Customized)?.patches(bundles, allowUnsupported)
|
(selectionState as? SelectionState.Customized)?.patches(bundles, allowUnsupported)
|
||||||
|
@ -115,7 +115,7 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
|
||||||
fun updateConfiguration(
|
fun updateConfiguration(
|
||||||
selection: PatchSelection?,
|
selection: PatchSelection?,
|
||||||
options: Options,
|
options: Options,
|
||||||
bundles: List<BundleInfo>
|
bundles: List<PatchBundleInfo.Scoped>
|
||||||
) {
|
) {
|
||||||
selectionState = selection?.let(SelectionState::Customized) ?: SelectionState.Default
|
selectionState = selection?.let(SelectionState::Customized) ?: SelectionState.Default
|
||||||
|
|
||||||
|
@ -142,11 +142,11 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
|
||||||
/**
|
/**
|
||||||
* Returns a copy with all nonexistent options removed.
|
* Returns a copy with all nonexistent options removed.
|
||||||
*/
|
*/
|
||||||
private fun Options.filtered(bundles: List<BundleInfo>): Options = buildMap options@{
|
private fun Options.filtered(bundles: List<PatchBundleInfo.Scoped>): Options = buildMap options@{
|
||||||
bundles.forEach bundles@{ bundle ->
|
bundles.forEach bundles@{ bundle ->
|
||||||
val bundleOptions = this@filtered[bundle.uid] ?: return@bundles
|
val bundleOptions = this@filtered[bundle.uid] ?: return@bundles
|
||||||
|
|
||||||
val patches = bundle.all.associateBy { it.name }
|
val patches = bundle.patches.associateBy { it.name }
|
||||||
|
|
||||||
this@options[bundle.uid] = buildMap bundleOptions@{
|
this@options[bundle.uid] = buildMap bundleOptions@{
|
||||||
bundleOptions.forEach patch@{ (patchName, values) ->
|
bundleOptions.forEach patch@{ (patchName, values) ->
|
||||||
|
@ -165,11 +165,11 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed interface SelectionState : Parcelable {
|
private sealed interface SelectionState : Parcelable {
|
||||||
fun patches(bundles: List<BundleInfo>, allowUnsupported: Boolean): PatchSelection
|
fun patches(bundles: List<PatchBundleInfo.Scoped>, allowUnsupported: Boolean): PatchSelection
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Customized(val patchSelection: PatchSelection) : SelectionState {
|
data class Customized(val patchSelection: PatchSelection) : SelectionState {
|
||||||
override fun patches(bundles: List<BundleInfo>, allowUnsupported: Boolean) =
|
override fun patches(bundles: List<PatchBundleInfo.Scoped>, allowUnsupported: Boolean) =
|
||||||
bundles.toPatchSelection(
|
bundles.toPatchSelection(
|
||||||
allowUnsupported
|
allowUnsupported
|
||||||
) { uid, patch ->
|
) { uid, patch ->
|
||||||
|
@ -179,7 +179,7 @@ private sealed interface SelectionState : Parcelable {
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object Default : SelectionState {
|
data object Default : SelectionState {
|
||||||
override fun patches(bundles: List<BundleInfo>, allowUnsupported: Boolean) =
|
override fun patches(bundles: List<PatchBundleInfo.Scoped>, allowUnsupported: Boolean) =
|
||||||
bundles.toPatchSelection(allowUnsupported) { _, patch -> patch.include }
|
bundles.toPatchSelection(allowUnsupported) { _, patch -> patch.include }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,8 @@ class VersionSelectorViewModel(
|
||||||
patchBundleRepository.suggestedVersions.first()[packageName]
|
patchBundleRepository.suggestedVersions.first()[packageName]
|
||||||
}
|
}
|
||||||
|
|
||||||
val supportedVersions = patchBundleRepository.bundles.map supportedVersions@{ bundles ->
|
val supportedVersions =
|
||||||
|
patchBundleRepository.bundleInfoFlow.map supportedVersions@{ bundles ->
|
||||||
requiredVersionAsync.await()?.let { version ->
|
requiredVersionAsync.await()?.let { version ->
|
||||||
// It is mandatory to use the suggested version if the safeguard is enabled.
|
// It is mandatory to use the suggested version if the safeguard is enabled.
|
||||||
return@supportedVersions mapOf(
|
return@supportedVersions mapOf(
|
||||||
|
|
|
@ -43,10 +43,10 @@ class PM(
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
val appList = patchBundleRepository.bundles.map { bundles ->
|
val appList = patchBundleRepository.bundleInfoFlow.map { bundles ->
|
||||||
val compatibleApps = scope.async {
|
val compatibleApps = scope.async {
|
||||||
val compatiblePackages = bundles.values
|
val compatiblePackages = bundles
|
||||||
.flatMap { it.patches }
|
.flatMap { (_, bundle) -> bundle.patches }
|
||||||
.flatMap { it.compatiblePackages.orEmpty() }
|
.flatMap { it.compatiblePackages.orEmpty() }
|
||||||
.groupingBy { it.packageName }
|
.groupingBy { it.packageName }
|
||||||
.eachCount()
|
.eachCount()
|
||||||
|
@ -80,7 +80,7 @@ class PM(
|
||||||
(compatibleApps.await() + installedApps.await())
|
(compatibleApps.await() + installedApps.await())
|
||||||
.distinctBy { it.packageName }
|
.distinctBy { it.packageName }
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
compareByDescending<AppInfo>{
|
compareByDescending<AppInfo> {
|
||||||
it.packageInfo != null && (it.patches ?: 0) > 0
|
it.packageInfo != null && (it.patches ?: 0) > 0
|
||||||
}.thenByDescending {
|
}.thenByDescending {
|
||||||
it.patches
|
it.patches
|
||||||
|
|
|
@ -16,6 +16,7 @@ collection = "0.3.7"
|
||||||
room-version = "2.6.1"
|
room-version = "2.6.1"
|
||||||
revanced-patcher = "19.3.1"
|
revanced-patcher = "19.3.1"
|
||||||
revanced-library = "2.2.1"
|
revanced-library = "2.2.1"
|
||||||
|
revanced-multidexlib2 = "3.0.3.r3"
|
||||||
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"
|
||||||
|
@ -77,6 +78,7 @@ room-compiler = { group = "androidx.room", name = "room-compiler", version.ref =
|
||||||
# Patcher
|
# Patcher
|
||||||
revanced-patcher = { group = "app.revanced", name = "revanced-patcher", version.ref = "revanced-patcher" }
|
revanced-patcher = { group = "app.revanced", name = "revanced-patcher", version.ref = "revanced-patcher" }
|
||||||
revanced-library = { group = "app.revanced", name = "revanced-library", version.ref = "revanced-library" }
|
revanced-library = { group = "app.revanced", name = "revanced-library", version.ref = "revanced-library" }
|
||||||
|
revanced-multidexlib2 = { group = "app.revanced", name = "multidexlib2", version.ref = "revanced-multidexlib2" }
|
||||||
|
|
||||||
# Koin
|
# Koin
|
||||||
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin-version" }
|
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin-version" }
|
||||||
|
|
Loading…
Reference in a new issue