move non-composable code to viewmodels

This commit is contained in:
X1nto 2022-04-21 01:08:26 +04:00
parent e7e170c264
commit e9f7e63569
10 changed files with 369 additions and 279 deletions

View file

@ -15,8 +15,6 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.platform.LocalContext
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.aliucord.manager.preferences.Prefs
@ -44,6 +42,13 @@ class MainActivity : ComponentActivity() {
sharedPreferences = getPreferences(Context.MODE_PRIVATE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
.setData("package:com.aliucord.manager".toUri())
startActivity(intent)
}
setContent {
ManagerTheme(
isBlack = Prefs.useBlack.get(),
@ -53,17 +58,6 @@ class MainActivity : ComponentActivity() {
theme == Theme.SYSTEM && isSystemInDarkTheme() || theme == Theme.DARK
}
) {
val context = LocalContext.current
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
SideEffect {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
.setData("package:com.aliucord.manager".toUri())
context.startActivity(intent)
}
}
ManagerScaffold()
}
}

View file

@ -9,27 +9,21 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.aliucord.manager.R
import com.aliucord.manager.models.github.GithubUser
import com.aliucord.manager.utils.Github
import com.aliucord.manager.utils.httpClient
import io.ktor.client.call.*
import io.ktor.client.request.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun ContributorsCard() = ElevatedCard(
fun ContributorsCard(
contributors: List<GithubUser>,
) = ElevatedCard(
modifier = Modifier.wrapContentHeight().fillMaxWidth()
) {
val contributors = remember { mutableStateListOf<GithubUser>() }
Column(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp)
@ -54,12 +48,6 @@ fun ContributorsCard() = ElevatedCard(
contentAlignment = Alignment.Center,
content = { CircularProgressIndicator() }
)
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
contributors.addAll(httpClient.get(Github.contributorsUrl).body<List<GithubUser>>().sortedByDescending { it.contributions })
}
}
}
}
}

View file

