feat: improve install screen UI (#69)

This commit is contained in:
rushii 2024-05-19 14:27:35 -07:00 • committed by GitHub
parent 6e1636873c
commit 7156f085ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 426 additions and 195 deletions

View file

@ -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" />

View file

@ -22,9 +22,9 @@ class KotlinInstallRunner(options: InstallOptions) : StepRunner() {
DownloadInjectorStep(),
DownloadAliuhookStep(),
DownloadKotlinStep(),
CopyDependenciesStep(),
// Patch
CopyDependenciesStep(),
ReplaceIconStep(options),
PatchManifestStep(options),
AddInjectorStep(),

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View 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,
)
}
}
}

View file

@ -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,
)
}
}

View file

@ -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())
// )
// }
// }

View file

@ -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

View file

@ -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,
)
}
}
}

View file

@ -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,
)
}
}
}

View file

@ -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) {

View file

@ -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)
)

View file

@ -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>