mirror of
https://github.com/revanced/revanced-manager-compose-old.git
synced 2025-04-30 14:34:29 +02:00
feat: patching screen
This commit is contained in:
parent
09e1082c5e
commit
a1e172a852
@ -86,6 +86,7 @@ dependencies {
|
||||
val koinVersion = "3.2.0"
|
||||
implementation("io.insert-koin:koin-android:$koinVersion")
|
||||
implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
|
||||
implementation("io.insert-koin:koin-androidx-workmanager:3.2.1")
|
||||
|
||||
// Compose
|
||||
val composeVersion = "1.3.0-alpha03"
|
||||
@ -117,7 +118,7 @@ dependencies {
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||
|
||||
// ReVanced
|
||||
implementation("app.revanced:revanced-patcher:4.2.3")
|
||||
implementation("app.revanced:revanced-patcher:4.4.2")
|
||||
|
||||
// Coil for network image
|
||||
implementation("io.coil-kt:coil-compose:2.1.0")
|
||||
@ -125,4 +126,5 @@ dependencies {
|
||||
// Signing & aligning
|
||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||
implementation("com.android.tools.build:apksig:7.2.2")
|
||||
|
||||
}
|
||||
|
@ -33,8 +33,19 @@
|
||||
android:supportsRtl="true"
|
||||
android:largeHeap="true"
|
||||
android:theme="@style/Theme.ReVancedManager"
|
||||
|
||||
tools:targetApi="33">
|
||||
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name="androidx.work.WorkManagerInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
@ -15,6 +15,7 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import app.revanced.manager.preferences.PreferencesManager
|
||||
import app.revanced.manager.ui.navigation.AppDestination
|
||||
import app.revanced.manager.ui.screen.MainDashboardScreen
|
||||
import app.revanced.manager.ui.screen.PatchingScreen
|
||||
import app.revanced.manager.ui.screen.subscreens.AppSelectorSubscreen
|
||||
import app.revanced.manager.ui.screen.subscreens.PatchesSelectorSubscreen
|
||||
import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
||||
@ -48,10 +49,9 @@ class MainActivity : ComponentActivity() {
|
||||
) { destination ->
|
||||
when (destination) {
|
||||
is AppDestination.Dashboard -> MainDashboardScreen(navigator = navigator)
|
||||
is AppDestination.AppSelector -> AppSelectorSubscreen(
|
||||
navigator = navigator
|
||||
)
|
||||
is AppDestination.AppSelector -> AppSelectorSubscreen(navigator = navigator)
|
||||
is AppDestination.PatchSelector -> PatchesSelectorSubscreen(navigator = navigator)
|
||||
is AppDestination.Patcher -> PatchingScreen(navigator = navigator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
package app.revanced.manager
|
||||
|
||||
import android.app.Application
|
||||
import app.revanced.manager.di.httpModule
|
||||
import app.revanced.manager.di.preferencesModule
|
||||
import app.revanced.manager.di.repositoryModule
|
||||
import app.revanced.manager.di.viewModelModule
|
||||
import app.revanced.manager.di.*
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.workmanager.koin.workManagerFactory
|
||||
import org.koin.core.context.startKoin
|
||||
|
||||
class ManagerApplication : Application() {
|
||||
@ -14,7 +12,8 @@ class ManagerApplication : Application() {
|
||||
|
||||
startKoin {
|
||||
androidContext(this@ManagerApplication)
|
||||
modules(httpModule, preferencesModule, viewModelModule, repositoryModule)
|
||||
workManagerFactory()
|
||||
modules(httpModule, preferencesModule, viewModelModule, repositoryModule, workerModule)
|
||||
}
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ val client = HttpClient(Android) {
|
||||
|
||||
class API(private val repository: GitHubRepository, private val prefs: PreferencesManager) {
|
||||
|
||||
private suspend fun findAsset(repo: String, file: String): PatchesAsset {
|
||||
suspend fun findAsset(repo: String, file: String): PatchesAsset {
|
||||
val release = repository.getLatestRelease(repo)
|
||||
val asset = release.assets.findAsset(file) ?: throw MissingAssetException()
|
||||
return PatchesAsset(release, asset)
|
||||
@ -47,7 +47,6 @@ class API(private val repository: GitHubRepository, private val prefs: Preferenc
|
||||
throw Exception("Failed to download patch bundle", e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun downloadIntegrations(workdir: File): File {
|
||||
return try {
|
||||
val (_, out) = downloadAsset(
|
||||
@ -60,7 +59,7 @@ class API(private val repository: GitHubRepository, private val prefs: Preferenc
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun downloadAsset(
|
||||
suspend fun downloadAsset(
|
||||
workdir: File,
|
||||
patchesAsset: PatchesAsset
|
||||
): Pair<PatchesAsset, File> {
|
||||
|
@ -1,15 +1,13 @@
|
||||
package app.revanced.manager.di
|
||||
|
||||
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel
|
||||
import app.revanced.manager.ui.viewmodel.DashboardViewModel
|
||||
import app.revanced.manager.ui.viewmodel.PatcherViewModel
|
||||
import app.revanced.manager.ui.viewmodel.SettingsViewModel
|
||||
import app.revanced.manager.ui.viewmodel.*
|
||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val viewModelModule = module {
|
||||
viewModelOf(::SettingsViewModel)
|
||||
viewModelOf(::DashboardViewModel)
|
||||
viewModelOf(::PatcherViewModel)
|
||||
viewModelOf(::PatcherScreenViewModel)
|
||||
viewModelOf(::AppSelectorViewModel)
|
||||
viewModelOf(::PatchingScreenViewModel)
|
||||
}
|
10
app/src/main/java/app/revanced/manager/di/WorkerModule.kt
Normal file
10
app/src/main/java/app/revanced/manager/di/WorkerModule.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package app.revanced.manager.di
|
||||
|
||||
import app.revanced.manager.patcher.worker.PatcherWorker
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.workmanager.dsl.worker
|
||||
import org.koin.dsl.module
|
||||
|
||||
val workerModule = module {
|
||||
worker { PatcherWorker( androidContext(), get(), get())}
|
||||
}
|
@ -14,13 +14,16 @@ import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.Variables.patches
|
||||
import app.revanced.manager.Variables.selectedAppPackage
|
||||
import app.revanced.manager.Variables.selectedPatches
|
||||
import app.revanced.manager.api.API
|
||||
import app.revanced.manager.patcher.aapt.Aapt
|
||||
import app.revanced.manager.patcher.aligning.ZipAligner
|
||||
import app.revanced.manager.patcher.aligning.zip.ZipFile
|
||||
import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry
|
||||
import app.revanced.manager.patcher.signing.Signer
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.manager.ui.viewmodel.Logging
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.PatcherOptions
|
||||
import app.revanced.patcher.data.Data
|
||||
@ -29,15 +32,19 @@ import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.logging.Logger
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
class PatcherWorker(context: Context, parameters: WorkerParameters, private val api: API): CoroutineWorker(context, parameters) ,KoinComponent {
|
||||
|
||||
class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
||||
CoroutineWorker(context, parameters) {
|
||||
val tag = "ReVanced Manager"
|
||||
private val workdir = File(inputData.getString("workdir")!!)
|
||||
|
||||
private val workdir = createWorkDir()
|
||||
override suspend fun doWork(): Result {
|
||||
|
||||
if (runAttemptCount > 0) {
|
||||
return Result.failure(
|
||||
androidx.work.Data.Builder()
|
||||
@ -77,32 +84,41 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun runPatcher(
|
||||
private suspend fun runPatcher(
|
||||
workdir: File
|
||||
): Boolean {
|
||||
val aaptPath = Aapt.binary(applicationContext).absolutePath
|
||||
val frameworkPath =
|
||||
applicationContext.filesDir.resolve("framework").also { it.mkdirs() }.absolutePath
|
||||
val integrationsCacheDir =
|
||||
applicationContext.filesDir.resolve("integrations-cache").also { it.mkdirs() }
|
||||
val appInfo = applicationContext.packageManager.getApplicationInfo(selectedAppPackage.value.get(), 3)
|
||||
|
||||
Log.d(tag, "Checking prerequisites")
|
||||
Logging.log += "Checking prerequisites\n"
|
||||
val patches = findPatchesByIds(selectedPatches)
|
||||
if (patches.isEmpty()) return true
|
||||
|
||||
|
||||
Log.d(tag, "Creating directories")
|
||||
|
||||
File(inputData.getString("input")!!).copyTo(
|
||||
applicationContext.filesDir.resolve("base.apk"),
|
||||
true
|
||||
)
|
||||
|
||||
val inputFile = File(applicationContext.filesDir, "base.apk")
|
||||
Logging.log += "Creating directories\n"
|
||||
val inputFile = File(applicationContext.filesDir, "input.apk")
|
||||
val patchedFile = File(workdir, "patched.apk")
|
||||
val outputFile = File(applicationContext.filesDir, "out.apk")
|
||||
val outputFile = File(applicationContext.filesDir, "output.apk")
|
||||
val cacheDirectory = workdir.resolve("cache")
|
||||
val integrations = workdir.resolve("integrations.apk")
|
||||
|
||||
Logging.log += "Downloading integrations\n"
|
||||
val integrations = api.downloadIntegrations(integrationsCacheDir)
|
||||
|
||||
Logging.log += "Copying base.apk from device\n"
|
||||
withContext(Dispatchers.IO) {
|
||||
Files.copy(
|
||||
File(appInfo.publicSourceDir).toPath(),
|
||||
inputFile.toPath(),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
Log.d(tag, "Creating patcher")
|
||||
Logging.log += "Decoding resources\n"
|
||||
val patcher = Patcher( // start patcher
|
||||
PatcherOptions(
|
||||
inputFile,
|
||||
@ -134,37 +150,42 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
||||
Log.d(tag, "Adding ${patches.size} patch(es)")
|
||||
patcher.addPatches(patches)
|
||||
|
||||
Logging.log += "Merging integrations\n"
|
||||
patcher.addFiles(listOf(integrations)) {}
|
||||
|
||||
patcher.applyPatches().forEach { (patch, result) ->
|
||||
Logging.log += "Applying $patch\n"
|
||||
if (result.isSuccess) {
|
||||
Log.i(tag, "[success] $patch")
|
||||
Logging.log += "$patch has been applied successfully\n"
|
||||
return@forEach
|
||||
}
|
||||
Log.e(tag, "[error] $patch:", result.exceptionOrNull()!!)
|
||||
Logging.log += "Failed to apply $patch \n" + result.exceptionOrNull()!!
|
||||
}
|
||||
Log.d(tag, "Saving file")
|
||||
Logging.log += "Saving file\n"
|
||||
|
||||
val result = patcher.save() // compile apk
|
||||
|
||||
if (patchedFile.exists()) Files.delete(patchedFile.toPath())
|
||||
if (patchedFile.exists()) withContext(Dispatchers.IO) {
|
||||
Files.delete(patchedFile.toPath())
|
||||
}
|
||||
|
||||
ZipFile(patchedFile).use { fs -> // somehow this function is the most resource intensive
|
||||
result.dexFiles.forEach { Log.d(tag, "Writing dex file ${it.name}")
|
||||
result.dexFiles.forEach { Logging.log += "Writing dex file ${it.name}\n"
|
||||
fs.addEntryCompressData(ZipEntry.createWithName(it.name), it.stream.readBytes())}
|
||||
|
||||
|
||||
Logging.log += "Aligning apk!\n"
|
||||
result.resourceFile?.let {
|
||||
fs.copyEntriesFromFileAligned(ZipFile(it), ZipAligner::getEntryAlignment)
|
||||
}
|
||||
fs.copyEntriesFromFileAligned(ZipFile(inputFile), ZipAligner::getEntryAlignment)
|
||||
}
|
||||
|
||||
Logging.log += "Signing apk\n"
|
||||
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outputFile)
|
||||
Log.i(tag, "Successfully patched into $outputFile")
|
||||
Logging.log += "Successfully patched!\n"
|
||||
} finally {
|
||||
Log.d(tag, "Deleting workdir")
|
||||
// workdir.deleteRecursively()
|
||||
workdir.deleteRecursively()
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -187,4 +208,8 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
||||
}
|
||||
return false
|
||||
}
|
||||
private fun createWorkDir(): File {
|
||||
return applicationContext.filesDir.resolve("tmp-${System.currentTimeMillis()}")
|
||||
.also { it.mkdirs() }
|
||||
}
|
||||
}
|
||||
|
@ -10,14 +10,14 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
||||
import app.revanced.manager.ui.viewmodel.PatcherViewModel
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import app.revanced.patcher.annotation.Package
|
||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@Composable
|
||||
fun PatchCompatibilityDialog(
|
||||
patchClass: PatchClass, pvm: PatcherViewModel = getViewModel(), onClose: () -> Unit
|
||||
patchClass: PatchClass, pvm: PatcherScreenViewModel = getViewModel(), onClose: () -> Unit
|
||||
) {
|
||||
val patch = patchClass.patch
|
||||
val packageName = pvm.getSelectedPackageInfo()?.packageName
|
||||
|
@ -21,6 +21,9 @@ sealed interface AppDestination : Destination {
|
||||
|
||||
@Parcelize
|
||||
object PatchSelector : AppDestination
|
||||
|
||||
@Parcelize
|
||||
object Patcher : AppDestination
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
|
@ -69,7 +69,8 @@ fun MainDashboardScreen(navigator: BackstackNavigator<AppDestination>) {
|
||||
DashboardDestination.DASHBOARD -> DashboardScreen()
|
||||
DashboardDestination.PATCHER -> PatcherScreen(
|
||||
onClickAppSelector = { navigator.push(AppDestination.AppSelector) },
|
||||
onClickPatchSelector = { navigator.push(AppDestination.PatchSelector) }
|
||||
onClickPatchSelector = { navigator.push(AppDestination.PatchSelector) },
|
||||
onClickPatch = { navigator.push(AppDestination.Patcher) },
|
||||
)
|
||||
DashboardDestination.SETTINGS -> SettingsScreen()
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.revanced.manager.ui.viewmodel.PatcherViewModel
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -24,7 +24,7 @@ import org.koin.androidx.compose.getViewModel
|
||||
fun NewPatcherScreen(
|
||||
onClickAppSelector: () -> Unit,
|
||||
onClickPatchSelector: () -> Unit,
|
||||
viewModel: PatcherViewModel = getViewModel()
|
||||
viewModel: PatcherScreenViewModel = getViewModel()
|
||||
) {
|
||||
var validBundle = false
|
||||
Column(
|
||||
|
@ -4,14 +4,12 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Build
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.Variables.patches
|
||||
@ -20,7 +18,7 @@ import app.revanced.manager.Variables.selectedPatches
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.manager.ui.component.FloatingActionButton
|
||||
import app.revanced.manager.ui.component.SplitAPKDialog
|
||||
import app.revanced.manager.ui.viewmodel.PatcherViewModel
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -28,7 +26,8 @@ import org.koin.androidx.compose.getViewModel
|
||||
fun PatcherScreen(
|
||||
onClickAppSelector: () -> Unit,
|
||||
onClickPatchSelector: () -> Unit,
|
||||
viewModel: PatcherViewModel = getViewModel()
|
||||
onClickPatch: () -> Unit,
|
||||
viewModel: PatcherScreenViewModel = getViewModel()
|
||||
) {
|
||||
val selectedAmount = selectedPatches.size
|
||||
val selectedAppPackage by selectedAppPackage
|
||||
@ -39,9 +38,7 @@ fun PatcherScreen(
|
||||
Scaffold(floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
enabled = hasAppSelected && viewModel.anyPatchSelected(),
|
||||
onClick = {
|
||||
if (viewModel.checkSplitApk()) { showDialog = true } else viewModel.startPatcher()
|
||||
},
|
||||
onClick = { if (viewModel.checkSplitApk()) { showDialog = true } else onClickPatch()},
|
||||
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
|
||||
text = { Text(text = "Patch") }
|
||||
)
|
||||
@ -53,7 +50,7 @@ fun PatcherScreen(
|
||||
.padding(16.dp),
|
||||
) {
|
||||
if (showDialog)
|
||||
SplitAPKDialog(onDismiss = { showDialog = false }, onConfirm = { viewModel.startPatcher() })
|
||||
SplitAPKDialog(onDismiss = { showDialog = false }, onConfirm = onClickPatch)
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
|
@ -0,0 +1,74 @@
|
||||
package app.revanced.manager.ui.screen
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.revanced.manager.ui.navigation.AppDestination
|
||||
import app.revanced.manager.ui.viewmodel.Logging
|
||||
import app.revanced.manager.ui.viewmodel.PatchingScreenViewModel
|
||||
import com.xinto.taxi.BackstackNavigator
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun PatchingScreen(
|
||||
navigator: BackstackNavigator<AppDestination>,
|
||||
vm: PatchingScreenViewModel = getViewModel()
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
Row {
|
||||
IconButton(onClick = navigator::pop) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.2f)
|
||||
.padding(paddingValues),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
|
||||
if (vm.patchingInProgress) {
|
||||
CircularProgressIndicator(modifier = Modifier.padding(vertical = 16.dp).size(30.dp))
|
||||
Text(text = "Patching...", fontSize = 30.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(20.dp)
|
||||
) {
|
||||
Card {
|
||||
Text(
|
||||
text = Logging.log,
|
||||
modifier = Modifier.padding(horizontal = 20.dp, vertical = 10.dp).fillMaxSize(),
|
||||
fontSize = 20.sp,
|
||||
lineHeight = 35.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ import app.revanced.manager.ui.component.PatchCompatibilityDialog
|
||||
import app.revanced.manager.ui.navigation.AppDestination
|
||||
import app.revanced.manager.ui.theme.Typography
|
||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
||||
import app.revanced.manager.ui.viewmodel.PatcherViewModel
|
||||
import app.revanced.manager.ui.viewmodel.PatcherScreenViewModel
|
||||
import app.revanced.patcher.extensions.PatchExtensions.description
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.extensions.PatchExtensions.version
|
||||
@ -34,7 +34,7 @@ import org.koin.androidx.compose.getViewModel
|
||||
@Composable
|
||||
fun PatchesSelectorSubscreen(
|
||||
navigator: BackstackNavigator<AppDestination>,
|
||||
pvm: PatcherViewModel = getViewModel(),
|
||||
pvm: PatcherScreenViewModel = getViewModel(),
|
||||
) {
|
||||
val patches = rememberSaveable { pvm.getFilteredPatchesAndCheckOptions() }
|
||||
var query by mutableStateOf("")
|
||||
|
@ -6,15 +6,10 @@ import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkManager
|
||||
import app.revanced.manager.Variables.patches
|
||||
import app.revanced.manager.Variables.selectedAppPackage
|
||||
import app.revanced.manager.Variables.selectedPatches
|
||||
import app.revanced.manager.api.API
|
||||
import app.revanced.manager.patcher.worker.PatcherWorker
|
||||
import app.revanced.manager.ui.Resource
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||
@ -24,31 +19,22 @@ import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.util.patch.impl.DexPatchBundle
|
||||
import dalvik.system.DexClassLoader
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.io.File
|
||||
|
||||
class PatcherViewModel(private val app: Application, private val api: API) : ViewModel() {
|
||||
private val workdir = createWorkDir()
|
||||
class PatcherScreenViewModel(private val app: Application, private val api: API) : ViewModel() {
|
||||
private lateinit var patchBundleFile: String
|
||||
private val tag = "ReVanced Manager"
|
||||
|
||||
init {
|
||||
runBlocking {
|
||||
viewModelScope.launch {
|
||||
loadPatches()
|
||||
downloadIntegrations()
|
||||
}
|
||||
}
|
||||
|
||||
fun selectPatch(patchId: String, state: Boolean) {
|
||||
if (state) selectedPatches.add(patchId)
|
||||
else selectedPatches.remove(patchId)
|
||||
}
|
||||
|
||||
private suspend fun downloadIntegrations() {
|
||||
api.downloadIntegrations(workdir).renameTo(File(workdir,"integrations.apk"))
|
||||
}
|
||||
|
||||
fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) {
|
||||
patchList.forEach { patch ->
|
||||
val patchId = patch.patch.patchName
|
||||
@ -85,6 +71,38 @@ class PatcherViewModel(private val app: Application, private val api: API) : Vie
|
||||
)
|
||||
else null
|
||||
|
||||
fun checkSplitApk(): Boolean {
|
||||
if (getSelectedPackageInfo()!!.applicationInfo!!.metaData!!.getBoolean("com.android.vending.splits.required", false)) {
|
||||
Log.d(tag, "APK is split.")
|
||||
return true
|
||||
}
|
||||
Log.d(tag, "APK is not split.")
|
||||
return false
|
||||
}
|
||||
|
||||
private fun loadPatches() = viewModelScope.launch {
|
||||
try {
|
||||
val file = api.downloadPatchBundle(app.filesDir)
|
||||
patchBundleFile = file.absolutePath
|
||||
loadPatches0(file.absolutePath)
|
||||
} catch (e: Exception) {
|
||||
Log.e("ReVancedManager", "An error occurred while loading patches", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPatches0(path: String) {
|
||||
val patchClasses = DexPatchBundle(
|
||||
path, DexClassLoader(
|
||||
path,
|
||||
app.codeCacheDir.absolutePath,
|
||||
null,
|
||||
javaClass.classLoader
|
||||
)
|
||||
).loadPatches()
|
||||
patches.value = Resource.Success(patchClasses)
|
||||
Log.d("ReVanced Manager", "Finished loading patches")
|
||||
}
|
||||
|
||||
fun getFilteredPatchesAndCheckOptions(): List<PatchClass> {
|
||||
return buildList {
|
||||
val selected = getSelectedPackageInfo() ?: return@buildList
|
||||
@ -108,61 +126,6 @@ class PatcherViewModel(private val app: Application, private val api: API) : Vie
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkSplitApk(): Boolean {
|
||||
if (getSelectedPackageInfo()!!.applicationInfo!!.metaData!!.getBoolean("com.android.vending.splits.required", false)) {
|
||||
Log.d(tag, "APK is split.")
|
||||
return true
|
||||
}
|
||||
Log.d(tag, "APK is not split.")
|
||||
return false
|
||||
}
|
||||
|
||||
private fun loadPatches() = viewModelScope.launch {
|
||||
try {
|
||||
val file = api.downloadPatchBundle(app.filesDir)
|
||||
patchBundleFile = file.absolutePath
|
||||
loadPatches0(file.absolutePath)
|
||||
} catch (e: Exception) {
|
||||
Log.e("ReVancedManager", "An error occurred while loading patches", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun loadPatches0(path: String) {
|
||||
val patchClasses = DexPatchBundle(
|
||||
path, DexClassLoader(
|
||||
path,
|
||||
app.codeCacheDir.absolutePath,
|
||||
null,
|
||||
javaClass.classLoader
|
||||
)
|
||||
).loadPatches()
|
||||
patches.value = Resource.Success(patchClasses)
|
||||
Log.d("ReVanced Manager", "Finished loading patches")
|
||||
}
|
||||
|
||||
fun startPatcher() {
|
||||
WorkManager
|
||||
.getInstance(app)
|
||||
.enqueueUniqueWork(
|
||||
"patching",
|
||||
ExistingWorkPolicy.KEEP,
|
||||
OneTimeWorkRequest.Builder(PatcherWorker::class.java)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.setInputData(
|
||||
androidx.work.Data.Builder()
|
||||
.putString("workdir", workdir.toString())
|
||||
.putString("input",
|
||||
getSelectedPackageInfo()?.applicationInfo?.publicSourceDir
|
||||
)
|
||||
.build()).build()
|
||||
)
|
||||
}
|
||||
private fun createWorkDir(): File {
|
||||
return app.filesDir.resolve("tmp-${System.currentTimeMillis()}")
|
||||
.also { it.mkdirs() }
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
@ -0,0 +1,46 @@
|
||||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkManager
|
||||
import app.revanced.manager.patcher.worker.PatcherWorker
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PatchingScreenViewModel(val app: Application) : ViewModel() {
|
||||
|
||||
var patchingInProgress = true
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
startPatcher()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPatcher() {
|
||||
WorkManager
|
||||
.getInstance(app)
|
||||
.enqueueUniqueWork(
|
||||
"patching",
|
||||
ExistingWorkPolicy.KEEP,
|
||||
OneTimeWorkRequest.Builder(PatcherWorker::class.java)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.setInputData(
|
||||
androidx.work.Data.Builder()
|
||||
.build()
|
||||
).build()
|
||||
)
|
||||
patchingInProgress = false
|
||||
}
|
||||
}
|
||||
|
||||
object Logging {
|
||||
var log by mutableStateOf("")
|
||||
}
|
@ -5,4 +5,5 @@ const val ghOrganization = "https://github.com/$team"
|
||||
const val ghPatches = "$team/revanced-patches"
|
||||
const val ghPatcher = "$team/revanced-patcher"
|
||||
const val ghManager = "$team/revanced-manager"
|
||||
const val ghIntegrations = "$team/revanced-integrations"
|
||||
const val ghIntegrations = "$team/revanced-integrations"
|
||||
const val tag = "ReVanced Manager"
|
Loading…
x
Reference in New Issue
Block a user