mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-22 00:42:38 +00:00
redo launch logic
This commit is contained in:
4 files changed
+382
-144
No files matched your search
@@ -1,6 +1,7 @@
|
||||
package com.geode.launcher
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.format.Formatter
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
@@ -15,19 +16,18 @@ import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -35,6 +35,7 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -48,11 +49,10 @@ import com.geode.launcher.main.*
|
||||
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||
import com.geode.launcher.ui.theme.LocalTheme
|
||||
import com.geode.launcher.ui.theme.Theme
|
||||
import com.geode.launcher.updater.ReleaseViewModel
|
||||
import com.geode.launcher.utils.Constants
|
||||
import com.geode.launcher.utils.GamePackageUtils
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AltMainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -84,6 +84,12 @@ class AltMainActivity : ComponentActivity() {
|
||||
|
||||
val backgroundOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.BLACK_BACKGROUND)
|
||||
|
||||
val launchViewModel = viewModel<LaunchViewModel>(factory = LaunchViewModel.Factory)
|
||||
|
||||
if (loadFailureInfo != null) {
|
||||
launchViewModel.loadFailure = loadFailureInfo
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalTheme provides theme) {
|
||||
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption) {
|
||||
Surface(
|
||||
@@ -91,7 +97,7 @@ class AltMainActivity : ComponentActivity() {
|
||||
color = Color.Transparent
|
||||
) {
|
||||
WithWave {
|
||||
AltMainScreen(gdInstalled, geodeInstalled, loadFailureInfo)
|
||||
AltMainScreen(launchViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,17 +114,140 @@ class AltMainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LaunchProgressBody(title: String, details: String, modifier: Modifier = Modifier) {
|
||||
data class LaunchStatusInfo(
|
||||
val title: String,
|
||||
val details: String? = null,
|
||||
val progress: (() -> Float)? = null
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LaunchCancelledIcon(cancelReason: LaunchViewModel.LaunchCancelReason) {
|
||||
when (cancelReason) {
|
||||
LaunchViewModel.LaunchCancelReason.GAME_OUTDATED,
|
||||
LaunchViewModel.LaunchCancelReason.GAME_MISSING -> Icon(painterResource(R.drawable.icon_error), contentDescription = null)
|
||||
LaunchViewModel.LaunchCancelReason.LAST_LAUNCH_CRASHED,
|
||||
LaunchViewModel.LaunchCancelReason.GEODE_NOT_FOUND -> Icon(Icons.Default.Warning, contentDescription = null)
|
||||
else -> Icon(Icons.Default.Info, contentDescription = null)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LaunchProgressCard(modifier: Modifier = Modifier) {
|
||||
Card(modifier = modifier) {
|
||||
Column {
|
||||
fun mapCancelReasonToInfo(cancelReason: LaunchViewModel.LaunchCancelReason): LaunchStatusInfo {
|
||||
return when (cancelReason) {
|
||||
LaunchViewModel.LaunchCancelReason.LAST_LAUNCH_CRASHED -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.launcher_cancelled_crash)
|
||||
)
|
||||
LaunchViewModel.LaunchCancelReason.GEODE_NOT_FOUND -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.geode_download_title)
|
||||
)
|
||||
LaunchViewModel.LaunchCancelReason.GAME_MISSING -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.launcher_cancelled_error),
|
||||
details = stringResource(R.string.game_not_found)
|
||||
)
|
||||
LaunchViewModel.LaunchCancelReason.GAME_OUTDATED -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.launcher_cancelled_error),
|
||||
details = stringResource(R.string.launcher_cancelled_outdated)
|
||||
)
|
||||
else -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.launcher_cancelled_manual)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun mapLaunchStatusToInfo(state: LaunchViewModel.LaunchUIState): LaunchStatusInfo {
|
||||
return when (state) {
|
||||
is LaunchViewModel.LaunchUIState.Initial,
|
||||
is LaunchViewModel.LaunchUIState.Working,
|
||||
is LaunchViewModel.LaunchUIState.Ready -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.launcher_starting_game)
|
||||
)
|
||||
is LaunchViewModel.LaunchUIState.UpdateCheck -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.release_fetch_in_progress)
|
||||
)
|
||||
is LaunchViewModel.LaunchUIState.Updating -> {
|
||||
val context = LocalContext.current
|
||||
|
||||
val downloaded = remember(state.downloaded) {
|
||||
Formatter.formatShortFileSize(context, state.downloaded)
|
||||
}
|
||||
|
||||
val outOf = remember(state.outOf) {
|
||||
Formatter.formatShortFileSize(context, state.outOf)
|
||||
}
|
||||
|
||||
LaunchStatusInfo(
|
||||
title = stringResource(R.string.launcher_downloading_update),
|
||||
details = stringResource(R.string.launcher_downloading_update_details, downloaded, outOf),
|
||||
progress = {
|
||||
val progress = state.downloaded / state.outOf.toDouble()
|
||||
progress.toFloat()
|
||||
}
|
||||
)
|
||||
}
|
||||
is LaunchViewModel.LaunchUIState.Cancelled -> {
|
||||
return mapCancelReasonToInfo(state.reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LaunchCancelledBody(statusInfo: LaunchStatusInfo, icon: @Composable () -> Unit, inProgress: Boolean) {
|
||||
Row {
|
||||
icon()
|
||||
Text(statusInfo.title)
|
||||
}
|
||||
|
||||
if (inProgress) {
|
||||
LinearProgressIndicator()
|
||||
}
|
||||
|
||||
if (statusInfo.details != null) {
|
||||
Text(statusInfo.details)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LaunchProgressBody(statusInfo: LaunchStatusInfo, modifier: Modifier = Modifier) {
|
||||
Column {
|
||||
Text(statusInfo.title)
|
||||
|
||||
if (statusInfo.progress != null) {
|
||||
LinearProgressIndicator(statusInfo.progress)
|
||||
} else {
|
||||
LinearProgressIndicator()
|
||||
}
|
||||
|
||||
if (statusInfo.details != null) {
|
||||
Text(statusInfo.details)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LaunchProgressCard(uiState: LaunchViewModel.LaunchUIState, onCancel: () -> Unit, onResume: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val status = mapLaunchStatusToInfo(uiState)
|
||||
Card(modifier = modifier) {
|
||||
if (uiState is LaunchViewModel.LaunchUIState.Cancelled) {
|
||||
LaunchCancelledBody(
|
||||
statusInfo = status,
|
||||
icon = { LaunchCancelledIcon(uiState.reason) },
|
||||
inProgress = uiState.inProgress
|
||||
)
|
||||
|
||||
if (uiState.reason.allowsRetry()) {
|
||||
TextButton(onClick = onResume) {
|
||||
Text(stringResource(R.string.launcher_cancelled_restart))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LaunchProgressBody(statusInfo = status)
|
||||
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(stringResource(R.string.release_fetch_button_cancel))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,151 +273,73 @@ fun GeodeLogo(modifier: Modifier = Modifier) {
|
||||
|
||||
@Composable
|
||||
fun AltMainScreen(
|
||||
gdInstalled: Boolean = true,
|
||||
geodePreinstalled: Boolean = true,
|
||||
loadFailureInfo: LoadFailureInfo? = null,
|
||||
releaseViewModel: ReleaseViewModel = viewModel(factory = ReleaseViewModel.Factory)
|
||||
launchViewModel: LaunchViewModel = viewModel(factory = LaunchViewModel.Factory)
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val shouldUpdate by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.UPDATE_AUTOMATICALLY)
|
||||
val autoUpdateState by releaseViewModel.uiState.collectAsState()
|
||||
LaunchedEffect(Unit) {
|
||||
launchViewModel.beginLaunchFlow()
|
||||
}
|
||||
|
||||
val geodeJustInstalled = (autoUpdateState as? ReleaseViewModel.ReleaseUIState.Finished)
|
||||
?.hasUpdated ?: false
|
||||
val geodeInstalled = geodePreinstalled || geodeJustInstalled
|
||||
|
||||
var beginLaunch by remember { mutableStateOf(false) }
|
||||
val launchUIState by launchViewModel.uiState.collectAsState()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var launchInSafeMode by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(shouldUpdate) {
|
||||
if (shouldUpdate && !releaseViewModel.hasPerformedCheck && gdInstalled) {
|
||||
releaseViewModel.runReleaseCheck()
|
||||
} else {
|
||||
releaseViewModel.useGlobalCheckState()
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
GeodeLogo(modifier = Modifier.offset(y = (-90).dp))
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
// automatically show load failed dialog (but not crash)
|
||||
var showErrorInfo by remember {
|
||||
mutableStateOf(
|
||||
loadFailureInfo != null && loadFailureInfo.title != LaunchUtils.LauncherError.CRASHED
|
||||
LaunchProgressCard(
|
||||
launchUIState,
|
||||
onCancel = {
|
||||
launchInSafeMode = false
|
||||
coroutineScope.launch {
|
||||
launchViewModel.cancelLaunch()
|
||||
}
|
||||
},
|
||||
onResume = {
|
||||
launchViewModel.loadFailure = null
|
||||
coroutineScope.launch {
|
||||
launchViewModel.beginLaunchFlow()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.offset(y = 90.dp)
|
||||
)
|
||||
}
|
||||
|
||||
val hasError = loadFailureInfo != null
|
||||
LaunchedEffect(hasError, showErrorInfo) {
|
||||
if (!showErrorInfo && loadFailureInfo != null) {
|
||||
showFailureSnackbar(context, loadFailureInfo, snackbarHostState) {
|
||||
showErrorInfo = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showErrorInfo && loadFailureInfo != null) {
|
||||
ErrorInfoSheet(loadFailureInfo, onDismiss = { showErrorInfo = false })
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = Color.White,
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
|
||||
) { innerPadding ->
|
||||
Box(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
GeodeLogo(modifier = Modifier.offset(y = (-90).dp))
|
||||
|
||||
when {
|
||||
gdInstalled && geodeInstalled -> {
|
||||
val gdVersion = remember {
|
||||
GamePackageUtils.getGameVersionCode(context.packageManager)
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
launchViewModel.cancelLaunch()
|
||||
}
|
||||
|
||||
if (gdVersion < Constants.SUPPORTED_VERSION_CODE) {
|
||||
val versionName = remember {
|
||||
GamePackageUtils.getUnifiedVersionName(context.packageManager)
|
||||
}
|
||||
|
||||
LaunchBlockedLabel(stringResource(R.string.game_outdated, versionName))
|
||||
} else {
|
||||
val waitForUpdate = (shouldUpdate && !releaseViewModel.hasPerformedCheck) || releaseViewModel.isInUpdate
|
||||
LaunchedEffect(waitForUpdate) {
|
||||
if (!waitForUpdate) {
|
||||
// beginLaunch = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gdInstalled -> {
|
||||
Text(
|
||||
context.getString(R.string.geode_download_title),
|
||||
modifier = Modifier.padding(12.dp)
|
||||
)
|
||||
|
||||
Row {
|
||||
Button(
|
||||
onClick = { releaseViewModel.runReleaseCheck(true) },
|
||||
enabled = !releaseViewModel.isInUpdate
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_download),
|
||||
contentDescription = context.getString(R.string.launcher_download_icon_alt)
|
||||
)
|
||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||
Text(context.getString(R.string.launcher_download))
|
||||
}
|
||||
Spacer(Modifier.size(2.dp))
|
||||
IconButton(onClick = { onSettings(context) }) {
|
||||
Icon(
|
||||
Icons.Filled.Settings,
|
||||
contentDescription = context.getString(R.string.launcher_settings_icon_alt)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> LaunchBlockedLabel(stringResource(R.string.game_not_found))
|
||||
}
|
||||
|
||||
OutlinedCard(
|
||||
modifier = Modifier.offset(y = 70.dp)
|
||||
onSettings(context)
|
||||
},
|
||||
) {
|
||||
UpdateCard(
|
||||
releaseViewModel,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
Icon(
|
||||
Icons.Filled.Settings,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Button(
|
||||
onClick = { onSettings(context) },
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Settings,
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||
Text(context.getString(R.string.launcher_settings))
|
||||
}
|
||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||
Text(context.getString(R.string.launcher_settings))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (beginLaunch) {
|
||||
if (launchUIState is LaunchViewModel.LaunchUIState.Ready) {
|
||||
UpdateWarning(launchInSafeMode) {
|
||||
beginLaunch = false
|
||||
launchInSafeMode = false
|
||||
coroutineScope.launch {
|
||||
launchViewModel.cancelLaunch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.max
|
||||
import androidx.compose.ui.unit.min
|
||||
import androidx.compose.ui.zIndex
|
||||
import kotlin.math.sin
|
||||
import kotlin.random.Random
|
||||
@@ -45,9 +42,6 @@ fun WithWave(modifier: Modifier = Modifier, contents: @Composable () -> Unit) {
|
||||
) {
|
||||
contents()
|
||||
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenHeight = configuration.screenHeightDp.dp
|
||||
|
||||
WaveBackground(
|
||||
color = WAVE_COLOR_1,
|
||||
period = 500.dp,
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.geode.launcher.main
|
||||
|
||||
import android.app.Application
|
||||
import android.os.CountDownTimer
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.geode.launcher.updater.ReleaseManager
|
||||
import com.geode.launcher.utils.Constants
|
||||
import com.geode.launcher.utils.GamePackageUtils
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
val application = this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application
|
||||
|
||||
LaunchViewModel(
|
||||
application = application
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var readyTimerPassed = false
|
||||
private var readyTimer: CountDownTimer? = null
|
||||
|
||||
private fun initReadyTimer() {
|
||||
readyTimer = object : CountDownTimer(1000, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
// no tick necessary
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
readyTimerPassed = true
|
||||
|
||||
if (_uiState.value is LaunchUIState.Working) {
|
||||
viewModelScope.launch {
|
||||
preReadyCheck()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
enum class LaunchCancelReason {
|
||||
MANUAL, LAST_LAUNCH_CRASHED, GEODE_NOT_FOUND, GAME_MISSING, GAME_OUTDATED;
|
||||
|
||||
fun allowsRetry() = when (this) {
|
||||
MANUAL,
|
||||
LAST_LAUNCH_CRASHED,
|
||||
GEODE_NOT_FOUND -> true
|
||||
GAME_MISSING,
|
||||
GAME_OUTDATED -> false
|
||||
}
|
||||
}
|
||||
|
||||
sealed class LaunchUIState {
|
||||
data object Initial : LaunchUIState()
|
||||
data object UpdateCheck : LaunchUIState()
|
||||
data class Updating(val downloaded: Long, val outOf: Long) : LaunchUIState()
|
||||
data class Cancelled(val reason: LaunchCancelReason, val inProgress: Boolean = false) : LaunchUIState()
|
||||
data object Working : LaunchUIState()
|
||||
data object Ready : LaunchUIState()
|
||||
}
|
||||
|
||||
private val _uiState = MutableStateFlow<LaunchUIState>(LaunchUIState.Initial)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
var loadFailure: LoadFailureInfo? = null
|
||||
private var hasCheckedForUpdates = false
|
||||
private var isCancelling = false
|
||||
|
||||
private suspend fun determineUpdateStatus() {
|
||||
ReleaseManager.get(application).uiState.takeWhile {
|
||||
it is ReleaseManager.ReleaseManagerState.InUpdateCheck || it is ReleaseManager.ReleaseManagerState.InDownload
|
||||
}.map { when (it) {
|
||||
is ReleaseManager.ReleaseManagerState.InUpdateCheck -> LaunchUIState.UpdateCheck
|
||||
is ReleaseManager.ReleaseManagerState.InDownload -> LaunchUIState.Updating(it.downloaded, it.outOf)
|
||||
else -> LaunchUIState.UpdateCheck
|
||||
}
|
||||
}.collect {
|
||||
_uiState.emit(it)
|
||||
}
|
||||
|
||||
// flow terminates once update check is finished
|
||||
hasCheckedForUpdates = true
|
||||
preReadyCheck()
|
||||
}
|
||||
|
||||
private suspend fun preReadyCheck() {
|
||||
if (isCancelling) {
|
||||
// don't let the game start if cancelled
|
||||
return
|
||||
}
|
||||
|
||||
val packageManager = application.packageManager
|
||||
|
||||
if (!GamePackageUtils.isGameInstalled(packageManager)) {
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_MISSING))
|
||||
}
|
||||
|
||||
if (ReleaseManager.get(application).isInUpdate) {
|
||||
viewModelScope.launch {
|
||||
determineUpdateStatus()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (loadFailure != null) {
|
||||
// last launch crashed, so cancel
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.LAST_LAUNCH_CRASHED))
|
||||
}
|
||||
|
||||
if (GamePackageUtils.getGameVersionCode(packageManager) < Constants.SUPPORTED_VERSION_CODE) {
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_OUTDATED))
|
||||
}
|
||||
|
||||
if (!LaunchUtils.isGeodeInstalled(application)) {
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GEODE_NOT_FOUND))
|
||||
}
|
||||
|
||||
if (!readyTimerPassed) {
|
||||
return _uiState.emit(LaunchUIState.Working)
|
||||
}
|
||||
|
||||
_uiState.emit(LaunchUIState.Ready)
|
||||
}
|
||||
|
||||
suspend fun beginLaunchFlow() {
|
||||
if (_uiState.value !is LaunchUIState.Initial && _uiState.value !is LaunchUIState.Cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
isCancelling = false
|
||||
|
||||
if (!GamePackageUtils.isGameInstalled(application.packageManager)) {
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_MISSING))
|
||||
}
|
||||
|
||||
if (readyTimer == null) {
|
||||
initReadyTimer()
|
||||
}
|
||||
|
||||
val shouldUpdate = PreferenceUtils.get(application).getBoolean(PreferenceUtils.Key.UPDATE_AUTOMATICALLY)
|
||||
if (shouldUpdate && !hasCheckedForUpdates) {
|
||||
ReleaseManager.get(application).checkForUpdates(false)
|
||||
}
|
||||
|
||||
preReadyCheck()
|
||||
}
|
||||
|
||||
suspend fun cancelLaunch() {
|
||||
isCancelling = true
|
||||
_uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.MANUAL, true))
|
||||
|
||||
val releaseManager = ReleaseManager.get(application)
|
||||
if (releaseManager.isInUpdate) {
|
||||
releaseManager.cancelUpdate()
|
||||
}
|
||||
|
||||
_uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.MANUAL, false))
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
||||
readyTimer?.cancel()
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,20 @@
|
||||
<string name="launcher_error_export_crash">Share crash</string>
|
||||
<string name="launcher_error_export_missing">No crash dump was found!</string>
|
||||
<string name="launcher_error_device_info">Android %2$s, %1$s</string>
|
||||
<string name="launcher_settings_notification_body">Launcher Settings</string>
|
||||
|
||||
<string name="launcher_notification_settings">Launcher Settings</string>
|
||||
<string name="launcher_notification_update_failed">Failed to check for updates.</string>
|
||||
<string name="launcher_notification_update_cancelled">Update cancelled.</string>
|
||||
<string name="launcher_notification_update_success">Geode updated.</string>
|
||||
|
||||
<string name="launcher_starting_game">Starting Geometry Dash…</string>
|
||||
<string name="launcher_downloading_update">Downloading update…</string>
|
||||
<string name="launcher_downloading_update_details">(%1$s/%2$s)</string>
|
||||
<string name="launcher_cancelled_manual">Launch paused.</string>
|
||||
<string name="launcher_cancelled_crash">Launch paused due to a crash.</string>
|
||||
<string name="launcher_cancelled_error">Could not start Geode.</string>
|
||||
<string name="launcher_cancelled_outdated">A Geometry Dash update is required.</string>
|
||||
<string name="launcher_cancelled_restart">Restart</string>
|
||||
|
||||
<!-- Updater strings -->
|
||||
<string name="release_fetch_failed">Update failed.\n%1$s</string>
|
||||
@@ -146,4 +159,5 @@
|
||||
<string name="application_logs_default_filename" translatable="false">launcher_logcat.txt</string>
|
||||
<string name="application_logs_crash_only">Show Crash Buffer</string>
|
||||
<string name="application_logs_crash_only_enabled_alt">Enabled</string>
|
||||
<string name="title_activity_alt_main">AltMainActivity</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user