@ -13,21 +13,23 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.aliucord.manager.R
import com.aliucord.manager.ui.components.about.ContributorsCard
import com.aliucord.manager.ui.components.about.TeamCard
import com.aliucord.manager.ui.viewmodels.AboutViewModel
import com.ramcosta.composedestinations.annotation.Destination
@Destination
@Composable
fun AboutScreen() {
val uriHandler = LocalUriHandler.current
val viewModel: AboutViewModel = viewModel()
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
TeamCard()
ContributorsCard()
ContributorsCard(viewModel.contributors)
Spacer(Modifier.weight(1f, true))

View file

@ -6,9 +6,6 @@
package com.aliucord.manager.ui.screens
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
@ -18,56 +15,29 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.net.toFile
import androidx.lifecycle.viewmodel.compose.viewModel
import com.aliucord.manager.R
import com.aliucord.manager.models.github.Version
import com.aliucord.manager.preferences.Prefs
import com.aliucord.manager.ui.components.PluginsList
import com.aliucord.manager.ui.components.installer.DownloadMethod
import com.aliucord.manager.ui.components.installer.InstallerDialog
import com.aliucord.manager.ui.screens.destinations.CommitsScreenDestination
import com.aliucord.manager.ui.screens.destinations.InstallerScreenDestination
import com.aliucord.manager.utils.*
import com.aliucord.manager.ui.viewmodels.HomeViewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
@OptIn(ExperimentalMaterial3Api::class)
@Destination(start = true)
@Composable
fun HomeScreen(navigator: DestinationsNavigator) {
val packageManager = LocalContext.current.packageManager
var supportedVersion by remember { mutableStateOf<String?>(null) }
val installedVersion = remember {
try {
packageManager.getPackageInfo(Prefs.packageName.get(), 0).versionName
} catch (th: Throwable) {
"-"
}
}
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
val version = json.decodeFromString<Version>(httpClient.get(Github.dataUrl).bodyAsText())
supportedVersion = "${version.versionName} - " + when (version.versionCode[3].toString()) {
"0" -> "Stable"
"1" -> "Beta"
"2" -> "Alpha"
else -> throw NoWhenBranchMatchedException()
}
}
}
val viewModel: HomeViewModel = viewModel()
var showOptionsDialog by remember { mutableStateOf(false) }
@ -113,20 +83,20 @@ fun HomeScreen(navigator: DestinationsNavigator) {
append("Supported version: ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(supportedVersion ?: "?")
append(viewModel.supportedVersion)
}
append("\nInstalled version: ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append(installedVersion)
append(viewModel.installedVersion)
}
})
}
val (drawable, description) = when (installedVersion) {
val (drawable, description) = when (viewModel.installedVersion) {
"-" -> R.drawable.ic_download_24dp to R.string.install
supportedVersion -> R.drawable.ic_reinstall_24dp to R.string.reinstall
viewModel.supportedVersion -> R.drawable.ic_reinstall_24dp to R.string.reinstall
else -> R.drawable.ic_update_24dp to R.string.update
}
@ -157,7 +127,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
Text(stringResource(description))
}
if (installedVersion == "-") Button(
if (viewModel.installedVersion == "-") Button(
modifier = Modifier.wrapContentSize(),
onClick = { navigator.navigate(CommitsScreenDestination) },
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
@ -171,21 +141,16 @@ fun HomeScreen(navigator: DestinationsNavigator) {
}
}
if (installedVersion != "-") {
if (viewModel.installedVersion != "-") {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
val context = LocalContext.current
Button(
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer),
onClick = {
val packageURI = Uri.parse("package:${Prefs.packageName.get()}")
val uninstallIntent = Intent(Intent.ACTION_DELETE, packageURI)
context.startActivity(uninstallIntent)
viewModel.uninstallAliucord()
}
) {
Icon(
@ -198,12 +163,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
Button(
modifier = Modifier.weight(1f),
onClick = {
packageManager.getLaunchIntentForPackage(Prefs.packageName.get())?.let {
context.startActivity(it)
} ?: Toast.makeText(context, "Failed to launch Aliucord", Toast.LENGTH_LONG)
.show()
},
onClick = viewModel::launchAliucord,
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
) {
Icon(

View file

@ -5,217 +5,51 @@
package com.aliucord.manager.ui.screens
import android.content.Intent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider
import com.aliucord.libzip.Zip
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.preferences.Prefs
import androidx.lifecycle.viewmodel.compose.viewModel
import com.aliucord.manager.ui.screens.destinations.HomeScreenDestination
import com.aliucord.manager.utils.*
import com.aliucord.manager.ui.viewmodels.InstallViewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileNotFoundException
@OptIn(ExperimentalFoundationApi::class)
@Destination
@Composable
fun InstallerScreen(navigator: DestinationsNavigator, apk: File?) {
var log by remember { mutableStateOf("") }
val context = LocalContext.current
val viewModel: InstallViewModel = viewModel()
val navigateMain by viewModel.returnToHome.collectAsState(initial = false)
if (navigateMain) {
navigator.navigate(HomeScreenDestination)
}
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
val supportedVersion = Github.version.versionCode
viewModel.startInstallation(apk)
}
val discordApk = apk ?: run {
log += "Checking for cached APK...\n"
context.externalCacheDir!!.resolve("discord-${supportedVersion}.apk").also { file ->
val archiveInfo = context.packageManager.getPackageArchiveInfo(file.path, 0)
if (archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@also
log += "Downloading Discord APK...\n"
DownloadUtils.downloadDiscordApk(context, supportedVersion)
log += "Done\n"
}
}
val injector = context.externalCacheDir!!.resolve("Injector.dex").also { file ->
if (file.exists()) return@also
log += "Downloading injector...\n"
DownloadUtils.downloadInjector(context)
log += "Done\n"
}
val outputDir = aliucordDir.also {
if (!it.exists() && !it.mkdirs()) throw FileNotFoundException()
}
val outputApk = outputDir.resolve("Aliucord.apk")
discordApk.copyTo(outputApk, true)
log += "Repacking APK\n"
var patched = false
var manifestBytes: ByteArray?
with(Zip(outputApk.absolutePath, 6, 'r')) {
for (index in 0 until totalEntries) {
openEntryByIndex(index)
val name = entryName
closeEntry()
if (name == "classes5.dex") {
patched = true
break
}
}
val cacheDir = context.cacheDir.absolutePath
if (!patched) (1..3).forEach { i ->
openEntry("classes${if (i == 1) "" else i}.dex")
extractEntry("$cacheDir/classes${i + 1}.dex")
closeEntry()
}
openEntry("AndroidManifest.xml")
manifestBytes = readEntry()
closeEntry()
close()
}
val assetManager = context.assets
with(Zip(outputApk.absolutePath, 6, 'a')) {
if (patched) {
deleteEntry("classes.dex")
deleteEntry("classes5.dex")
deleteEntry("classes6.dex")
} else {
(1..3).forEach { i -> deleteEntry("classes${if (i == 1) "" else i}.dex") }
}
deleteEntry("AndroidManifest.xml")
listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64").forEach { arch ->
listOf("/libaliuhook.so", "/liblsplant.so", "/libc++_shared.so").forEach { file ->
deleteEntry("lib/$arch$file")
}
}
if (!patched) for (i in 2..4) {
val name = "classes$i.dex"
val cacheFile = context.cacheDir.resolve(name)
openEntry(name)
compressFile(cacheFile.absolutePath)
closeEntry()
cacheFile.delete()
}
openEntry("classes.dex")
compressFile(injector.absolutePath)
closeEntry()
writeEntry("classes5.dex", assetManager.open("aliuhook/classes.dex"))
listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64").forEach { arch ->
listOf("/libaliuhook.so", "/liblsplant.so", "/libc++_shared.so").forEach { file ->
writeEntry("lib/$arch$file", assetManager.open("aliuhook/$arch$file"))
}
}
writeEntry("classes6.dex", assetManager.open("kotlin/classes.dex"))
manifestBytes?.let { bytes ->
log += "Patching manifest\n"
val newManifestBytes = patchManifest(bytes)
openEntry("AndroidManifest.xml")
writeEntry(newManifestBytes, newManifestBytes.size.toLong())
closeEntry()
}
close()
}
if (Prefs.replaceBg.get()) {
log += "Replacing icon background\n"
val icon1Bytes = assetManager.open("icon1.png").readBytes()
val icon2Bytes = assetManager.open("icon2.png").readBytes()
// use androguard to figure out entries
// androguard arsc resources.arsc --id 0x7f0f0000 (icon1)
// androguard arsc resources.arsc --id 0x7f0f0002 and androguard arsc resources.arsc --id 0x7f0f0006 (icon2)
val icon1Entries = listOf("MbV.png", "kbF.png", "_eu.png", "EtS.png")
val icon2Entries =
listOf("_h_.png", "9MB.png", "Dy7.png", "kC0.png", "oEH.png", "RG0.png", "ud_.png", "W_3.png")
with(Zip(outputApk.absolutePath, 0, 'a')) {
icon1Entries.forEach { entry -> deleteEntry("res/$entry") }
icon2Entries.forEach { entry -> deleteEntry("res/$entry") }
icon1Entries.forEach { entry -> writeEntry("res/$entry", icon1Bytes) }
icon2Entries.forEach { entry -> writeEntry("res/$entry", icon2Bytes) }
close()
}
}
log += "Signing APK...\n"
Signer.signApk(outputApk)
log += "Signed APK\n"
val intent = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
setDataAndType(
FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", outputApk),
"application/vnd.android.package-archive"
)
}
context.startActivity(intent)
launch(Dispatchers.Main) {
navigator.navigate(HomeScreenDestination)
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
stickyHeader {
LinearProgressIndicator(
modifier = Modifier.fillParentMaxWidth()
)
}
items(viewModel.logs) { log ->
Text(
text = log,
modifier = Modifier.padding(8.dp)
)
}
}
Column(
modifier = Modifier.fillMaxWidth()
) {
Text(log)
Spacer(Modifier.weight(1f, true))
CircularProgressIndicator(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(8.dp)
.size(32.dp)
)
}
}

View file

@ -17,14 +17,15 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.aliucord.manager.R
import com.aliucord.manager.preferences.Prefs
import com.aliucord.manager.ui.components.ListItem
import com.aliucord.manager.ui.components.settings.*
import com.aliucord.manager.ui.viewmodels.SettingsViewModel
import com.ramcosta.composedestinations.annotation.Destination
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@ -34,7 +35,8 @@ fun SettingsScreen() = Column(
modifier = Modifier.verticalScroll(state = rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
val context = LocalContext.current
val viewModifier: SettingsViewModel = viewModel()
var showThemeDialog by remember { mutableStateOf(false) }
if (showThemeDialog) {
@ -133,7 +135,7 @@ fun SettingsScreen() = Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
onClick = context.cacheDir::deleteRecursively
onClick = viewModifier::clearCacheDir
) {
Text(stringResource(R.string.clear_files_cache), textAlign = TextAlign.Center)
}

View file

@ -0,0 +1,31 @@
package com.aliucord.manager.ui.viewmodels
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aliucord.manager.models.github.GithubUser
import com.aliucord.manager.utils.Github
import com.aliucord.manager.utils.httpClient
import io.ktor.client.call.*
import io.ktor.client.request.*
import kotlinx.coroutines.launch
class AboutViewModel : ViewModel() {
val contributors = mutableStateListOf<GithubUser>()
fun load() {
viewModelScope.launch {
val githubContributors = httpClient.get(Github.contributorsUrl).body<List<GithubUser>>()
.sortedByDescending {
it.contributions
}
contributors.addAll(githubContributors)
}
}
init {
load()
}
}

View file

@ -0,0 +1,67 @@
package com.aliucord.manager.ui.viewmodels
import android.app.Application
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.compose.runtime.*
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.aliucord.manager.models.github.Version
import com.aliucord.manager.preferences.Prefs
import com.aliucord.manager.utils.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
class HomeViewModel(application: Application) : AndroidViewModel(application) {
private val packageManager = getApplication<Application>().packageManager
var supportedVersion by mutableStateOf("")
private set
var installedVersion by mutableStateOf("-")
private set
fun load() {
viewModelScope.launch(Dispatchers.IO) {
val version = json.decodeFromString<Version>(httpClient.get(Github.dataUrl).bodyAsText())
supportedVersion = "${version.versionName} - " + when (version.versionCode[3]) {
'0' -> "Stable"
'1' -> "Beta"
'2' -> "Alpha"
else -> throw NoWhenBranchMatchedException()
}
installedVersion = try {
packageManager.getPackageInfo(Prefs.packageName.get(), 0).versionName
} catch (th: Throwable) {
"-"
}
}
}
fun launchAliucord() {
packageManager.getLaunchIntentForPackage(Prefs.packageName.get())?.let {
getApplication<Application>().startActivity(it)
} ?: Toast.makeText(getApplication(), "Failed to launch Aliucord", Toast.LENGTH_LONG)
.show()
}
fun uninstallAliucord() {
val packageURI = Uri.parse("package:${Prefs.packageName.get()}")
val uninstallIntent = Intent(Intent.ACTION_DELETE, packageURI).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
getApplication<Application>().startActivity(uninstallIntent)
}
init {
load()
}
}

View file

@ -0,0 +1,200 @@
package com.aliucord.manager.ui.viewmodels
import android.app.Application
import android.content.Intent
import androidx.compose.runtime.mutableStateListOf
import androidx.core.content.FileProvider
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.aliucord.libzip.Zip
import com.aliucord.manager.BuildConfig
import com.aliucord.manager.preferences.Prefs
import com.aliucord.manager.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileNotFoundException
class InstallViewModel(application: Application) : AndroidViewModel(application) {
private val _returnToHome = MutableSharedFlow<Boolean>()
val returnToHome = _returnToHome.asSharedFlow()
private var installationRunning: Boolean = false
val logs = mutableStateListOf<String>()
fun startInstallation(apk: File?) {
if (installationRunning) return
viewModelScope.launch(Dispatchers.IO) {
installationRunning = true
val supportedVersion = Github.version.versionCode
val discordApk = apk ?: run {
logs.add("Checking for cached APK...")
getApplication<Application>().externalCacheDir!!.resolve("discord-${supportedVersion}.apk").also { file ->
val archiveInfo = getApplication<Application>().packageManager.getPackageArchiveInfo(file.path, 0)
if (archiveInfo?.versionCode.toString().startsWith(supportedVersion)) return@also
logs.add("Downloading Discord APK...")
DownloadUtils.downloadDiscordApk(getApplication<Application>(), supportedVersion)
logs.add("Done")
}
}
val injector = getApplication<Application>().externalCacheDir!!.resolve("Injector.dex").also { file ->
if (file.exists()) return@also
logs.add("Downloading injector...")
DownloadUtils.downloadInjector(getApplication<Application>())
logs.add("Done")
}
val outputDir = aliucordDir.also {
if (!it.exists() && !it.mkdirs()) throw FileNotFoundException()
}
val outputApk = outputDir.resolve("Aliucord.apk")
discordApk.copyTo(outputApk, true)
logs.add("Repacking APK")
var patched = false
var manifestBytes: ByteArray?
with(Zip(outputApk.absolutePath, 6, 'r')) {
for (index in 0 until totalEntries) {
openEntryByIndex(index)
val name = entryName
closeEntry()
if (name == "classes5.dex") {
patched = true
break
}
}
val cacheDir = getApplication<Application>().cacheDir.absolutePath
if (!patched) (1..3).forEach { i ->
openEntry("classes${if (i == 1) "" else i}.dex")
extractEntry("$cacheDir/classes${i + 1}.dex")
closeEntry()
}
openEntry("AndroidManifest.xml")
manifestBytes = readEntry()
closeEntry()
close()
}
val assetManager = getApplication<Application>().assets
with(Zip(outputApk.absolutePath, 6, 'a')) {
if (patched) {
deleteEntry("classes.dex")
deleteEntry("classes5.dex")
deleteEntry("classes6.dex")
} else {
(1..3).forEach { i -> deleteEntry("classes${if (i == 1) "" else i}.dex") }
}
deleteEntry("AndroidManifest.xml")
listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64").forEach { arch ->
listOf("/libaliuhook.so", "/liblsplant.so", "/libc++_shared.so").forEach { file ->
deleteEntry("lib/$arch$file")
}
}
if (!patched) for (i in 2..4) {
val name = "classes$i.dex"
val cacheFile = getApplication<Application>().cacheDir.resolve(name)
openEntry(name)
compressFile(cacheFile.absolutePath)
closeEntry()
cacheFile.delete()
}
openEntry("classes.dex")
compressFile(injector.absolutePath)
closeEntry()
writeEntry("classes5.dex", assetManager.open("aliuhook/classes.dex"))
listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64").forEach { arch ->
listOf("/libaliuhook.so", "/liblsplant.so", "/libc++_shared.so").forEach { file ->
writeEntry("lib/$arch$file", assetManager.open("aliuhook/$arch$file"))
}
}
writeEntry("classes6.dex", assetManager.open("kotlin/classes.dex"))
manifestBytes?.let { bytes ->
logs.add("Patching manifest")
val newManifestBytes = patchManifest(bytes)
openEntry("AndroidManifest.xml")
writeEntry(newManifestBytes, newManifestBytes.size.toLong())
closeEntry()
}
close()
}
if (Prefs.replaceBg.get()) {
logs.add("Replacing icon background")
val icon1Bytes = assetManager.open("icon1.png").readBytes()
val icon2Bytes = assetManager.open("icon2.png").readBytes()
// use androguard to figure out entries
// androguard arsc resources.arsc --id 0x7f0f0000 (icon1)
// androguard arsc resources.arsc --id 0x7f0f0002 and androguard arsc resources.arsc --id 0x7f0f0006 (icon2)
val icon1Entries = listOf("MbV.png", "kbF.png", "_eu.png", "EtS.png")
val icon2Entries =
listOf("_h_.png", "9MB.png", "Dy7.png", "kC0.png", "oEH.png", "RG0.png", "ud_.png", "W_3.png")
with(Zip(outputApk.absolutePath, 0, 'a')) {
icon1Entries.forEach { entry -> deleteEntry("res/$entry") }
icon2Entries.forEach { entry -> deleteEntry("res/$entry") }
icon1Entries.forEach { entry -> writeEntry("res/$entry", icon1Bytes) }
icon2Entries.forEach { entry -> writeEntry("res/$entry", icon2Bytes) }
close()
}
}
logs.add("Signing APK...")
Signer.signApk(outputApk)
logs.add("Signed APK")
val intent = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
setDataAndType(
FileProvider.getUriForFile(getApplication<Application>(), "${BuildConfig.APPLICATION_ID}.provider", outputApk),
"application/vnd.android.package-archive"
)
}
getApplication<Application>().startActivity(intent)
_returnToHome.emit(true)
installationRunning = false
}
}
}

View file

@ -0,0 +1,12 @@
package com.aliucord.manager.ui.viewmodels
import android.app.Application
import androidx.lifecycle.AndroidViewModel
class SettingsViewModel(application: Application) : AndroidViewModel(application) {
fun clearCacheDir() {
getApplication<Application>().cacheDir.deleteRecursively()
}
}