feat: improve install screen UI (#69)
This commit is contained in:
parent
6e1636873c
commit
7156f085ae
20 changed files with 426 additions and 195 deletions
|
|
@ -45,7 +45,9 @@
|
|||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.AliucordManager.SplashScreen">
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AliucordManager.SplashScreen"
|
||||
tools:ignore="DiscouragedApi,LockedOrientationActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ class KotlinInstallRunner(options: InstallOptions) : StepRunner() {
|
|||
DownloadInjectorStep(),
|
||||
DownloadAliuhookStep(),
|
||||
DownloadKotlinStep(),
|
||||
CopyDependenciesStep(),
|
||||
|
||||
// Patch
|
||||
CopyDependenciesStep(),
|
||||
ReplaceIconStep(options),
|
||||
PatchManifestStep(options),
|
||||
AddInjectorStep(),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
package com.aliucord.manager.installer.steps.patch
|
||||
package com.aliucord.manager.installer.steps.download
|
||||
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.installer.steps.StepGroup
|
||||
import com.aliucord.manager.installer.steps.StepRunner
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.download.DownloadDiscordStep
|
||||
import com.aliucord.manager.manager.PathManager
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
|
@ -22,7 +21,7 @@ class CopyDependenciesStep : Step(), KoinComponent {
|
|||
val patchedApk: File = paths.patchingWorkingDir()
|
||||
.resolve("patched.apk")
|
||||
|
||||
override val group = StepGroup.Patch
|
||||
override val group = StepGroup.Download
|
||||
override val localizedName = R.string.install_step_copy
|
||||
|
||||
override suspend fun execute(container: StepRunner) {
|
||||
|
|
@ -6,7 +6,7 @@ import com.aliucord.manager.installer.steps.StepGroup
|
|||
import com.aliucord.manager.installer.steps.StepRunner
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.base.StepState
|
||||
import com.aliucord.manager.installer.steps.patch.CopyDependenciesStep
|
||||
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
|
||||
import com.github.diamondminer88.zip.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.aliucord.manager.installer.steps.StepGroup
|
|||
import com.aliucord.manager.installer.steps.StepRunner
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.base.StepState
|
||||
import com.aliucord.manager.installer.steps.patch.CopyDependenciesStep
|
||||
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
|
||||
import com.aliucord.manager.installers.InstallerResult
|
||||
import com.aliucord.manager.manager.InstallerManager
|
||||
import com.aliucord.manager.manager.PreferencesManager
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.aliucord.manager.R
|
|||
import com.aliucord.manager.installer.steps.StepGroup
|
||||
import com.aliucord.manager.installer.steps.StepRunner
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.patch.CopyDependenciesStep
|
||||
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
|
||||
import com.aliucord.manager.installer.util.Signer
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.aliucord.manager.R
|
|||
import com.aliucord.manager.installer.steps.StepGroup
|
||||
import com.aliucord.manager.installer.steps.StepRunner
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
|
||||
import com.aliucord.manager.installer.steps.download.DownloadAliuhookStep
|
||||
import com.github.diamondminer88.zip.ZipReader
|
||||
import com.github.diamondminer88.zip.ZipWriter
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ import com.aliucord.manager.R
|
|||
import com.aliucord.manager.installer.steps.StepGroup
|
||||
import com.aliucord.manager.installer.steps.StepRunner
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.download.DownloadInjectorStep
|
||||
import com.aliucord.manager.installer.steps.download.DownloadKotlinStep
|
||||
import com.aliucord.manager.installer.steps.download.*
|
||||
import com.github.diamondminer88.zip.ZipReader
|
||||
import com.github.diamondminer88.zip.ZipWriter
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.aliucord.manager.R
|
|||
import com.aliucord.manager.installer.steps.StepGroup
|
||||
import com.aliucord.manager.installer.steps.StepRunner
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
|
||||
import com.aliucord.manager.installer.util.ManifestPatcher
|
||||
import com.aliucord.manager.ui.screens.installopts.InstallOptions
|
||||
import com.github.diamondminer88.zip.ZipReader
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.aliucord.manager.installer.steps.StepGroup
|
|||
import com.aliucord.manager.installer.steps.StepRunner
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.base.StepState
|
||||
import com.aliucord.manager.installer.steps.download.CopyDependenciesStep
|
||||
import com.aliucord.manager.installer.util.ArscUtil
|
||||
import com.aliucord.manager.installer.util.ArscUtil.addColorResource
|
||||
import com.aliucord.manager.installer.util.ArscUtil.addResource
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ object ManifestPatcher {
|
|||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||
if (name == NETWORK_SECURITY_CONFIG) return
|
||||
super.attr(ns, name, resourceId, type, value)
|
||||
if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) addLegacyStorage = false;
|
||||
if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) addLegacyStorage = false
|
||||
if (name == DEBUGGABLE) addDebuggable = false
|
||||
if (name == USES_CLEARTEXT_TRAFFIC) addUseClearTextTraffic = false
|
||||
}
|
||||
|
|
|
|||
58
app/src/main/kotlin/com/aliucord/manager/ui/TextBanner.kt
Normal file
58
app/src/main/kotlin/com/aliucord/manager/ui/TextBanner.kt
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package com.aliucord.manager.ui
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.ui.util.thenIf
|
||||
|
||||
@Composable
|
||||
fun TextBanner(
|
||||
text: String,
|
||||
icon: Painter,
|
||||
iconColor: Color,
|
||||
outlineColor: Color?,
|
||||
containerColor: Color,
|
||||
onClick: (() -> Unit)? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier
|
||||
.thenIf(outlineColor) { color ->
|
||||
border(
|
||||
width = 2.dp,
|
||||
color = color,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
)
|
||||
}
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(containerColor)
|
||||
.thenIf(onClick) { clickable(onClick = it) }
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 14.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = icon,
|
||||
tint = iconColor,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(28.dp),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,10 @@ package com.aliucord.manager.ui.screens.install
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.*
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
|
|
@ -25,6 +27,7 @@ import kotlinx.coroutines.*
|
|||
import kotlinx.coroutines.flow.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class InstallModel(
|
||||
private val application: Application,
|
||||
|
|
@ -35,16 +38,35 @@ class InstallModel(
|
|||
private var installJob: Job? = null
|
||||
private var stepRunner: StepRunner? = null
|
||||
|
||||
private var autocloseCancelled: Boolean = false
|
||||
|
||||
var installSteps by mutableStateOf<ImmutableMap<StepGroup, ImmutableList<Step>>?>(null)
|
||||
private set
|
||||
|
||||
var showGppWarning by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
@get:StringRes
|
||||
var funFact by mutableIntStateOf(0)
|
||||
private set
|
||||
|
||||
init {
|
||||
restart()
|
||||
|
||||
// Rotate fun facts every 20s
|
||||
screenModelScope.launch {
|
||||
while (true) {
|
||||
funFact = FUN_FACTS.random()
|
||||
delay(20.seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun launchApp() {
|
||||
if (state.value !is InstallScreenState.Success)
|
||||
return
|
||||
|
||||
Intent(options.packageName)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.also(application::startActivity)
|
||||
}
|
||||
|
||||
fun copyDebugToClipboard() {
|
||||
|
|
@ -68,17 +90,10 @@ class InstallModel(
|
|||
}
|
||||
|
||||
fun clearCache() {
|
||||
paths.clearCache()
|
||||
screenModelScope.launch { paths.clearCache() }
|
||||
application.showToast(R.string.action_cleared_cache)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the screen auto-close once installation was completed
|
||||
*/
|
||||
fun cancelAutoclose() {
|
||||
autocloseCancelled = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the 'Google Play Protect is enabled on your device' warning dialog
|
||||
*/
|
||||
|
|
@ -130,13 +145,6 @@ class InstallModel(
|
|||
// At this point, the installation has successfully completed
|
||||
else {
|
||||
mutableState.value = InstallScreenState.Success
|
||||
autocloseCancelled = false
|
||||
|
||||
// Wait 5s before returning to Home if screen hasn't been clicked
|
||||
delay(5000)
|
||||
if (!autocloseCancelled) {
|
||||
mutableState.value = InstallScreenState.CloseScreen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +196,25 @@ class InstallModel(
|
|||
SOC: $soc
|
||||
""".trimIndent()
|
||||
|
||||
return header + "\n\n" + Log.getStackTraceString(stacktrace)
|
||||
return header + "\n\n" + Log.getStackTraceString(stacktrace).trimEnd()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
/**
|
||||
* Random fun facts to show on the installation screen.
|
||||
*/
|
||||
val FUN_FACTS = arrayOf(
|
||||
R.string.fun_fact_1,
|
||||
R.string.fun_fact_2,
|
||||
R.string.fun_fact_3,
|
||||
R.string.fun_fact_4,
|
||||
R.string.fun_fact_5,
|
||||
R.string.fun_fact_6,
|
||||
R.string.fun_fact_7,
|
||||
R.string.fun_fact_8,
|
||||
R.string.fun_fact_9,
|
||||
R.string.fun_fact_10,
|
||||
R.string.fun_fact_11,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,35 +7,48 @@ package com.aliucord.manager.ui.screens.install
|
|||
|
||||
import android.os.Parcelable
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.koin.getScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.installer.steps.StepGroup
|
||||
import com.aliucord.manager.ui.components.Wakelock
|
||||
import com.aliucord.manager.ui.components.back
|
||||
import com.aliucord.manager.ui.TextBanner
|
||||
import com.aliucord.manager.ui.components.*
|
||||
import com.aliucord.manager.ui.components.dialogs.InstallerAbortDialog
|
||||
import com.aliucord.manager.ui.components.dialogs.PlayProtectDialog
|
||||
import com.aliucord.manager.ui.screens.install.components.*
|
||||
import com.aliucord.manager.ui.screens.installopts.InstallOptions
|
||||
import com.aliucord.manager.ui.util.paddings.*
|
||||
import com.aliucord.manager.ui.util.spacedByLastAtBottom
|
||||
import com.aliucord.manager.ui.util.thenIf
|
||||
import com.aliucord.manager.util.isIgnoringBatteryOptimizations
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
private val VERTICAL_PADDING: Dp = 18.dp
|
||||
|
||||
@Parcelize
|
||||
class InstallScreen(private val data: InstallOptions) : Screen, Parcelable {
|
||||
@IgnoredOnParcel
|
||||
|
|
@ -44,24 +57,31 @@ class InstallScreen(private val data: InstallOptions) : Screen, Parcelable {
|
|||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val model = getScreenModel<InstallModel> { parametersOf(data) }
|
||||
val state = model.state.collectAsState() // TODO: use `by`
|
||||
|
||||
LaunchedEffect(state.value) {
|
||||
if (state.value is InstallScreenState.CloseScreen)
|
||||
val state by model.state.collectAsState()
|
||||
val listState = rememberLazyListState()
|
||||
val showMinimizationWarning = remember { !context.isIgnoringBatteryOptimizations() }
|
||||
|
||||
LaunchedEffect(state) {
|
||||
if (state is InstallScreenState.CloseScreen)
|
||||
navigator.back(currentActivity = null)
|
||||
}
|
||||
|
||||
// Prevent screen from turning off while working
|
||||
Wakelock(active = state.value is InstallScreenState.Working)
|
||||
Wakelock(active = state is InstallScreenState.Working)
|
||||
|
||||
// Exit warning dialog (dismiss itself if install process state changes, esp. for Success)
|
||||
var showAbortWarning by remember(model.state.collectAsState()) { mutableStateOf(false) }
|
||||
|
||||
// The currently expanded step group on this screen
|
||||
var expandedGroup by remember { mutableStateOf<StepGroup?>(StepGroup.Prepare) }
|
||||
|
||||
// Only show exit warning if currently working
|
||||
val onTryExit: () -> Unit = remember {
|
||||
{
|
||||
if (state.value == InstallScreenState.Working) {
|
||||
if (state == InstallScreenState.Working) {
|
||||
showAbortWarning = true
|
||||
} else {
|
||||
navigator.back(currentActivity = null)
|
||||
|
|
@ -69,6 +89,14 @@ class InstallScreen(private val data: InstallOptions) : Screen, Parcelable {
|
|||
}
|
||||
}
|
||||
|
||||
// Close all groups when successfully finished everything
|
||||
LaunchedEffect(state) {
|
||||
if (state == InstallScreenState.Success)
|
||||
expandedGroup = null
|
||||
|
||||
listState.animateScrollToItem(0)
|
||||
}
|
||||
|
||||
if (showAbortWarning) {
|
||||
InstallerAbortDialog(
|
||||
onDismiss = { showAbortWarning = false },
|
||||
|
|
@ -87,100 +115,172 @@ class InstallScreen(private val data: InstallOptions) : Screen, Parcelable {
|
|||
|
||||
Scaffold(
|
||||
topBar = { InstallAppBar(onTryExit) },
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
indication = null,
|
||||
onClick = model::cancelAutoclose,
|
||||
interactionSource = remember(::MutableInteractionSource),
|
||||
),
|
||||
) { paddingValues ->
|
||||
Column(Modifier.padding(paddingValues)) {
|
||||
if (state.value is InstallScreenState.Working) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(VERTICAL_PADDING),
|
||||
modifier = Modifier
|
||||
.padding(paddingValues.exclude(PaddingValuesSides.Bottom)),
|
||||
) {
|
||||
if (state == InstallScreenState.Working) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.height(4.dp)
|
||||
.fillMaxWidth()
|
||||
.height(8.dp)
|
||||
.padding(bottom = 4.dp)
|
||||
)
|
||||
|
||||
MinimizationWarning(
|
||||
} else {
|
||||
HorizontalDivider(
|
||||
thickness = 2.dp,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.spacedByLastAtBottom(0.dp),
|
||||
contentPadding = paddingValues
|
||||
.exclude(PaddingValuesSides.Horizontal + PaddingValuesSides.Top)
|
||||
.add(PaddingValues(bottom = 25.dp)),
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
var expandedGroup by remember { mutableStateOf<StepGroup?>(StepGroup.Prepare) }
|
||||
|
||||
// Close all groups when successfully finished everything
|
||||
LaunchedEffect(state.value) {
|
||||
if (state.value == InstallScreenState.Success)
|
||||
expandedGroup = null
|
||||
}
|
||||
|
||||
model.installSteps?.let { groupedSteps ->
|
||||
for ((group, steps) in groupedSteps.entries) key(group) {
|
||||
StepGroupCard(
|
||||
name = stringResource(group.localizedName),
|
||||
subSteps = steps,
|
||||
isExpanded = expandedGroup == group,
|
||||
onExpand = {
|
||||
model.cancelAutoclose()
|
||||
expandedGroup = group
|
||||
},
|
||||
item(key = "MINIMIZATION_WARNING") {
|
||||
BannerSection(visible = showMinimizationWarning && !state.isFinished) {
|
||||
TextBanner(
|
||||
text = stringResource(R.string.installer_minimization_warning),
|
||||
icon = painterResource(R.drawable.ic_warning),
|
||||
iconColor = MaterialTheme.customColors.onWarningContainer,
|
||||
outlineColor = MaterialTheme.customColors.warning,
|
||||
containerColor = MaterialTheme.customColors.warningContainer,
|
||||
modifier = Modifier
|
||||
.padding(bottom = VERTICAL_PADDING)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.value.isFinished) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
item(key = "FAILED_BANNER") {
|
||||
BannerSection(visible = state is InstallScreenState.Failed) {
|
||||
val handler = LocalUriHandler.current
|
||||
|
||||
TextBanner(
|
||||
text = "Installation failed! You can either retry or click this banner to open the Aliucord server for help.",
|
||||
icon = painterResource(R.drawable.ic_warning),
|
||||
iconColor = MaterialTheme.colorScheme.error,
|
||||
outlineColor = null,
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
onClick = { handler.openUri("https://discord.gg/${BuildConfig.SUPPORT_SERVER}") },
|
||||
modifier = Modifier
|
||||
.padding(bottom = VERTICAL_PADDING)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item(key = "INSTALLED_BANNER") {
|
||||
BannerSection(visible = state is InstallScreenState.Success) {
|
||||
TextBanner(
|
||||
text = "Successfully installed Aliucord! Do *NOT* uninstall the manager (this app) as it is required to perform certain types of updates.",
|
||||
icon = painterResource(R.drawable.ic_check_circle),
|
||||
iconColor = Color(0xFF59B463),
|
||||
outlineColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
modifier = Modifier
|
||||
.padding(bottom = VERTICAL_PADDING)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for ((group, steps) in model.installSteps?.entries ?: persistentListOf()) {
|
||||
item(key = System.identityHashCode(group)) {
|
||||
StepGroupCard(
|
||||
name = stringResource(group.localizedName),
|
||||
subSteps = steps,
|
||||
isExpanded = expandedGroup == group,
|
||||
onExpand = { expandedGroup = group },
|
||||
modifier = Modifier
|
||||
.padding(bottom = VERTICAL_PADDING)
|
||||
.fillMaxWidth()
|
||||
.thenIf(state is InstallScreenState.Success) { alpha(.6f) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item(key = "BUTTONS") {
|
||||
var cacheCleared by remember { mutableStateOf(false) }
|
||||
val filteredState by remember { model.state.filter { it.isNewlyFinished } }
|
||||
.collectAsState(initial = null)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = filteredState != null,
|
||||
enter = fadeIn() + slideInVertically(),
|
||||
exit = ExitTransition.None,
|
||||
) {
|
||||
FilledTonalButton(onClick = model::clearCache) {
|
||||
Text(stringResource(R.string.setting_clear_cache))
|
||||
Column {
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier
|
||||
.padding(vertical = 4.dp)
|
||||
.padding(bottom = VERTICAL_PADDING)
|
||||
)
|
||||
|
||||
when (filteredState) {
|
||||
InstallScreenState.Success -> {
|
||||
MainActionButton(
|
||||
text = "Launch Aliucord",
|
||||
icon = painterResource(R.drawable.ic_launch),
|
||||
onClick = model::launchApp,
|
||||
)
|
||||
}
|
||||
|
||||
is InstallScreenState.Failed -> {
|
||||
MainActionButton(
|
||||
text = "Retry installation",
|
||||
icon = painterResource(R.drawable.ic_refresh),
|
||||
onClick = model::restart,
|
||||
)
|
||||
}
|
||||
|
||||
else -> error("unreachable")
|
||||
}
|
||||
|
||||
MainActionButton(
|
||||
text = stringResource(R.string.setting_clear_cache),
|
||||
icon = painterResource(R.drawable.ic_delete_forever),
|
||||
enabled = !cacheCleared,
|
||||
colors = IconButtonDefaults.filledTonalIconButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.error,
|
||||
),
|
||||
onClick = {
|
||||
cacheCleared = true
|
||||
model.clearCache()
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(top = 14.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.value is InstallScreenState.Failed) {
|
||||
val failureLog = (state.value as InstallScreenState.Failed).failureLog
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.End),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
FilledTonalButton(onClick = model::clearCache) {
|
||||
Text(stringResource(R.string.setting_clear_cache))
|
||||
item(key = "FUN_FACT") {
|
||||
AnimatedContent(
|
||||
targetState = model.funFact,
|
||||
label = "fun fact transition",
|
||||
transitionSpec = {
|
||||
(fadeIn(tween(220, delayMillis = 90)) + slideInHorizontally { it * -2 }) togetherWith
|
||||
(fadeOut(tween(90)) + slideOutHorizontally { it * 2 })
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f, true))
|
||||
|
||||
OutlinedButton(onClick = model::saveFailureLog) {
|
||||
Text(stringResource(R.string.installer_save_file))
|
||||
}
|
||||
|
||||
FilledTonalButton(onClick = model::copyDebugToClipboard) {
|
||||
Text(stringResource(R.string.action_copy))
|
||||
}
|
||||
}
|
||||
|
||||
SelectionContainer {
|
||||
) { text ->
|
||||
Text(
|
||||
text = failureLog,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
softWrap = false,
|
||||
text = stringResource(R.string.fun_fact_prefix, stringResource(text)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(10.dp))
|
||||
.padding(10.dp)
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(top = VERTICAL_PADDING, bottom = 25.dp, start = VERTICAL_PADDING, end = VERTICAL_PADDING)
|
||||
.fillMaxWidth()
|
||||
.alpha(.6f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -189,3 +289,44 @@ class InstallScreen(private val data: InstallOptions) : Screen, Parcelable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BannerSection(
|
||||
visible: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn() + slideInVertically(),
|
||||
exit = fadeOut() + slideOutVertically(),
|
||||
modifier = modifier
|
||||
.padding(bottom = VERTICAL_PADDING),
|
||||
) {
|
||||
Column {
|
||||
content()
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier.padding(vertical = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// item(key = "ERROR_LOG") {
|
||||
// SelectionContainer {
|
||||
// Text(
|
||||
// text = failureLog,
|
||||
// style = MaterialTheme.typography.labelSmall,
|
||||
// fontFamily = FontFamily.Monospace,
|
||||
// softWrap = false,
|
||||
// modifier = Modifier
|
||||
// .padding(top = VERTICAL_PADDING)
|
||||
// .clip(RoundedCornerShape(10.dp))
|
||||
// .background(MaterialTheme.colorScheme.surfaceColorAtElevation(10.dp))
|
||||
// .padding(24.dp)
|
||||
// .horizontalScroll(rememberScrollState())
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.aliucord.manager.ui.screens.install
|
||||
|
||||
import com.aliucord.manager.ui.screens.install.InstallScreenState.*
|
||||
|
||||
sealed interface InstallScreenState {
|
||||
data object Pending : InstallScreenState
|
||||
data object Working : InstallScreenState
|
||||
|
|
@ -10,7 +12,10 @@ sealed interface InstallScreenState {
|
|||
) : InstallScreenState
|
||||
|
||||
data object CloseScreen : InstallScreenState
|
||||
|
||||
val isFinished: Boolean
|
||||
get() = this !is Pending && this !is Working
|
||||
}
|
||||
|
||||
val InstallScreenState.isNewlyFinished: Boolean
|
||||
inline get() = this == Success || this is Failed
|
||||
|
||||
val InstallScreenState.isFinished: Boolean
|
||||
inline get() = isNewlyFinished || this == CloseScreen
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.aliucord.manager.ui.screens.install.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun MainActionButton(
|
||||
text: String,
|
||||
icon: Painter,
|
||||
onClick: () -> Unit,
|
||||
enabled: Boolean = true,
|
||||
colors: IconButtonColors = IconButtonDefaults.filledTonalIconButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
FilledTonalIconButton(
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = colors,
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(46.dp),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
package com.aliucord.manager.ui.screens.install.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.ui.components.customColors
|
||||
import com.aliucord.manager.util.isIgnoringBatteryOptimizations
|
||||
|
||||
@Composable
|
||||
fun MinimizationWarning(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val hide = remember(context) { context.isIgnoringBatteryOptimizations() }
|
||||
|
||||
if (hide) return
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier
|
||||
.padding(
|
||||
start = 20.dp,
|
||||
end = 20.dp,
|
||||
top = 20.dp,
|
||||
bottom = 4.dp,
|
||||
)
|
||||
.border(
|
||||
width = 2.dp,
|
||||
color = MaterialTheme.customColors.warning,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
)
|
||||
.clip(MaterialTheme.shapes.medium)
|
||||
.background(MaterialTheme.customColors.warningContainer)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp, vertical = 14.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_warning),
|
||||
tint = MaterialTheme.customColors.onWarningContainer,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(28.dp),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.installer_minimization_warning),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import androidx.compose.animation.AnimatedVisibility
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -16,7 +15,6 @@ import androidx.compose.ui.unit.dp
|
|||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.installer.steps.base.Step
|
||||
import com.aliucord.manager.installer.steps.base.StepState
|
||||
import com.aliucord.manager.ui.util.thenIf
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
|
|
@ -25,8 +23,9 @@ fun StepGroupCard(
|
|||
subSteps: ImmutableList<Step>,
|
||||
isExpanded: Boolean,
|
||||
onExpand: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val groupState by remember {
|
||||
val groupState by remember(subSteps) {
|
||||
derivedStateOf {
|
||||
when {
|
||||
// If all steps are pending then show pending
|
||||
|
|
@ -57,22 +56,22 @@ fun StepGroupCard(
|
|||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.thenIf(isExpanded) {
|
||||
background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||
}
|
||||
modifier = modifier
|
||||
.clip(MaterialTheme.shapes.large)
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||
modifier = Modifier
|
||||
.clickable(true, onClick = onExpand)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.padding(20.dp)
|
||||
) {
|
||||
StepStateIcon(groupState, 24.dp)
|
||||
StepStateIcon(
|
||||
state = groupState,
|
||||
size = 24.dp,
|
||||
)
|
||||
|
||||
Text(text = name)
|
||||
|
||||
|
|
@ -102,7 +101,7 @@ fun StepGroupCard(
|
|||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background.copy(0.6f))
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.padding(20.dp)
|
||||
.padding(start = 4.dp)
|
||||
) {
|
||||
for (step in subSteps) key(step) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ fun StepStateIcon(
|
|||
StepState.Pending -> Icon(
|
||||
painter = painterResource(R.drawable.ic_circle),
|
||||
contentDescription = stringResource(R.string.status_queued),
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(0.4f),
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(.2f),
|
||||
modifier = Modifier.size(size)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@
|
|||
<string name="version_unknown">Unknown</string>
|
||||
<string name="version_load_fail">Failed to retrieve version</string>
|
||||
|
||||
<string name="installer_save_file">Save to file</string>
|
||||
<string name="installer_file_save_success">Saved to %s</string>
|
||||
<string name="installer_file_save_failed">Failed to save %s</string>
|
||||
<string name="installer_success">Successfully installed Aliucord</string>
|
||||
|
|
@ -159,4 +158,17 @@
|
|||
<string name="play_protect_warning_desc">Google Play Protect appears to be enabled on your device. It may attempt to interfere with a new installation due to the usage of a unique signing key. You can disable it in Play Protect\'s settings.\n\nIf it does show a warning dialog, press\n\"More Details\" -> \"Install anyway\"</string>
|
||||
<string name="play_protect_warning_ok">Continue</string>
|
||||
<string name="play_protect_warning_open_gpp">Open Play Protect</string>
|
||||
|
||||
<string name="fun_fact_prefix">Fun Fact: %s</string>
|
||||
<string name="fun_fact_1">Did you know that Google Play Protect is useless?</string>
|
||||
<string name="fun_fact_2">Did you know Aliucord with 150+ plugins somehow runs better than Discord\'s new app?</string>
|
||||
<string name="fun_fact_3">Did you know Aliucord has over 200 available plugins?</string>
|
||||
<string name="fun_fact_4">i am in your walls</string>
|
||||
<string name="fun_fact_5">Did you know AliucordRN spawned Enmity and Vendetta?</string>
|
||||
<string name="fun_fact_6">Having issues? Visit the #support channel in our Discord!</string>
|
||||
<string name="fun_fact_7">The IWillCrashYourDiscord plugin crashes your discord.</string>
|
||||
<string name="fun_fact_8">\"@Vendicated my bad.\"</string>
|
||||
<string name="fun_fact_9">Aliucord can run on WearOS</string>
|
||||
<string name="fun_fact_10">Aliucord uses the *real* old UI, from mid-2022!</string>
|
||||
<string name="fun_fact_11">There was an attempt to make Aliucord run on iOS…</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue