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"
|
val koinVersion = "3.2.0"
|
||||||
implementation("io.insert-koin:koin-android:$koinVersion")
|
implementation("io.insert-koin:koin-android:$koinVersion")
|
||||||
implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
|
implementation("io.insert-koin:koin-androidx-compose:$koinVersion")
|
||||||
|
implementation("io.insert-koin:koin-androidx-workmanager:3.2.1")
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
val composeVersion = "1.3.0-alpha03"
|
val composeVersion = "1.3.0-alpha03"
|
||||||
@ -117,7 +118,7 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
|
||||||
|
|
||||||
// ReVanced
|
// ReVanced
|
||||||
implementation("app.revanced:revanced-patcher:4.2.3")
|
implementation("app.revanced:revanced-patcher:4.4.2")
|
||||||
|
|
||||||
// Coil for network image
|
// Coil for network image
|
||||||
implementation("io.coil-kt:coil-compose:2.1.0")
|
implementation("io.coil-kt:coil-compose:2.1.0")
|
||||||
@ -125,4 +126,5 @@ dependencies {
|
|||||||
// Signing & aligning
|
// Signing & aligning
|
||||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||||
implementation("com.android.tools.build:apksig:7.2.2")
|
implementation("com.android.tools.build:apksig:7.2.2")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,19 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:theme="@style/Theme.ReVancedManager"
|
android:theme="@style/Theme.ReVancedManager"
|
||||||
|
|
||||||
tools:targetApi="33">
|
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
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -15,6 +15,7 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|||||||
import app.revanced.manager.preferences.PreferencesManager
|
import app.revanced.manager.preferences.PreferencesManager
|
||||||
import app.revanced.manager.ui.navigation.AppDestination
|
import app.revanced.manager.ui.navigation.AppDestination
|
||||||
import app.revanced.manager.ui.screen.MainDashboardScreen
|
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.AppSelectorSubscreen
|
||||||
import app.revanced.manager.ui.screen.subscreens.PatchesSelectorSubscreen
|
import app.revanced.manager.ui.screen.subscreens.PatchesSelectorSubscreen
|
||||||
import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
import app.revanced.manager.ui.theme.ReVancedManagerTheme
|
||||||
@ -48,10 +49,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
) { destination ->
|
) { destination ->
|
||||||
when (destination) {
|
when (destination) {
|
||||||
is AppDestination.Dashboard -> MainDashboardScreen(navigator = navigator)
|
is AppDestination.Dashboard -> MainDashboardScreen(navigator = navigator)
|
||||||
is AppDestination.AppSelector -> AppSelectorSubscreen(
|
is AppDestination.AppSelector -> AppSelectorSubscreen(navigator = navigator)
|
||||||
navigator = navigator
|
|
||||||
)
|
|
||||||
is AppDestination.PatchSelector -> PatchesSelectorSubscreen(navigator = navigator)
|
is AppDestination.PatchSelector -> PatchesSelectorSubscreen(navigator = navigator)
|
||||||
|
is AppDestination.Patcher -> PatchingScreen(navigator = navigator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package app.revanced.manager
|
package app.revanced.manager
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import app.revanced.manager.di.httpModule
|
import app.revanced.manager.di.*
|
||||||
import app.revanced.manager.di.preferencesModule
|
|
||||||
import app.revanced.manager.di.repositoryModule
|
|
||||||
import app.revanced.manager.di.viewModelModule
|
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.androidx.workmanager.koin.workManagerFactory
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
|
|
||||||
class ManagerApplication : Application() {
|
class ManagerApplication : Application() {
|
||||||
@ -14,7 +12,8 @@ class ManagerApplication : Application() {
|
|||||||
|
|
||||||
startKoin {
|
startKoin {
|
||||||
androidContext(this@ManagerApplication)
|
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) {
|
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 release = repository.getLatestRelease(repo)
|
||||||
val asset = release.assets.findAsset(file) ?: throw MissingAssetException()
|
val asset = release.assets.findAsset(file) ?: throw MissingAssetException()
|
||||||
return PatchesAsset(release, asset)
|
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)
|
throw Exception("Failed to download patch bundle", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadIntegrations(workdir: File): File {
|
suspend fun downloadIntegrations(workdir: File): File {
|
||||||
return try {
|
return try {
|
||||||
val (_, out) = downloadAsset(
|
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,
|
workdir: File,
|
||||||
patchesAsset: PatchesAsset
|
patchesAsset: PatchesAsset
|
||||||
): Pair<PatchesAsset, File> {
|
): Pair<PatchesAsset, File> {
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
package app.revanced.manager.di
|
package app.revanced.manager.di
|
||||||
|
|
||||||
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel
|
import app.revanced.manager.ui.viewmodel.*
|
||||||
import app.revanced.manager.ui.viewmodel.DashboardViewModel
|
|
||||||
import app.revanced.manager.ui.viewmodel.PatcherViewModel
|
|
||||||
import app.revanced.manager.ui.viewmodel.SettingsViewModel
|
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val viewModelModule = module {
|
val viewModelModule = module {
|
||||||
viewModelOf(::SettingsViewModel)
|
viewModelOf(::SettingsViewModel)
|
||||||
viewModelOf(::DashboardViewModel)
|
viewModelOf(::DashboardViewModel)
|
||||||
viewModelOf(::PatcherViewModel)
|
viewModelOf(::PatcherScreenViewModel)
|
||||||
viewModelOf(::AppSelectorViewModel)
|
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 androidx.work.WorkerParameters
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.Variables.patches
|
import app.revanced.manager.Variables.patches
|
||||||
|
import app.revanced.manager.Variables.selectedAppPackage
|
||||||
import app.revanced.manager.Variables.selectedPatches
|
import app.revanced.manager.Variables.selectedPatches
|
||||||
|
import app.revanced.manager.api.API
|
||||||
import app.revanced.manager.patcher.aapt.Aapt
|
import app.revanced.manager.patcher.aapt.Aapt
|
||||||
import app.revanced.manager.patcher.aligning.ZipAligner
|
import app.revanced.manager.patcher.aligning.ZipAligner
|
||||||
import app.revanced.manager.patcher.aligning.zip.ZipFile
|
import app.revanced.manager.patcher.aligning.zip.ZipFile
|
||||||
import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry
|
import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry
|
||||||
import app.revanced.manager.patcher.signing.Signer
|
import app.revanced.manager.patcher.signing.Signer
|
||||||
import app.revanced.manager.ui.Resource
|
import app.revanced.manager.ui.Resource
|
||||||
|
import app.revanced.manager.ui.viewmodel.Logging
|
||||||
import app.revanced.patcher.Patcher
|
import app.revanced.patcher.Patcher
|
||||||
import app.revanced.patcher.PatcherOptions
|
import app.revanced.patcher.PatcherOptions
|
||||||
import app.revanced.patcher.data.Data
|
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.logging.Logger
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
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.io.File
|
||||||
import java.nio.file.Files
|
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"
|
val tag = "ReVanced Manager"
|
||||||
private val workdir = File(inputData.getString("workdir")!!)
|
private val workdir = createWorkDir()
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
|
|
||||||
if (runAttemptCount > 0) {
|
if (runAttemptCount > 0) {
|
||||||
return Result.failure(
|
return Result.failure(
|
||||||
androidx.work.Data.Builder()
|
androidx.work.Data.Builder()
|
||||||
@ -77,32 +84,41 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runPatcher(
|
private suspend fun runPatcher(
|
||||||
workdir: File
|
workdir: File
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val aaptPath = Aapt.binary(applicationContext).absolutePath
|
val aaptPath = Aapt.binary(applicationContext).absolutePath
|
||||||
val frameworkPath =
|
val frameworkPath =
|
||||||
applicationContext.filesDir.resolve("framework").also { it.mkdirs() }.absolutePath
|
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)
|
val patches = findPatchesByIds(selectedPatches)
|
||||||
if (patches.isEmpty()) return true
|
if (patches.isEmpty()) return true
|
||||||
|
|
||||||
|
|
||||||
Log.d(tag, "Creating directories")
|
Logging.log += "Creating directories\n"
|
||||||
|
val inputFile = File(applicationContext.filesDir, "input.apk")
|
||||||
File(inputData.getString("input")!!).copyTo(
|
|
||||||
applicationContext.filesDir.resolve("base.apk"),
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
val inputFile = File(applicationContext.filesDir, "base.apk")
|
|
||||||
val patchedFile = File(workdir, "patched.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 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 {
|
try {
|
||||||
Log.d(tag, "Creating patcher")
|
Logging.log += "Decoding resources\n"
|
||||||
val patcher = Patcher( // start patcher
|
val patcher = Patcher( // start patcher
|
||||||
PatcherOptions(
|
PatcherOptions(
|
||||||
inputFile,
|
inputFile,
|
||||||
@ -134,37 +150,42 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
|||||||
Log.d(tag, "Adding ${patches.size} patch(es)")
|
Log.d(tag, "Adding ${patches.size} patch(es)")
|
||||||
patcher.addPatches(patches)
|
patcher.addPatches(patches)
|
||||||
|
|
||||||
|
Logging.log += "Merging integrations\n"
|
||||||
patcher.addFiles(listOf(integrations)) {}
|
patcher.addFiles(listOf(integrations)) {}
|
||||||
|
|
||||||
patcher.applyPatches().forEach { (patch, result) ->
|
patcher.applyPatches().forEach { (patch, result) ->
|
||||||
|
Logging.log += "Applying $patch\n"
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
Log.i(tag, "[success] $patch")
|
Logging.log += "$patch has been applied successfully\n"
|
||||||
return@forEach
|
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
|
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
|
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())}
|
fs.addEntryCompressData(ZipEntry.createWithName(it.name), it.stream.readBytes())}
|
||||||
|
|
||||||
|
Logging.log += "Aligning apk!\n"
|
||||||
result.resourceFile?.let {
|
result.resourceFile?.let {
|
||||||
fs.copyEntriesFromFileAligned(ZipFile(it), ZipAligner::getEntryAlignment)
|
fs.copyEntriesFromFileAligned(ZipFile(it), ZipAligner::getEntryAlignment)
|
||||||
}
|
}
|
||||||
fs.copyEntriesFromFileAligned(ZipFile(inputFile), ZipAligner::getEntryAlignment)
|
fs.copyEntriesFromFileAligned(ZipFile(inputFile), ZipAligner::getEntryAlignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logging.log += "Signing apk\n"
|
||||||
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outputFile)
|
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outputFile)
|
||||||
Log.i(tag, "Successfully patched into $outputFile")
|
Logging.log += "Successfully patched!\n"
|
||||||
} finally {
|
} finally {
|
||||||
Log.d(tag, "Deleting workdir")
|
Log.d(tag, "Deleting workdir")
|
||||||
// workdir.deleteRecursively()
|
workdir.deleteRecursively()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -187,4 +208,8 @@ class PatcherWorker(context: Context, parameters: WorkerParameters) :
|
|||||||
}
|
}
|
||||||
return false
|
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 androidx.compose.ui.unit.dp
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
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.annotation.Package
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||||
import org.koin.androidx.compose.getViewModel
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PatchCompatibilityDialog(
|
fun PatchCompatibilityDialog(
|
||||||
patchClass: PatchClass, pvm: PatcherViewModel = getViewModel(), onClose: () -> Unit
|
patchClass: PatchClass, pvm: PatcherScreenViewModel = getViewModel(), onClose: () -> Unit
|
||||||
) {
|
) {
|
||||||
val patch = patchClass.patch
|
val patch = patchClass.patch
|
||||||
val packageName = pvm.getSelectedPackageInfo()?.packageName
|
val packageName = pvm.getSelectedPackageInfo()?.packageName
|
||||||
|
@ -21,6 +21,9 @@ sealed interface AppDestination : Destination {
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object PatchSelector : AppDestination
|
object PatchSelector : AppDestination
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
object Patcher : AppDestination
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
@ -69,7 +69,8 @@ fun MainDashboardScreen(navigator: BackstackNavigator<AppDestination>) {
|
|||||||
DashboardDestination.DASHBOARD -> DashboardScreen()
|
DashboardDestination.DASHBOARD -> DashboardScreen()
|
||||||
DashboardDestination.PATCHER -> PatcherScreen(
|
DashboardDestination.PATCHER -> PatcherScreen(
|
||||||
onClickAppSelector = { navigator.push(AppDestination.AppSelector) },
|
onClickAppSelector = { navigator.push(AppDestination.AppSelector) },
|
||||||
onClickPatchSelector = { navigator.push(AppDestination.PatchSelector) }
|
onClickPatchSelector = { navigator.push(AppDestination.PatchSelector) },
|
||||||
|
onClickPatch = { navigator.push(AppDestination.Patcher) },
|
||||||
)
|
)
|
||||||
DashboardDestination.SETTINGS -> SettingsScreen()
|
DashboardDestination.SETTINGS -> SettingsScreen()
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
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
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@ -24,7 +24,7 @@ import org.koin.androidx.compose.getViewModel
|
|||||||
fun NewPatcherScreen(
|
fun NewPatcherScreen(
|
||||||
onClickAppSelector: () -> Unit,
|
onClickAppSelector: () -> Unit,
|
||||||
onClickPatchSelector: () -> Unit,
|
onClickPatchSelector: () -> Unit,
|
||||||
viewModel: PatcherViewModel = getViewModel()
|
viewModel: PatcherScreenViewModel = getViewModel()
|
||||||
) {
|
) {
|
||||||
var validBundle = false
|
var validBundle = false
|
||||||
Column(
|
Column(
|
||||||
|
@ -4,14 +4,12 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Build
|
import androidx.compose.material.icons.filled.Build
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.Variables.patches
|
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.Resource
|
||||||
import app.revanced.manager.ui.component.FloatingActionButton
|
import app.revanced.manager.ui.component.FloatingActionButton
|
||||||
import app.revanced.manager.ui.component.SplitAPKDialog
|
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
|
import org.koin.androidx.compose.getViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@ -28,7 +26,8 @@ import org.koin.androidx.compose.getViewModel
|
|||||||
fun PatcherScreen(
|
fun PatcherScreen(
|
||||||
onClickAppSelector: () -> Unit,
|
onClickAppSelector: () -> Unit,
|
||||||
onClickPatchSelector: () -> Unit,
|
onClickPatchSelector: () -> Unit,
|
||||||
viewModel: PatcherViewModel = getViewModel()
|
onClickPatch: () -> Unit,
|
||||||
|
viewModel: PatcherScreenViewModel = getViewModel()
|
||||||
) {
|
) {
|
||||||
val selectedAmount = selectedPatches.size
|
val selectedAmount = selectedPatches.size
|
||||||
val selectedAppPackage by selectedAppPackage
|
val selectedAppPackage by selectedAppPackage
|
||||||
@ -39,9 +38,7 @@ fun PatcherScreen(
|
|||||||
Scaffold(floatingActionButton = {
|
Scaffold(floatingActionButton = {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
enabled = hasAppSelected && viewModel.anyPatchSelected(),
|
enabled = hasAppSelected && viewModel.anyPatchSelected(),
|
||||||
onClick = {
|
onClick = { if (viewModel.checkSplitApk()) { showDialog = true } else onClickPatch()},
|
||||||
if (viewModel.checkSplitApk()) { showDialog = true } else viewModel.startPatcher()
|
|
||||||
},
|
|
||||||
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
|
icon = { Icon(Icons.Default.Build, contentDescription = "Patch") },
|
||||||
text = { Text(text = "Patch") }
|
text = { Text(text = "Patch") }
|
||||||
)
|
)
|
||||||
@ -53,7 +50,7 @@ fun PatcherScreen(
|
|||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
) {
|
) {
|
||||||
if (showDialog)
|
if (showDialog)
|
||||||
SplitAPKDialog(onDismiss = { showDialog = false }, onConfirm = { viewModel.startPatcher() })
|
SplitAPKDialog(onDismiss = { showDialog = false }, onConfirm = onClickPatch)
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(4.dp)
|
.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.navigation.AppDestination
|
||||||
import app.revanced.manager.ui.theme.Typography
|
import app.revanced.manager.ui.theme.Typography
|
||||||
import app.revanced.manager.ui.viewmodel.PatchClass
|
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.description
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.version
|
import app.revanced.patcher.extensions.PatchExtensions.version
|
||||||
@ -34,7 +34,7 @@ import org.koin.androidx.compose.getViewModel
|
|||||||
@Composable
|
@Composable
|
||||||
fun PatchesSelectorSubscreen(
|
fun PatchesSelectorSubscreen(
|
||||||
navigator: BackstackNavigator<AppDestination>,
|
navigator: BackstackNavigator<AppDestination>,
|
||||||
pvm: PatcherViewModel = getViewModel(),
|
pvm: PatcherScreenViewModel = getViewModel(),
|
||||||
) {
|
) {
|
||||||
val patches = rememberSaveable { pvm.getFilteredPatchesAndCheckOptions() }
|
val patches = rememberSaveable { pvm.getFilteredPatchesAndCheckOptions() }
|
||||||
var query by mutableStateOf("")
|
var query by mutableStateOf("")
|
||||||
|
@ -6,15 +6,10 @@ import android.os.Parcelable
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
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.patches
|
||||||
import app.revanced.manager.Variables.selectedAppPackage
|
import app.revanced.manager.Variables.selectedAppPackage
|
||||||
import app.revanced.manager.Variables.selectedPatches
|
import app.revanced.manager.Variables.selectedPatches
|
||||||
import app.revanced.manager.api.API
|
import app.revanced.manager.api.API
|
||||||
import app.revanced.manager.patcher.worker.PatcherWorker
|
|
||||||
import app.revanced.manager.ui.Resource
|
import app.revanced.manager.ui.Resource
|
||||||
import app.revanced.patcher.data.Data
|
import app.revanced.patcher.data.Data
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
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 app.revanced.patcher.util.patch.impl.DexPatchBundle
|
||||||
import dalvik.system.DexClassLoader
|
import dalvik.system.DexClassLoader
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class PatcherViewModel(private val app: Application, private val api: API) : ViewModel() {
|
class PatcherScreenViewModel(private val app: Application, private val api: API) : ViewModel() {
|
||||||
private val workdir = createWorkDir()
|
|
||||||
private lateinit var patchBundleFile: String
|
private lateinit var patchBundleFile: String
|
||||||
private val tag = "ReVanced Manager"
|
private val tag = "ReVanced Manager"
|
||||||
|
|
||||||
init {
|
init {
|
||||||
runBlocking {
|
viewModelScope.launch {
|
||||||
loadPatches()
|
loadPatches()
|
||||||
downloadIntegrations()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectPatch(patchId: String, state: Boolean) {
|
fun selectPatch(patchId: String, state: Boolean) {
|
||||||
if (state) selectedPatches.add(patchId)
|
if (state) selectedPatches.add(patchId)
|
||||||
else selectedPatches.remove(patchId)
|
else selectedPatches.remove(patchId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun downloadIntegrations() {
|
|
||||||
api.downloadIntegrations(workdir).renameTo(File(workdir,"integrations.apk"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) {
|
fun selectAllPatches(patchList: List<PatchClass>, selectAll: Boolean) {
|
||||||
patchList.forEach { patch ->
|
patchList.forEach { patch ->
|
||||||
val patchId = patch.patch.patchName
|
val patchId = patch.patch.patchName
|
||||||
@ -85,6 +71,38 @@ class PatcherViewModel(private val app: Application, private val api: API) : Vie
|
|||||||
)
|
)
|
||||||
else null
|
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> {
|
fun getFilteredPatchesAndCheckOptions(): List<PatchClass> {
|
||||||
return buildList {
|
return buildList {
|
||||||
val selected = getSelectedPackageInfo() ?: 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
|
@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 ghPatches = "$team/revanced-patches"
|
||||||
const val ghPatcher = "$team/revanced-patcher"
|
const val ghPatcher = "$team/revanced-patcher"
|
||||||
const val ghManager = "$team/revanced-manager"
|
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