feat: remove RN install
This commit is contained in:
parent
4617e5ec56
commit
4953947e1d
8 changed files with 108 additions and 461 deletions
|
|
@ -25,9 +25,6 @@ class DownloadManager(
|
|||
suspend fun downloadDiscordApk(version: String, out: File): File =
|
||||
download("$BACKEND_HOST/download/discord?v=$version", out)
|
||||
|
||||
suspend fun downloadSplit(version: String, split: String, out: File): File =
|
||||
download("$BACKEND_HOST/download/discord?v=$version&split=$split", out)
|
||||
|
||||
// Aliucord Kotlin downloads
|
||||
suspend fun downloadKtInjector(out: File): File =
|
||||
download(AliucordGithubService.KT_INJECTOR_URL, out)
|
||||
|
|
@ -38,9 +35,6 @@ class DownloadManager(
|
|||
suspend fun downloadKotlinDex(out: File): File =
|
||||
download(AliucordGithubService.KOTLIN_DEX_URL, out)
|
||||
|
||||
suspend fun downloadBootstrap(out: File): File =
|
||||
download(AliucordGithubService.BOOTSTRAP_URL, out)
|
||||
|
||||
suspend fun download(url: String, out: File): File {
|
||||
out.parentFile?.mkdirs()
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,5 @@ class GithubRepository(
|
|||
|
||||
suspend fun getDataJson() = service.getDataJson()
|
||||
|
||||
suspend fun getHermesRelease() = service.getLatestHermesRelease()
|
||||
suspend fun getAliucordNativeRelease() = service.getLatestAliucordNativeRelease()
|
||||
suspend fun getManagerReleases() = service.getManagerReleases()
|
||||
suspend fun getLatestBootstrapCommit() = service.getLatestBootstrapCommit()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package com.aliucord.manager.network.service
|
||||
|
||||
import com.aliucord.manager.network.dto.Commit
|
||||
import com.aliucord.manager.network.dto.Version
|
||||
import com.aliucord.manager.network.utils.ApiResponse
|
||||
import com.aliucord.manager.network.utils.transform
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.url
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
|
@ -20,31 +18,15 @@ class AliucordGithubService(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun getLatestBootstrapCommit(): ApiResponse<String> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
http.request<List<Commit>> {
|
||||
url("https://api.github.com/repos/$ORG/$RN_REPO/commits?sha=builds&path=bootstrap.js&per_page=1")
|
||||
}.transform {
|
||||
it.single().sha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCommits(page: Int = 0) = github.getCommits(ORG, MAIN_REPO, page)
|
||||
suspend fun getLatestHermesRelease() = github.getLatestRelease(ORG, HERMES_REPO)
|
||||
suspend fun getLatestAliucordNativeRelease() = github.getLatestRelease(ORG, ALIUNATIVE_REPO)
|
||||
suspend fun getManagerReleases() = github.getReleases(ORG, MANAGER_REPO)
|
||||
suspend fun getContributors() = github.getContributors(ORG, MAIN_REPO)
|
||||
|
||||
companion object {
|
||||
private const val ORG = "Aliucord"
|
||||
private const val MAIN_REPO = "Aliucord"
|
||||
private const val RN_REPO = "AliucordRN"
|
||||
private const val HERMES_REPO = "Hermes"
|
||||
private const val ALIUNATIVE_REPO = "AliucordNative"
|
||||
private const val MANAGER_REPO = "AliucordManager"
|
||||
private const val MANAGER_REPO = "Manager"
|
||||
|
||||
const val BOOTSTRAP_URL = "https://raw.githubusercontent.com/$ORG/$RN_REPO/builds/bootstrap.js"
|
||||
const val KT_INJECTOR_URL = "https://raw.githubusercontent.com/$ORG/$MAIN_REPO/builds/Injector.dex"
|
||||
const val KOTLIN_DEX_URL = "https://raw.githubusercontent.com/$ORG/$MAIN_REPO/main/installer/android/app/src/main/assets/kotlin/classes.dex"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,25 +5,11 @@
|
|||
|
||||
package com.aliucord.manager.ui.dialog
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.screen.InstallData
|
||||
|
||||
enum class DownloadMethod { DOWNLOAD }
|
||||
enum class DiscordType { KOTLIN, REACT_NATIVE }
|
||||
|
||||
@Composable
|
||||
fun InstallerDialog(
|
||||
|
|
@ -31,104 +17,104 @@ fun InstallerDialog(
|
|||
onConfirm: (InstallData) -> Unit
|
||||
) {
|
||||
val downloadMethod by rememberSaveable { mutableStateOf(DownloadMethod.DOWNLOAD) }
|
||||
var discordType by rememberSaveable { mutableStateOf<DiscordType?>(null) }
|
||||
|
||||
fun triggerConfirm() {
|
||||
onDismiss()
|
||||
onConfirm(
|
||||
InstallData(
|
||||
downloadMethod,
|
||||
discordType ?: return
|
||||
)
|
||||
InstallData(downloadMethod)
|
||||
)
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
Surface(
|
||||
color = AlertDialogDefaults.containerColor,
|
||||
tonalElevation = AlertDialogDefaults.TonalElevation,
|
||||
shape = AlertDialogDefaults.shape,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.sizeIn(minWidth = 380.dp)
|
||||
.padding(24.dp)
|
||||
) {
|
||||
// Icon
|
||||
Box(
|
||||
Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 16.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Download,
|
||||
tint = AlertDialogDefaults.iconContentColor,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
SideEffect(::triggerConfirm)
|
||||
|
||||
// Title
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 30.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.selector_discord_type),
|
||||
color = AlertDialogDefaults.titleContentColor,
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
}
|
||||
// TODO: local install option
|
||||
|
||||
// Body
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 30.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.selector_discord_type_body),
|
||||
textAlign = TextAlign.Center,
|
||||
color = AlertDialogDefaults.textContentColor,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
|
||||
// Buttons
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.clip(MaterialTheme.shapes.large)
|
||||
) {
|
||||
ProvideTextStyle(MaterialTheme.typography.labelMedium) {
|
||||
Button(
|
||||
shape = MaterialTheme.shapes.extraSmall,
|
||||
enabled = BuildConfig.RN_ENABLED,
|
||||
onClick = {
|
||||
discordType = DiscordType.REACT_NATIVE
|
||||
triggerConfirm()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.selector_discord_type_kotlin))
|
||||
}
|
||||
Button(
|
||||
shape = MaterialTheme.shapes.extraSmall,
|
||||
onClick = {
|
||||
discordType = DiscordType.KOTLIN
|
||||
triggerConfirm()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.selector_discord_type_old))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Dialog(
|
||||
// onDismissRequest = onDismiss,
|
||||
// ) {
|
||||
// Surface(
|
||||
// color = AlertDialogDefaults.containerColor,
|
||||
// tonalElevation = AlertDialogDefaults.TonalElevation,
|
||||
// shape = AlertDialogDefaults.shape,
|
||||
// ) {
|
||||
// Column(
|
||||
// modifier = Modifier
|
||||
// .sizeIn(minWidth = 380.dp)
|
||||
// .padding(24.dp)
|
||||
// ) {
|
||||
// // Icon
|
||||
// Box(
|
||||
// Modifier
|
||||
// .align(Alignment.CenterHorizontally)
|
||||
// .padding(bottom = 16.dp)
|
||||
// ) {
|
||||
// Icon(
|
||||
// imageVector = Icons.Default.Check, // TODO: move away from icon lib
|
||||
// tint = AlertDialogDefaults.iconContentColor,
|
||||
// contentDescription = null
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // Title
|
||||
// Box(
|
||||
// modifier = Modifier
|
||||
// .align(Alignment.CenterHorizontally)
|
||||
// .padding(bottom = 30.dp)
|
||||
// ) {
|
||||
// Text(
|
||||
// stringResource(R.string.selector_discord_type),
|
||||
// color = AlertDialogDefaults.titleContentColor,
|
||||
// style = MaterialTheme.typography.headlineSmall
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // Body
|
||||
// Box(
|
||||
// modifier = Modifier
|
||||
// .padding(bottom = 30.dp)
|
||||
// ) {
|
||||
// Text(
|
||||
// stringResource(R.string.selector_discord_type_body),
|
||||
// textAlign = TextAlign.Center,
|
||||
// color = AlertDialogDefaults.textContentColor,
|
||||
// style = MaterialTheme.typography.bodyMedium
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// Buttons
|
||||
// Column(
|
||||
// verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
// modifier = Modifier.clip(MaterialTheme.shapes.large)
|
||||
// ) {
|
||||
// ProvideTextStyle(MaterialTheme.typography.labelMedium) {
|
||||
// Button(
|
||||
// shape = MaterialTheme.shapes.extraSmall,
|
||||
// enabled = BuildConfig.RN_ENABLED,
|
||||
// onClick = {
|
||||
// discordType = DiscordType.REACT_NATIVE
|
||||
// triggerConfirm()
|
||||
// },
|
||||
// modifier = Modifier
|
||||
// .fillMaxWidth()
|
||||
// .height(50.dp)
|
||||
// ) {
|
||||
// Text(stringResource(R.string.selector_discord_type_kotlin))
|
||||
// }
|
||||
// Button(
|
||||
// shape = MaterialTheme.shapes.extraSmall,
|
||||
// onClick = {
|
||||
// discordType = DiscordType.KOTLIN
|
||||
// triggerConfirm()
|
||||
// },
|
||||
// modifier = Modifier
|
||||
// .fillMaxWidth()
|
||||
// .height(50.dp)
|
||||
// ) {
|
||||
// Text(stringResource(R.string.selector_discord_type_old))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.NavigateBefore
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -22,7 +22,6 @@ import androidx.compose.ui.text.font.FontFamily
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.component.installer.*
|
||||
import com.aliucord.manager.ui.dialog.DiscordType
|
||||
import com.aliucord.manager.ui.dialog.DownloadMethod
|
||||
import com.aliucord.manager.ui.viewmodel.InstallViewModel
|
||||
import com.aliucord.manager.ui.viewmodel.InstallViewModel.InstallStepGroup
|
||||
|
|
@ -30,13 +29,11 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
import kotlinx.parcelize.Parcelize
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import java.util.*
|
||||
|
||||
@Immutable // this isn't *really* stable, but this never gets modified after being passed to a composable, so...
|
||||
@Parcelize
|
||||
data class InstallData(
|
||||
val downloadMethod: DownloadMethod,
|
||||
val discordType: DiscordType,
|
||||
var baseApk: String? = null,
|
||||
var splits: List<String>? = null
|
||||
) : Parcelable
|
||||
|
|
@ -50,7 +47,7 @@ fun InstallerScreen(
|
|||
) {
|
||||
if (viewModel.returnToHome) onBackClick()
|
||||
|
||||
var expandedGroup by mutableStateOf<InstallStepGroup?>(null)
|
||||
var expandedGroup by remember { mutableStateOf<InstallStepGroup?>(null) }
|
||||
|
||||
LaunchedEffect(viewModel.currentStep) {
|
||||
expandedGroup = viewModel.currentStep?.group
|
||||
|
|
@ -63,7 +60,7 @@ fun InstallerScreen(
|
|||
navigationIcon = {
|
||||
IconButton(onClick = onBackClick) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.NavigateBefore,
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = stringResource(R.string.navigation_back)
|
||||
)
|
||||
}
|
||||
|
|
@ -88,7 +85,7 @@ fun InstallerScreen(
|
|||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
for (group in InstallStepGroup.values()) {
|
||||
for (group in InstallStepGroup.entries) {
|
||||
InstallGroup(
|
||||
name = stringResource(group.nameResId),
|
||||
isCurrent = expandedGroup == group,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.aliucord.manager.ui.viewmodel
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
|
|
@ -17,15 +18,13 @@ import com.aliucord.manager.installer.util.*
|
|||
import com.aliucord.manager.network.utils.getOrThrow
|
||||
import com.aliucord.manager.ui.component.installer.InstallStatus
|
||||
import com.aliucord.manager.ui.component.installer.InstallStepData
|
||||
import com.aliucord.manager.ui.dialog.DiscordType
|
||||
import com.aliucord.manager.ui.screen.InstallData
|
||||
import com.aliucord.manager.util.*
|
||||
import com.github.diamondminer88.zip.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
class InstallViewModel(
|
||||
|
|
@ -55,7 +54,7 @@ class InstallViewModel(
|
|||
Running Android ${Build.VERSION.RELEASE}, API level ${Build.VERSION.SDK_INT}
|
||||
Supported ABIs: ${Build.SUPPORTED_ABIS.joinToString()}
|
||||
|
||||
Installing ${installData.discordType} with the ${installData.downloadMethod} apk method
|
||||
Installing Aliucord kt with the ${installData.downloadMethod} apk method
|
||||
|
||||
Failed on: ${currentStep?.name}
|
||||
""".trimIndent()
|
||||
|
|
@ -69,11 +68,13 @@ class InstallViewModel(
|
|||
}
|
||||
|
||||
private var debugLogPath by mutableStateOf<String?>(null)
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
fun saveDebugToFile() {
|
||||
val name = if (debugLogPath != null) {
|
||||
debugLogPath!!
|
||||
} else {
|
||||
"Aliucord Manager ${SimpleDateFormat("YYYY-MM-dd hh-mm-s a").format(Date())}.log"
|
||||
"Aliucord Manager ${SimpleDateFormat("yyyy-MM-dd hh-mm-s a").format(Date())}.log"
|
||||
.also { debugLogPath = it }
|
||||
}
|
||||
|
||||
|
|
@ -92,10 +93,7 @@ class InstallViewModel(
|
|||
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
when (installData.discordType) {
|
||||
DiscordType.REACT_NATIVE -> installReactNative()
|
||||
DiscordType.KOTLIN -> installKotlin()
|
||||
}
|
||||
installKotlin()
|
||||
|
||||
isFinished = true
|
||||
delay(20000)
|
||||
|
|
@ -145,304 +143,6 @@ class InstallViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun installReactNative() {
|
||||
steps += listOfNotNull(
|
||||
InstallStep.DL_BASE_APK,
|
||||
InstallStep.DL_LIBS_APK,
|
||||
InstallStep.DL_LANG_APK,
|
||||
InstallStep.DL_RESC_APK,
|
||||
InstallStep.DL_HERMES,
|
||||
InstallStep.DL_ALIUNATIVE,
|
||||
InstallStep.DL_BOOTSTRAP,
|
||||
if (preferences.replaceIcon) InstallStep.PATCH_APP_ICON else null,
|
||||
InstallStep.PATCH_MANIFEST,
|
||||
InstallStep.PATCH_DEX,
|
||||
InstallStep.PATCH_LIBS,
|
||||
InstallStep.SIGN_APK,
|
||||
InstallStep.INSTALL_APK,
|
||||
).map {
|
||||
it to InstallStepData(it.nameResId, InstallStatus.QUEUED)
|
||||
}
|
||||
|
||||
val supportedVersion = preferences.version
|
||||
val arch = Build.SUPPORTED_ABIS.first()
|
||||
val cacheDir = externalCacheDir
|
||||
val discordCacheDir = externalCacheDir.resolve(supportedVersion)
|
||||
val patchedDir = discordCacheDir.resolve("patched").also { it.deleteRecursively() }
|
||||
|
||||
clearOldCache(supportedVersion.toInt())
|
||||
uninstallNewAliucord(supportedVersion.toInt())
|
||||
|
||||
// Download base.apk
|
||||
val baseApkFile = step(InstallStep.DL_BASE_APK) {
|
||||
discordCacheDir.resolve("base-${supportedVersion}.apk").let { file ->
|
||||
if (file.exists()) {
|
||||
cached = true
|
||||
} else {
|
||||
downloadManager.downloadDiscordApk(supportedVersion, file)
|
||||
}
|
||||
|
||||
file.copyTo(
|
||||
patchedDir.resolve(file.name),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Download the native libraries split
|
||||
val libsApkFile = step(InstallStep.DL_LIBS_APK) {
|
||||
val libArch = arch.replace("-v", "_v")
|
||||
discordCacheDir.resolve("config.$libArch-${supportedVersion}.apk").let { file ->
|
||||
if (file.exists()) {
|
||||
cached = true
|
||||
} else downloadManager.downloadSplit(
|
||||
version = supportedVersion,
|
||||
split = "config.$libArch",
|
||||
out = file
|
||||
)
|
||||
|
||||
file.copyTo(
|
||||
patchedDir.resolve(file.name),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Download the locale split
|
||||
val localeApkFile = step(InstallStep.DL_LANG_APK) {
|
||||
discordCacheDir.resolve("config.en-${supportedVersion}.apk").let { file ->
|
||||
if (file.exists()) {
|
||||
cached = true
|
||||
} else downloadManager.downloadSplit(
|
||||
version = supportedVersion,
|
||||
split = "config.en",
|
||||
out = file
|
||||
)
|
||||
|
||||
file.copyTo(
|
||||
patchedDir.resolve(file.name),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Download the drawables split
|
||||
val resApkFile = step(InstallStep.DL_RESC_APK) {
|
||||
// TODO: download the appropriate dpi res apk
|
||||
discordCacheDir.resolve("config.xxhdpi-${supportedVersion}.apk").let { file ->
|
||||
if (file.exists()) {
|
||||
cached = true
|
||||
} else downloadManager.downloadSplit(
|
||||
version = supportedVersion,
|
||||
split = "config.xxhdpi",
|
||||
out = file
|
||||
)
|
||||
|
||||
file.copyTo(
|
||||
patchedDir.resolve(file.name),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Download hermes & c++ runtime lib
|
||||
val (hermesLibrary, cppRuntimeLibrary) = step(InstallStep.DL_HERMES) {
|
||||
// Fetch gh releases for Aliucord/Hermes
|
||||
val latestHermesRelease = githubRepository.getHermesRelease().getOrThrow()
|
||||
|
||||
// Download the hermes-release.aar file to replace in the apk
|
||||
val hermes = cacheDir.resolve("hermes-release-${latestHermesRelease.tagName}.aar").also { file ->
|
||||
if (file.exists()) {
|
||||
cached = true
|
||||
return@also
|
||||
}
|
||||
|
||||
latestHermesRelease.assets
|
||||
.find { it.name == "hermes-release.aar" }!!.browserDownloadUrl
|
||||
.also { downloadManager.download(it, file) }
|
||||
}
|
||||
|
||||
// Download the hermes-cppruntime-release.aar file to replace in the apk
|
||||
val cppRuntime = if (!preferences.hermesReplaceLibCpp) null else {
|
||||
cacheDir.resolve("hermes-cppruntime-release-${latestHermesRelease.tagName}.aar").also { file ->
|
||||
if (file.exists()) return@also
|
||||
cached = false
|
||||
|
||||
latestHermesRelease.assets
|
||||
.find { it.name == "hermes-cppruntime-release.aar" }!!.browserDownloadUrl
|
||||
.also { downloadManager.download(it, file) }
|
||||
}
|
||||
}
|
||||
|
||||
hermes to cppRuntime
|
||||
}
|
||||
|
||||
// Download Aliucord Native lib
|
||||
val aliucordDexFile = step(InstallStep.DL_ALIUNATIVE) {
|
||||
// Fetch the gh releases for Aliucord/AliucordNative
|
||||
val latestAliucordNativeRelease = githubRepository.getAliucordNativeRelease().getOrThrow()
|
||||
|
||||
// Download the Aliucord classes.dex file to add to the apk
|
||||
cacheDir.resolve("aliucord-${latestAliucordNativeRelease.tagName}.dex").also { file ->
|
||||
if (file.exists()) {
|
||||
cached = true
|
||||
return@also
|
||||
}
|
||||
|
||||
latestAliucordNativeRelease.assets
|
||||
.find { it.name == "classes.dex" }!!.browserDownloadUrl
|
||||
.also { downloadManager.download(it, file) }
|
||||
}
|
||||
}
|
||||
|
||||
// Download AliucordRN bootstrap
|
||||
val aliucordBootstrap = step(InstallStep.DL_BOOTSTRAP) {
|
||||
// Fetch latest commit for bootstrap.js
|
||||
val commit = githubRepository.getLatestBootstrapCommit().getOrThrow()
|
||||
cacheDir.resolve("bootstrap-$commit.js").also {
|
||||
if (it.exists()) {
|
||||
cached = true
|
||||
return@also
|
||||
}
|
||||
|
||||
downloadManager.downloadBootstrap(it)
|
||||
}
|
||||
}
|
||||
|
||||
val apks = arrayOf(baseApkFile, libsApkFile, localeApkFile, resApkFile)
|
||||
|
||||
// Replace app icons
|
||||
if (preferences.replaceIcon) {
|
||||
step(InstallStep.PATCH_APP_ICON) {
|
||||
ZipWriter(baseApkFile, true).use { baseApk ->
|
||||
val mipmaps = arrayOf("mipmap-xhdpi-v4", "mipmap-xxhdpi-v4", "mipmap-xxxhdpi-v4")
|
||||
val icons = arrayOf("ic_logo_foreground.png", "ic_logo_square.png", "ic_logo_foreground.png")
|
||||
|
||||
for (icon in icons) {
|
||||
val newIcon = application.assets.open("icons/$icon")
|
||||
.use { it.readBytes() }
|
||||
|
||||
for (mipmap in mipmaps) {
|
||||
val path = "res/$mipmap/$icon"
|
||||
baseApk.deleteEntry(path)
|
||||
baseApk.writeEntry(path, newIcon)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Patch manifests
|
||||
step(InstallStep.PATCH_MANIFEST) {
|
||||
apks.forEach { apk ->
|
||||
val manifest = ZipReader(apk)
|
||||
.use { zip -> zip.openEntry("AndroidManifest.xml")?.read() }
|
||||
?: throw IllegalStateException("No manifest in ${apk.name}")
|
||||
|
||||
ZipWriter(apk, true).use { zip ->
|
||||
val patchedManifestBytes = if (apk == baseApkFile) {
|
||||
ManifestPatcher.patchManifest(
|
||||
manifestBytes = manifest,
|
||||
packageName = preferences.packageName,
|
||||
appName = preferences.appName,
|
||||
debuggable = preferences.debuggable,
|
||||
)
|
||||
} else {
|
||||
ManifestPatcher.renamePackage(manifest, preferences.packageName)
|
||||
}
|
||||
|
||||
zip.deleteEntry("AndroidManifest.xml", apk == libsApkFile) // Preserve alignment in libs apk
|
||||
zip.writeEntry("AndroidManifest.xml", patchedManifestBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-order dex files
|
||||
step(InstallStep.PATCH_DEX) {
|
||||
val (dexCount, firstDexBytes) = ZipReader(baseApkFile).use { zip ->
|
||||
Pair(
|
||||
// Find the amount of .dex files in apk
|
||||
zip.entryNames.count { it.endsWith(".dex") },
|
||||
|
||||
// Get the first classes.dex bytes
|
||||
zip.openEntry("classes.dex")?.read()
|
||||
?: throw IllegalStateException("No classes.dex in base apk")
|
||||
)
|
||||
}
|
||||
|
||||
ZipWriter(baseApkFile, true).use { zip ->
|
||||
// Move first classes.dex to the dex file count + 1 to make place for Aliucord's .dex
|
||||
zip.deleteEntry("classes.dex")
|
||||
zip.writeEntry("classes${dexCount + 1}.dex", firstDexBytes)
|
||||
|
||||
// Add Aliucord's .dex and make it load first by being the first .dex
|
||||
zip.writeEntry("classes.dex", aliucordDexFile.readBytes())
|
||||
|
||||
// Add bootstrap
|
||||
zip.writeEntry("bootstrap.js", aliucordBootstrap.readBytes(), ZipCompression.NONE)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace libs
|
||||
step(InstallStep.PATCH_LIBS) {
|
||||
ZipWriter(libsApkFile, true).use { libsApk ->
|
||||
// Process the hermes and cpp runtime library
|
||||
for (libFile in arrayOf(hermesLibrary, cppRuntimeLibrary)) {
|
||||
libFile ?: continue
|
||||
|
||||
// Map .aar to the embedded .so inside
|
||||
val binaryName = with(libFile.name) {
|
||||
when {
|
||||
startsWith("hermes-release") -> "libhermes.so"
|
||||
startsWith("hermes-cppruntime-release") -> "libc++_shared.so"
|
||||
else -> throw Error("Unable to map $this to embedded .so")
|
||||
}
|
||||
}
|
||||
|
||||
// Read the embedded .so inside the .aar library
|
||||
val libBytes = ZipReader(libFile).use { libZip ->
|
||||
libZip.openEntry("jni/$arch/$binaryName")?.read()
|
||||
?: throw IllegalStateException("Failed to read jni/$arch/$binaryName from ${libFile.name}")
|
||||
}
|
||||
|
||||
// Delete the old binary and add the new one instead
|
||||
libsApk.deleteEntry("lib/$arch/$binaryName", true)
|
||||
libsApk.writeEntry("lib/$arch/$binaryName", libBytes, ZipCompression.NONE, 4096)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
step(InstallStep.SIGN_APK) {
|
||||
// Align resources.arsc due to targeting api 30 for silent install
|
||||
if (Build.VERSION.SDK_INT >= 31) {
|
||||
for (file in apks) {
|
||||
val bytes = ZipReader(file).use {
|
||||
if (it.entryNames.contains("resources.arsc")) {
|
||||
it.openEntry("resources.arsc")?.read()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: continue
|
||||
|
||||
ZipWriter(file, true).use {
|
||||
it.deleteEntry("resources.arsc", true)
|
||||
it.writeEntry("resources.arsc", bytes, ZipCompression.NONE, 4096)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apks.forEach(Signer::signApk)
|
||||
}
|
||||
|
||||
step(InstallStep.INSTALL_APK) {
|
||||
application.installApks(silent = !preferences.devMode, *apks)
|
||||
|
||||
if (!preferences.keepPatchedApks) {
|
||||
patchedDir.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun installKotlin() {
|
||||
steps += listOfNotNull(
|
||||
InstallStep.FETCH_KT_VERSION,
|
||||
|
|
@ -645,7 +345,6 @@ class InstallViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
private inline fun <T> step(step: InstallStep, block: InstallStepData.() -> T): T {
|
||||
steps[step]!!.status = InstallStatus.ONGOING
|
||||
currentStep = step
|
||||
|
|
@ -691,16 +390,6 @@ class InstallViewModel(
|
|||
@StringRes
|
||||
val nameResId: Int
|
||||
) {
|
||||
// React Native
|
||||
DL_BASE_APK(InstallStepGroup.APK_DL, R.string.install_step_dl_apk_base),
|
||||
DL_LIBS_APK(InstallStepGroup.APK_DL, R.string.install_step_dl_apk_lib),
|
||||
DL_LANG_APK(InstallStepGroup.APK_DL, R.string.install_step_dl_apk_locale),
|
||||
DL_RESC_APK(InstallStepGroup.APK_DL, R.string.install_step_dl_apk_resource),
|
||||
|
||||
DL_HERMES(InstallStepGroup.LIB_DL, R.string.install_step_dl_lib_hermes),
|
||||
DL_ALIUNATIVE(InstallStepGroup.LIB_DL, R.string.install_step_dl_lib_aliunative),
|
||||
DL_BOOTSTRAP(InstallStepGroup.LIB_DL, R.string.install_step_dl_bootstrap),
|
||||
|
||||
// Kotlin
|
||||
FETCH_KT_VERSION(InstallStepGroup.APK_DL, R.string.install_step_fetch_kt_version),
|
||||
DL_KT_APK(InstallStepGroup.APK_DL, R.string.install_step_dl_kt_apk),
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@
|
|||
<string name="install_group_apk_dl">Download APKs</string>
|
||||
<string name="install_group_lib_dl">Download Libraries</string>
|
||||
<string name="install_group_patch">Patch APKs</string>
|
||||
<string name="install_group_install">Install</string>
|
||||
<string name="install_group_install">Install APK</string>
|
||||
|
||||
<string name="install_step_patch_icons">Patching app icons</string>
|
||||
<string name="install_step_patch_manifests">Patching apk manifests</string>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ accompanist-systemUiController = { module = "com.google.accompanist:accompanist-
|
|||
androidx-activity = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
|
||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
||||
androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-splashscreen" }
|
||||
androidx-paging = "androidx.paging:paging-compose:3.3.0-alpha02" # TODO: remove paging
|
||||
androidx-paging = "androidx.paging:paging-compose:1.0.0-alpha17" # TODO: remove paging
|
||||
|
||||
# Compose
|
||||
compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" }
|
||||
|
|
@ -37,6 +37,7 @@ compose-foundation = { module = "androidx.compose.foundation:foundation", versio
|
|||
compose-animations = { module = "androidx.compose.animation:animation-graphics", version.ref = "compose" }
|
||||
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
|
||||
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
|
||||
compose-icons = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" }
|
||||
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }
|
||||
|
||||
# Compose debug tooling
|
||||
|
|
@ -81,6 +82,7 @@ compose = [
|
|||
"compose-runtime",
|
||||
"compose-ui",
|
||||
"compose-foundation",
|
||||
"compose-icons",
|
||||
"compose-material3",
|
||||
"compose-animations",
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue