mirror of
https://github.com/revanced/revanced-manager-compose-old.git
synced 2025-05-01 23:14:27 +02:00
refactor: minor cleanup
This commit is contained in:
parent
74318f30fb
commit
79d4330408
@ -1,10 +1,9 @@
|
|||||||
package app.revanced.manager.di
|
package app.revanced.manager.di
|
||||||
|
|
||||||
import app.revanced.manager.patcher.worker.PatcherWorker
|
import app.revanced.manager.patcher.worker.PatcherWorker
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.androidx.workmanager.dsl.workerOf
|
||||||
import org.koin.androidx.workmanager.dsl.worker
|
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val workerModule = module {
|
val workerModule = module {
|
||||||
worker { PatcherWorker(androidContext(), get(), get(), get()) }
|
workerOf(::PatcherWorker)
|
||||||
}
|
}
|
@ -38,7 +38,7 @@ class ManagerAPI(
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadPatches() = withContext(Dispatchers.Default) {
|
suspend fun downloadPatches() = withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val asset =
|
val asset =
|
||||||
if (prefs.srcPatches!! == ghPatches) reVancedAPI.findAsset(ghPatches, ".jar")
|
if (prefs.srcPatches!! == ghPatches) reVancedAPI.findAsset(ghPatches, ".jar")
|
||||||
@ -46,7 +46,6 @@ class ManagerAPI(
|
|||||||
asset.run {
|
asset.run {
|
||||||
downloadAsset(app.cacheDir, downloadUrl).run {
|
downloadAsset(app.cacheDir, downloadUrl).run {
|
||||||
patcherUtils.run {
|
patcherUtils.run {
|
||||||
patchBundleFile = absolutePath
|
|
||||||
loadPatchBundle(absolutePath)
|
loadPatchBundle(absolutePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,8 @@ import android.util.Log
|
|||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import app.revanced.manager.ui.Resource
|
import app.revanced.manager.ui.Resource
|
||||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
|
||||||
import app.revanced.manager.util.tag
|
import app.revanced.manager.util.tag
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.data.Context
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.util.patch.PatchBundle
|
import app.revanced.patcher.util.patch.PatchBundle
|
||||||
import dalvik.system.DexClassLoader
|
import dalvik.system.DexClassLoader
|
||||||
@ -19,12 +16,10 @@ import io.sentry.Sentry
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class PatcherUtils(val app: Application) {
|
class PatcherUtils(val app: Application) {
|
||||||
val patches = mutableStateOf<Resource<List<Class<out Patch<Context>>>>>(Resource.Loading)
|
val patches = mutableStateOf<Resource<List<ReVancedPatch>>>(Resource.Loading)
|
||||||
val filteredPatches = mutableStateListOf<PatchClass>()
|
|
||||||
val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>())
|
val selectedAppPackage = mutableStateOf(Optional.empty<ApplicationInfo>())
|
||||||
val selectedAppPackagePath = mutableStateOf<String?>(null)
|
val selectedAppPackagePath = mutableStateOf<String?>(null)
|
||||||
val selectedPatches = mutableStateListOf<String>()
|
val selectedPatches = mutableStateListOf<ReVancedPatch>()
|
||||||
lateinit var patchBundleFile: String
|
|
||||||
|
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
patches.value = Resource.Loading
|
patches.value = Resource.Loading
|
||||||
@ -32,16 +27,15 @@ class PatcherUtils(val app: Application) {
|
|||||||
selectedPatches.clear()
|
selectedPatches.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadPatchBundle(file: String? = patchBundleFile) {
|
fun loadPatchBundle(file: String) {
|
||||||
|
cleanup()
|
||||||
try {
|
try {
|
||||||
if (this::patchBundleFile.isInitialized) {
|
val patchClasses = PatchBundle.Dex(
|
||||||
val patchClasses = PatchBundle.Dex(
|
file, DexClassLoader(
|
||||||
file!!, DexClassLoader(
|
file, app.codeCacheDir.absolutePath, null, javaClass.classLoader
|
||||||
file, app.codeCacheDir.absolutePath, null, javaClass.classLoader
|
)
|
||||||
)
|
).loadPatches()
|
||||||
).loadPatches()
|
patches.value = Resource.Success(patchClasses)
|
||||||
patches.value = Resource.Success(patchClasses)
|
|
||||||
} else throw IllegalStateException("No patch bundle(s) selected.")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "Failed to load patch bundle.", e)
|
Log.e(tag, "Failed to load patch bundle.", e)
|
||||||
Sentry.captureException(e)
|
Sentry.captureException(e)
|
||||||
@ -50,7 +44,9 @@ class PatcherUtils(val app: Application) {
|
|||||||
|
|
||||||
fun getSelectedPackageInfo(): PackageInfo? {
|
fun getSelectedPackageInfo(): PackageInfo? {
|
||||||
return if (selectedAppPackage.value.isPresent) {
|
return if (selectedAppPackage.value.isPresent) {
|
||||||
val path = selectedAppPackage.value.get().publicSourceDir ?: selectedAppPackagePath.value ?: return null
|
val path =
|
||||||
|
selectedAppPackage.value.get().publicSourceDir ?: selectedAppPackagePath.value
|
||||||
|
?: return null
|
||||||
app.packageManager.getPackageArchiveInfo(
|
app.packageManager.getPackageArchiveInfo(
|
||||||
path, 1
|
path, 1
|
||||||
)
|
)
|
||||||
@ -58,9 +54,5 @@ class PatcherUtils(val app: Application) {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findPatchesByIds(ids: Iterable<String>): List<Class<out Patch<Context>>> {
|
|
||||||
val (patches) = patches.value as? Resource.Success ?: return listOf()
|
|
||||||
return patches.filter { patch -> ids.any { it == patch.patchName } && patch.compatiblePackages!!.any { it.name == getSelectedPackageInfo()?.packageName } }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
typealias ReVancedPatch = Class<out Patch<Context>>
|
||||||
|
@ -13,6 +13,7 @@ import android.util.Log
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.Data
|
||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
@ -80,20 +81,6 @@ class PatcherWorker(
|
|||||||
Sentry.captureException(e)
|
Sentry.captureException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
|
||||||
runPatcher(workdir)
|
|
||||||
Result.success()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
log("Error while patching: ${e::class.simpleName}: ${e.message}", ERROR)
|
|
||||||
Log.e(tag, e.stackTraceToString())
|
|
||||||
Sentry.captureException(e)
|
|
||||||
Result.failure()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun runPatcher(
|
|
||||||
workdir: File
|
|
||||||
): Boolean {
|
|
||||||
val wakeLock: PowerManager.WakeLock =
|
val wakeLock: PowerManager.WakeLock =
|
||||||
(applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
(applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||||
newWakeLock(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, "$tag::Patcher").apply {
|
newWakeLock(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, "$tag::Patcher").apply {
|
||||||
@ -102,7 +89,8 @@ class PatcherWorker(
|
|||||||
}
|
}
|
||||||
Log.d(tag, "Acquired wakelock.")
|
Log.d(tag, "Acquired wakelock.")
|
||||||
|
|
||||||
try {
|
return try {
|
||||||
|
|
||||||
val aaptPath = Aapt.binary(applicationContext)?.absolutePath
|
val aaptPath = Aapt.binary(applicationContext)?.absolutePath
|
||||||
if (aaptPath == null) {
|
if (aaptPath == null) {
|
||||||
log("AAPT2 not found.", ERROR)
|
log("AAPT2 not found.", ERROR)
|
||||||
@ -118,13 +106,14 @@ class PatcherWorker(
|
|||||||
val appPath = patcherUtils.selectedAppPackagePath.value
|
val appPath = patcherUtils.selectedAppPackagePath.value
|
||||||
|
|
||||||
log("Checking prerequisites...", INFO)
|
log("Checking prerequisites...", INFO)
|
||||||
val patches = patcherUtils.findPatchesByIds(patcherUtils.selectedPatches)
|
val patches = patcherUtils.selectedPatches
|
||||||
if (patches.isEmpty()) throw IllegalStateException("No patches selected.")
|
if (patches.isEmpty()) throw IllegalStateException("No patches selected.")
|
||||||
|
|
||||||
log("Creating directories...", INFO)
|
log("Creating directories...", INFO)
|
||||||
val inputFile = File(workdir, "input.apk")
|
val inputFile = File(workdir, "input.apk")
|
||||||
val patchedFile = File(workdir, "patched.apk")
|
val patchedFile = File(workdir, "patched.apk")
|
||||||
val outputFile = File(inputData.getString("output")!!)
|
val outputFile = File(workdir, "output.apk")
|
||||||
|
val finalFile = reVancedFolder.resolve(appInfo.packageName + ".apk")
|
||||||
val cacheDirectory = workdir.resolve("cache")
|
val cacheDirectory = workdir.resolve("cache")
|
||||||
|
|
||||||
val integrations = managerAPI.downloadIntegrations(integrationsCacheDir)
|
val integrations = managerAPI.downloadIntegrations(integrationsCacheDir)
|
||||||
@ -203,19 +192,24 @@ class PatcherWorker(
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Files.copy(
|
Files.copy(
|
||||||
outputFile.inputStream(),
|
outputFile.inputStream(),
|
||||||
reVancedFolder.resolve(appInfo.packageName + ".apk").toPath(),
|
finalFile.toPath(),
|
||||||
StandardCopyOption.REPLACE_EXISTING
|
StandardCopyOption.REPLACE_EXISTING
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
log("Successfully patched!", SUCCESS)
|
log("Successfully patched!", SUCCESS)
|
||||||
patcherUtils.cleanup()
|
Result.success(Data.Builder().putString(OUTPUT, finalFile.absolutePath).build())
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log("Error while patching: ${e::class.simpleName}: ${e.message}", ERROR)
|
||||||
|
Log.e(tag, e.stackTraceToString())
|
||||||
|
Sentry.captureException(e)
|
||||||
|
Result.failure()
|
||||||
} finally {
|
} finally {
|
||||||
Log.d(tag, "Deleting workdir")
|
Log.d(tag, "Deleting workdir")
|
||||||
workdir.deleteRecursively()
|
workdir.deleteRecursively()
|
||||||
wakeLock.release()
|
wakeLock.release()
|
||||||
Log.d(tag, "Released wakelock.")
|
Log.d(tag, "Released wakelock.")
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -236,6 +230,7 @@ class PatcherWorker(
|
|||||||
const val PATCH_MESSAGE = "PATCH_MESSAGE"
|
const val PATCH_MESSAGE = "PATCH_MESSAGE"
|
||||||
const val PATCH_STATUS = "PATCH_STATUS"
|
const val PATCH_STATUS = "PATCH_STATUS"
|
||||||
const val PATCH_LOG = "PATCH_LOG"
|
const val PATCH_LOG = "PATCH_LOG"
|
||||||
|
const val OUTPUT = "output"
|
||||||
|
|
||||||
const val INFO = 0
|
const val INFO = 0
|
||||||
const val ERROR = 1
|
const val ERROR = 1
|
||||||
|
@ -29,14 +29,12 @@ fun PatcherScreen(
|
|||||||
onClickSourceSelector: () -> Unit,
|
onClickSourceSelector: () -> Unit,
|
||||||
vm: PatcherScreenViewModel = getViewModel(),
|
vm: PatcherScreenViewModel = getViewModel(),
|
||||||
) {
|
) {
|
||||||
val hasAppSelected by mutableStateOf(vm.selectedAppPackage.isPresent)
|
|
||||||
val patchesLoaded by mutableStateOf(vm.patchesLoaded is Resource.Success)
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
enabled = hasAppSelected && vm.selectedPatches.isNotEmpty(),
|
enabled = vm.selectedAppPackage.isPresent && vm.selectedPatches.isNotEmpty(),
|
||||||
onClick = onClickPatch,
|
onClick = onClickPatch,
|
||||||
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
|
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
|
||||||
text = { Text(stringResource(R.string.patch)) }
|
text = { Text(stringResource(R.string.patch)) }
|
||||||
@ -66,7 +64,7 @@ fun PatcherScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 4.dp)
|
.padding(vertical = 4.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
enabled = patchesLoaded,
|
enabled = vm.patches is Resource.Success,
|
||||||
onClick = onClickAppSelector
|
onClick = onClickAppSelector
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
@ -86,7 +84,7 @@ fun PatcherScreen(
|
|||||||
Spacer(Modifier.width(5.dp))
|
Spacer(Modifier.width(5.dp))
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = if (patchesLoaded) {
|
text = if (vm.patches is Resource.Success) {
|
||||||
if (vm.selectedAppPackage.isPresent) {
|
if (vm.selectedAppPackage.isPresent) {
|
||||||
vm.selectedAppPackage.get().packageName
|
vm.selectedAppPackage.get().packageName
|
||||||
} else {
|
} else {
|
||||||
@ -105,7 +103,7 @@ fun PatcherScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 4.dp)
|
.padding(vertical = 4.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
enabled = hasAppSelected,
|
enabled = vm.selectedAppPackage.isPresent,
|
||||||
onClick = onClickPatchSelector
|
onClick = onClickPatchSelector
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
@ -114,7 +112,7 @@ fun PatcherScreen(
|
|||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = if (!hasAppSelected) {
|
text = if (!vm.selectedAppPackage.isPresent) {
|
||||||
stringResource(R.string.select_an_application_first)
|
stringResource(R.string.select_an_application_first)
|
||||||
} else if (vm.selectedPatches.isNotEmpty()) {
|
} else if (vm.selectedPatches.isNotEmpty()) {
|
||||||
"${vm.selectedPatches.size} patches selected."
|
"${vm.selectedPatches.size} patches selected."
|
||||||
|
@ -26,7 +26,7 @@ fun ContributorsSubscreen(
|
|||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
vm: ContributorsViewModel = getViewModel()
|
vm: ContributorsViewModel = getViewModel()
|
||||||
) {
|
) {
|
||||||
val ctx = LocalContext.current.applicationContext
|
val ctx = LocalContext.current
|
||||||
AppScaffold(
|
AppScaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppLargeTopBar(
|
AppLargeTopBar(
|
||||||
|
@ -15,12 +15,9 @@ import androidx.compose.ui.unit.dp
|
|||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.ui.component.AppMediumTopBar
|
import app.revanced.manager.ui.component.AppMediumTopBar
|
||||||
import app.revanced.manager.ui.component.AppScaffold
|
import app.revanced.manager.ui.component.AppScaffold
|
||||||
import app.revanced.manager.ui.component.LoadingIndicator
|
|
||||||
import app.revanced.manager.ui.component.PatchCard
|
import app.revanced.manager.ui.component.PatchCard
|
||||||
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.koin.androidx.compose.getViewModel
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
|
||||||
@SuppressLint("UnrememberedMutableState")
|
@SuppressLint("UnrememberedMutableState")
|
||||||
@ -30,101 +27,79 @@ fun PatchesSelectorSubscreen(
|
|||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
vm: PatchesSelectorViewModel = getViewModel(),
|
vm: PatchesSelectorViewModel = getViewModel(),
|
||||||
) {
|
) {
|
||||||
val patches = vm.filteredPatches
|
|
||||||
var query by mutableStateOf("")
|
|
||||||
|
|
||||||
LaunchedEffect(null) {
|
AppScaffold(topBar = { scrollBehavior ->
|
||||||
launch(Dispatchers.Default) {
|
AppMediumTopBar(
|
||||||
vm.filterPatches()
|
topBarTitle = stringResource(id = R.string.card_patches_header),
|
||||||
}
|
scrollBehavior = scrollBehavior,
|
||||||
}
|
actions = {
|
||||||
AppScaffold(
|
IconButton(onClick = {
|
||||||
topBar = { scrollBehavior ->
|
vm.selectAllPatches(vm.patches, vm.selectedPatches.isEmpty())
|
||||||
AppMediumTopBar(
|
}) {
|
||||||
topBarTitle = stringResource(id = R.string.card_patches_header),
|
if (vm.selectedPatches.isEmpty()) Icon(
|
||||||
scrollBehavior = scrollBehavior,
|
Icons.Default.SelectAll, contentDescription = null
|
||||||
actions = {
|
) else Icon(Icons.Default.Deselect, contentDescription = null)
|
||||||
IconButton(onClick = {
|
}
|
||||||
vm.selectAllPatches(patches, vm.selectedPatches.isEmpty())
|
},
|
||||||
}) {
|
onBackClick = onBackClick
|
||||||
if (vm.selectedPatches.isEmpty()) Icon(
|
)
|
||||||
Icons.Default.SelectAll, contentDescription = null
|
}) { paddingValues ->
|
||||||
) else Icon(Icons.Default.Deselect, contentDescription = null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onBackClick = onBackClick
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { paddingValues ->
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(paddingValues)
|
modifier = Modifier.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
if (!vm.loading) {
|
val search = vm.search
|
||||||
if (patches.isNotEmpty()) {
|
if (vm.patches.isNotEmpty()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp, 4.dp),
|
.padding(8.dp, 4.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Row(
|
OutlinedTextField(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier
|
||||||
) {
|
.fillMaxWidth()
|
||||||
OutlinedTextField(
|
.padding(8.dp),
|
||||||
modifier = Modifier
|
shape = RoundedCornerShape(12.dp),
|
||||||
.fillMaxWidth()
|
value = search,
|
||||||
.padding(8.dp),
|
onValueChange = { vm.search(it) },
|
||||||
shape = RoundedCornerShape(12.dp),
|
leadingIcon = {
|
||||||
value = query,
|
Icon(Icons.Default.Search, "Search")
|
||||||
onValueChange = { newValue ->
|
},
|
||||||
query = newValue
|
trailingIcon = {
|
||||||
},
|
if (search.isNotEmpty()) {
|
||||||
leadingIcon = {
|
IconButton(onClick = {
|
||||||
Icon(Icons.Default.Search, "Search")
|
vm.clearSearch()
|
||||||
},
|
}) {
|
||||||
trailingIcon = {
|
Icon(Icons.Default.Clear, "Clear")
|
||||||
if (query.isNotEmpty()) {
|
|
||||||
IconButton(onClick = {
|
|
||||||
query = ""
|
|
||||||
}) {
|
|
||||||
Icon(Icons.Default.Clear, "Clear")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LazyColumn(Modifier.padding(0.dp, 2.dp)) {
|
|
||||||
|
|
||||||
if (query.isEmpty() || query.isBlank()) {
|
|
||||||
items(count = patches.size) {
|
|
||||||
val patch = patches[it]
|
|
||||||
val name = patch.patch.patchName
|
|
||||||
PatchCard(patch, vm.isPatchSelected(name)) {
|
|
||||||
vm.selectPatch(name, !vm.isPatchSelected(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
items(count = patches.size) {
|
|
||||||
val patch = patches[it]
|
|
||||||
val name = patch.patch.patchName
|
|
||||||
if (name.contains(query.lowercase())) {
|
|
||||||
PatchCard(patch, vm.isPatchSelected(name)) {
|
|
||||||
vm.selectPatch(name, !vm.isPatchSelected(name))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Column(
|
|
||||||
Modifier.fillMaxSize(),
|
|
||||||
Arrangement.Center,
|
|
||||||
Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.no_compatible_patches))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else LoadingIndicator(null)
|
LazyColumn(Modifier.padding(0.dp, 2.dp)) {
|
||||||
|
items(count = vm.patches.size) {
|
||||||
|
val patchClass = vm.patches[it]
|
||||||
|
val name = patchClass.patch.patchName
|
||||||
|
if (search.isBlank() && name.contains(search.lowercase())) {
|
||||||
|
PatchCard(patchClass, vm.isPatchSelected(patchClass.patch)) {
|
||||||
|
vm.selectPatch(
|
||||||
|
patchClass.patch,
|
||||||
|
!vm.isPatchSelected(patchClass.patch)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
Modifier.fillMaxSize(), Arrangement.Center, Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.no_compatible_patches))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -53,7 +53,7 @@ fun PatchingSubscreen(
|
|||||||
Column {
|
Column {
|
||||||
if (vm.installFailure) {
|
if (vm.installFailure) {
|
||||||
InstallFailureDialog(
|
InstallFailureDialog(
|
||||||
onDismiss = { vm.installFailure = false },
|
onDismiss = { vm.dismissDialog() },
|
||||||
status = vm.pmStatus,
|
status = vm.pmStatus,
|
||||||
result = vm.extra
|
result = vm.extra
|
||||||
)
|
)
|
||||||
@ -149,7 +149,7 @@ fun PatchingSubscreen(
|
|||||||
) {
|
) {
|
||||||
Spacer(Modifier.weight(1f, true))
|
Spacer(Modifier.weight(1f, true))
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
vm.installApk(vm.outputFile)
|
vm.installApk()
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(R.string.install))
|
Text(text = stringResource(R.string.install))
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ class AppSelectorViewModel(
|
|||||||
|
|
||||||
val filteredApps = mutableStateListOf<ApplicationInfo>()
|
val filteredApps = mutableStateListOf<ApplicationInfo>()
|
||||||
val patches = patcherUtils.patches
|
val patches = patcherUtils.patches
|
||||||
private val filteredPatches = patcherUtils.filteredPatches
|
|
||||||
private val selectedAppPackage = patcherUtils.selectedAppPackage
|
private val selectedAppPackage = patcherUtils.selectedAppPackage
|
||||||
private val selectedAppPackagePath = patcherUtils.selectedAppPackagePath
|
private val selectedAppPackagePath = patcherUtils.selectedAppPackagePath
|
||||||
private val selectedPatches = patcherUtils.selectedPatches
|
private val selectedPatches = patcherUtils.selectedPatches
|
||||||
@ -34,22 +33,27 @@ class AppSelectorViewModel(
|
|||||||
viewModelScope.launch { filterApps() }
|
viewModelScope.launch { filterApps() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun filterApps() = withContext(Dispatchers.Default) {
|
@Suppress("RemoveExplicitTypeArguments")
|
||||||
|
private suspend fun filterApps() = withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val (patches) = patches.value as Resource.Success
|
val (patches) = patches.value as Resource.Success
|
||||||
patches.forEach patch@{ patch ->
|
val apps = buildList<ApplicationInfo> {
|
||||||
patch.compatiblePackages?.forEach { pkg ->
|
patches.forEach patch@{ patch ->
|
||||||
try {
|
patch.compatiblePackages?.forEach { pkg ->
|
||||||
if (!(filteredApps.any { it.packageName == pkg.name })) {
|
try {
|
||||||
val appInfo = app.packageManager.getApplicationInfo(pkg.name, 1)
|
if (!any { it.packageName == pkg.name }) {
|
||||||
filteredApps.add(appInfo)
|
add(app.packageManager.getApplicationInfo(pkg.name, 1))
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
return@forEach
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
filteredApps.addAll(apps)
|
||||||
|
}
|
||||||
Log.d(tag, "Filtered apps.")
|
Log.d(tag, "Filtered apps.")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "An error occurred while filtering", e)
|
Log.e(tag, "An error occurred while filtering", e)
|
||||||
@ -69,7 +73,6 @@ class AppSelectorViewModel(
|
|||||||
selectedAppPackage.value.ifPresent { s ->
|
selectedAppPackage.value.ifPresent { s ->
|
||||||
if (s != appId) {
|
if (s != appId) {
|
||||||
selectedPatches.clear()
|
selectedPatches.clear()
|
||||||
filteredPatches.clear()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectedAppPackage.value = Optional.of(appId)
|
selectedAppPackage.value = Optional.of(appId)
|
||||||
@ -86,8 +89,7 @@ class AppSelectorViewModel(
|
|||||||
setSelectedAppPackage(
|
setSelectedAppPackage(
|
||||||
app.packageManager.getPackageArchiveInfo(
|
app.packageManager.getPackageArchiveInfo(
|
||||||
apkDir.path, 1
|
apkDir.path, 1
|
||||||
)!!.applicationInfo,
|
)!!.applicationInfo, apkDir.absolutePath
|
||||||
apkDir.absolutePath
|
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "Failed to load apk", e)
|
Log.e(tag, "Failed to load apk", e)
|
||||||
|
@ -21,5 +21,5 @@ class PatcherScreenViewModel(
|
|||||||
|
|
||||||
val selectedPatches = patcherUtils.selectedPatches
|
val selectedPatches = patcherUtils.selectedPatches
|
||||||
val selectedAppPackage by patcherUtils.selectedAppPackage
|
val selectedAppPackage by patcherUtils.selectedAppPackage
|
||||||
val patchesLoaded by patcherUtils.patches
|
val patches by patcherUtils.patches
|
||||||
}
|
}
|
@ -2,61 +2,79 @@ package app.revanced.manager.ui.viewmodel
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.revanced.manager.patcher.PatcherUtils
|
import app.revanced.manager.patcher.PatcherUtils
|
||||||
|
import app.revanced.manager.patcher.ReVancedPatch
|
||||||
import app.revanced.manager.ui.Resource
|
import app.revanced.manager.ui.Resource
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.data.Context
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
class PatchesSelectorViewModel(
|
class PatchesSelectorViewModel(
|
||||||
private val patcherUtils: PatcherUtils
|
private val patcherUtils: PatcherUtils
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val filteredPatches = patcherUtils.filteredPatches
|
val patches = mutableStateListOf<PatchClass>()
|
||||||
val selectedPatches = patcherUtils.selectedPatches
|
val selectedPatches = patcherUtils.selectedPatches
|
||||||
var loading by mutableStateOf(true)
|
var search by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
fun isPatchSelected(patchId: String): Boolean {
|
fun search(search: String) {
|
||||||
return selectedPatches.contains(patchId)
|
this.search = search
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectPatch(patchId: String, state: Boolean) {
|
fun clearSearch() {
|
||||||
if (state) selectedPatches.add(patchId)
|
search = ""
|
||||||
else selectedPatches.remove(patchId)
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch { filterPatches() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isPatchSelected(patch: ReVancedPatch): Boolean {
|
||||||
|
return selectedPatches.contains(patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectPatch(patch: ReVancedPatch, state: Boolean) {
|
||||||
|
if (state) selectedPatches.add(patch)
|
||||||
|
else selectedPatches.remove(patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) {
|
fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) {
|
||||||
patchList.forEach { patch ->
|
patchList.forEach { patchClass ->
|
||||||
val patchId = patch.patch.patchName
|
val patch = patchClass.patch
|
||||||
if (selectAll && !patch.unsupported) selectedPatches.add(patchId)
|
if (selectAll && !patchClass.unsupported) selectedPatches.add(patch)
|
||||||
else selectedPatches.remove(patchId)
|
else selectedPatches.remove(patch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filterPatches() {
|
private suspend fun filterPatches() = withContext(Dispatchers.IO) {
|
||||||
loading = true
|
val selected = patcherUtils.getSelectedPackageInfo() ?: return@withContext
|
||||||
val selected = patcherUtils.getSelectedPackageInfo() ?: return
|
val (patchList) = patcherUtils.patches.value as? Resource.Success ?: return@withContext
|
||||||
val (patches) = patcherUtils.patches.value as? Resource.Success ?: return
|
val filtered = buildList {
|
||||||
if (filteredPatches.isNotEmpty()) {
|
patchList.forEach { patch ->
|
||||||
loading = false; return
|
var unsupported = false
|
||||||
}
|
patch.compatiblePackages?.forEach { pkg ->
|
||||||
patches.forEach patch@{ patch ->
|
// if we detect unsupported once, don't overwrite it
|
||||||
var unsupported = false
|
if (pkg.name == selected.packageName) {
|
||||||
patch.compatiblePackages?.forEach { pkg ->
|
if (!unsupported)
|
||||||
// if we detect unsupported once, don't overwrite it
|
unsupported =
|
||||||
if (pkg.name == selected.packageName) {
|
pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName }
|
||||||
if (!unsupported)
|
add(PatchClass(patch, unsupported))
|
||||||
unsupported =
|
}
|
||||||
pkg.versions.isNotEmpty() && !pkg.versions.any { it == selected.versionName }
|
|
||||||
filteredPatches.add(PatchClass(patch, unsupported))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loading = false
|
withContext(Dispatchers.Main) {
|
||||||
|
patches.addAll(filtered)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import app.revanced.manager.installer.service.InstallService
|
|||||||
import app.revanced.manager.installer.service.UninstallService
|
import app.revanced.manager.installer.service.UninstallService
|
||||||
import app.revanced.manager.installer.utils.PM
|
import app.revanced.manager.installer.utils.PM
|
||||||
import app.revanced.manager.patcher.worker.PatcherWorker
|
import app.revanced.manager.patcher.worker.PatcherWorker
|
||||||
|
import app.revanced.manager.util.toast
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class PatchingScreenViewModel(
|
class PatchingScreenViewModel(
|
||||||
@ -38,32 +39,43 @@ class PatchingScreenViewModel(
|
|||||||
object Failure : Status()
|
object Failure : Status()
|
||||||
}
|
}
|
||||||
|
|
||||||
val workManager = WorkManager.getInstance(app)
|
private var output: String? = null
|
||||||
var installFailure by mutableStateOf(false)
|
|
||||||
var pmStatus by mutableStateOf(-999)
|
|
||||||
var extra by mutableStateOf("")
|
|
||||||
|
|
||||||
val outputFile = File(app.cacheDir, "output.apk")
|
var installFailure by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var pmStatus by mutableStateOf(-999)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var extra by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val workManager = WorkManager.getInstance(app)
|
||||||
|
|
||||||
private val patcherWorker =
|
private val patcherWorker =
|
||||||
OneTimeWorkRequest.Builder(PatcherWorker::class.java) // create Worker
|
OneTimeWorkRequest.Builder(PatcherWorker::class.java) // create Worker
|
||||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST).setInputData(
|
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST).setInputData(
|
||||||
Data.Builder().putString("output", outputFile.path).build()
|
Data.Builder().putString(PatcherWorker.OUTPUT, null).build()
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
private val liveData = workManager.getWorkInfoByIdLiveData(patcherWorker.id) // get LiveData
|
private val liveData = workManager.getWorkInfoByIdLiveData(patcherWorker.id)
|
||||||
|
|
||||||
private val observer = Observer { workInfo: WorkInfo -> // observer for observing patch status
|
private val observer = Observer { workInfo: WorkInfo ->
|
||||||
status = when (workInfo.state) {
|
status = when (workInfo.state) {
|
||||||
WorkInfo.State.RUNNING -> Status.Patching
|
WorkInfo.State.RUNNING -> Status.Patching
|
||||||
WorkInfo.State.SUCCEEDED -> Status.Success
|
WorkInfo.State.SUCCEEDED -> Status.Success
|
||||||
WorkInfo.State.FAILED -> Status.Failure
|
WorkInfo.State.FAILED -> Status.Failure
|
||||||
else -> Status.Idle
|
else -> Status.Idle
|
||||||
}
|
}
|
||||||
|
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
|
||||||
|
output = workInfo.outputData.getString("output")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val logs = mutableStateListOf<PatchLog>()
|
val logs = mutableStateListOf<PatchLog>()
|
||||||
|
|
||||||
var status by mutableStateOf<Status>(Status.Idle)
|
var status by mutableStateOf<Status>(Status.Idle)
|
||||||
|
private set
|
||||||
|
|
||||||
private val installBroadcastReceiver = object : BroadcastReceiver() {
|
private val installBroadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
|
||||||
@ -95,15 +107,25 @@ class PatchingScreenViewModel(
|
|||||||
workManager.enqueueUniqueWork("patching", ExistingWorkPolicy.KEEP, patcherWorker)
|
workManager.enqueueUniqueWork("patching", ExistingWorkPolicy.KEEP, patcherWorker)
|
||||||
liveData.observeForever(observer)
|
liveData.observeForever(observer)
|
||||||
app.registerReceiver(installBroadcastReceiver, IntentFilter().apply {
|
app.registerReceiver(installBroadcastReceiver, IntentFilter().apply {
|
||||||
addAction(InstallService.APP_INSTALL_ACTION)
|
arrayOf(
|
||||||
addAction(UninstallService.APP_UNINSTALL_ACTION)
|
InstallService.APP_INSTALL_ACTION,
|
||||||
addAction(PatcherWorker.PATCH_LOG)
|
UninstallService.APP_UNINSTALL_ACTION,
|
||||||
|
PatcherWorker.PATCH_LOG
|
||||||
|
).forEach {
|
||||||
|
addAction(it)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun installApk(apk: File) {
|
fun dismissDialog() {
|
||||||
PM.installApp(apk, app)
|
installFailure = false
|
||||||
log(PatchLog.Info("Installing..."))
|
}
|
||||||
|
|
||||||
|
fun installApk() {
|
||||||
|
if (output != null) {
|
||||||
|
PM.installApp(File(output!!), app)
|
||||||
|
log(PatchLog.Info("Installing..."))
|
||||||
|
} else app.toast("Couldn't find APK file.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun postInstallStatus() {
|
fun postInstallStatus() {
|
||||||
|
@ -7,11 +7,11 @@ import androidx.lifecycle.ViewModel
|
|||||||
import app.revanced.manager.patcher.PatcherUtils
|
import app.revanced.manager.patcher.PatcherUtils
|
||||||
import app.revanced.manager.util.tag
|
import app.revanced.manager.util.tag
|
||||||
import io.sentry.Sentry
|
import io.sentry.Sentry
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
|
|
||||||
class SourceSelectorViewModel(val app: Application, val patcherUtils: PatcherUtils) : ViewModel() {
|
class SourceSelectorViewModel(val app: Application, private val patcherUtils: PatcherUtils) :
|
||||||
|
ViewModel() {
|
||||||
fun loadBundle(uri: Uri) {
|
fun loadBundle(uri: Uri) {
|
||||||
try {
|
try {
|
||||||
val patchesFile = app.cacheDir.resolve("patches.jar")
|
val patchesFile = app.cacheDir.resolve("patches.jar")
|
||||||
@ -20,10 +20,7 @@ class SourceSelectorViewModel(val app: Application, val patcherUtils: PatcherUti
|
|||||||
patchesFile.toPath(),
|
patchesFile.toPath(),
|
||||||
StandardCopyOption.REPLACE_EXISTING
|
StandardCopyOption.REPLACE_EXISTING
|
||||||
)
|
)
|
||||||
patchesFile.absolutePath.also {
|
patcherUtils.loadPatchBundle(patchesFile.absolutePath)
|
||||||
patcherUtils.patchBundleFile = it
|
|
||||||
patcherUtils.loadPatchBundle(it)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "Failed to load bundle", e)
|
Log.e(tag, "Failed to load bundle", e)
|
||||||
Sentry.captureException(e)
|
Sentry.captureException(e)
|
||||||
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
|
|
||||||
@ -20,3 +21,7 @@ fun Context.loadIcon(string: String): Drawable? {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.toast(string: String, duration: Int = Toast.LENGTH_SHORT) {
|
||||||
|
Toast.makeText(this, string, duration).show()
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user