diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9618ec9..4773e99 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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"> diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/KotlinInstallRunner.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/KotlinInstallRunner.kt index 2a806a9..083e7c5 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/KotlinInstallRunner.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/KotlinInstallRunner.kt @@ -22,9 +22,9 @@ class KotlinInstallRunner(options: InstallOptions) : StepRunner() { DownloadInjectorStep(), DownloadAliuhookStep(), DownloadKotlinStep(), + CopyDependenciesStep(), // Patch - CopyDependenciesStep(), ReplaceIconStep(options), PatchManifestStep(options), AddInjectorStep(), diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/CopyDependenciesStep.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/download/CopyDependenciesStep.kt similarity index 87% rename from app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/CopyDependenciesStep.kt rename to app/src/main/kotlin/com/aliucord/manager/installer/steps/download/CopyDependenciesStep.kt index 309f402..7402410 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/CopyDependenciesStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/download/CopyDependenciesStep.kt @@ -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) { diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/AlignmentStep.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/AlignmentStep.kt index b4e9d75..22cc46d 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/AlignmentStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/AlignmentStep.kt @@ -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 diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/InstallStep.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/InstallStep.kt index 00aa9ae..8721f90 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/InstallStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/InstallStep.kt @@ -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 diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/SigningStep.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/SigningStep.kt index bf84ace..261df70 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/SigningStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/install/SigningStep.kt @@ -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 diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/AddAliuhookStep.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/AddAliuhookStep.kt index b10f997..169ca47 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/AddAliuhookStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/AddAliuhookStep.kt @@ -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 diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/AddInjectorStep.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/AddInjectorStep.kt index e60ce32..3318d45 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/AddInjectorStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/AddInjectorStep.kt @@ -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 diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/PatchManifestStep.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/PatchManifestStep.kt index ae7d608..8a6ab36 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/PatchManifestStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/PatchManifestStep.kt @@ -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 diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/ReplaceIconStep.kt b/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/ReplaceIconStep.kt index e20f9fd..1b961a8 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/ReplaceIconStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/steps/patch/ReplaceIconStep.kt @@ -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 diff --git a/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt b/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt index dc0cd49..5b5cf6a 100644 --- a/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt +++ b/app/src/main/kotlin/com/aliucord/manager/installer/util/ManifestPatcher.kt @@ -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 } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/TextBanner.kt b/app/src/main/kotlin/com/aliucord/manager/ui/TextBanner.kt new file mode 100644 index 0000000..282b67d --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/TextBanner.kt @@ -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, + ) + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt index 2ff0ca5..43e539d 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallModel.kt @@ -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>?>(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, + ) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreen.kt index 6b28cc9..e67807b 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreen.kt @@ -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 { 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.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.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()) +// ) +// } +// } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreenState.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreenState.kt index f9fab47..c6d9d0c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreenState.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/InstallScreenState.kt @@ -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 diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/MainActionButton.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/MainActionButton.kt new file mode 100644 index 0000000..e37e42e --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/MainActionButton.kt @@ -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, + ) + } + } +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/MinimizationWarning.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/MinimizationWarning.kt deleted file mode 100644 index b963d7a..0000000 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/MinimizationWarning.kt +++ /dev/null @@ -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, - ) - } - } -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepGroupCard.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepGroupCard.kt index 42abcdc..b5cce94 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepGroupCard.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepGroupCard.kt @@ -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, 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) { diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepStateIcon.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepStateIcon.kt index cfb2173..d71cd50 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepStateIcon.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/install/components/StepStateIcon.kt @@ -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) ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 65ddb16..8c4f99e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -78,7 +78,6 @@ Unknown Failed to retrieve version - Save to file Saved to %s Failed to save %s Successfully installed Aliucord @@ -159,4 +158,17 @@ 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\" Continue Open Play Protect + + Fun Fact: %s + Did you know that Google Play Protect is useless? + Did you know Aliucord with 150+ plugins somehow runs better than Discord\'s new app? + Did you know Aliucord has over 200 available plugins? + i am in your walls + Did you know AliucordRN spawned Enmity and Vendetta? + Having issues? Visit the #support channel in our Discord! + The IWillCrashYourDiscord plugin crashes your discord. + \"@Vendicated my bad.\" + Aliucord can run on WearOS + Aliucord uses the *real* old UI, from mid-2022! + There was an attempt to make Aliucord run on iOS…