mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-22 08:42:38 +00:00
proper initial state check
This commit is contained in:
4 files changed
+71
-39
No files matched your search
@@ -151,6 +151,8 @@ class MainActivity : ComponentActivity() {
|
||||
launchViewModel.launchArguments = launchArguments
|
||||
}
|
||||
|
||||
launchViewModel.beginLaunchFlow()
|
||||
|
||||
CompositionLocalProvider(LocalTheme provides theme) {
|
||||
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption, dynamicColor = !dynamicColorOption) {
|
||||
Surface(
|
||||
@@ -176,6 +178,7 @@ fun LaunchCancelledIcon(cancelReason: LaunchViewModel.LaunchCancelReason, modifi
|
||||
LaunchViewModel.LaunchCancelReason.GAME_OUTDATED,
|
||||
LaunchViewModel.LaunchCancelReason.GAME_MISSING -> Icon(painterResource(R.drawable.icon_error), contentDescription = null, modifier)
|
||||
LaunchViewModel.LaunchCancelReason.LAST_LAUNCH_CRASHED,
|
||||
LaunchViewModel.LaunchCancelReason.LAST_LAUNCH_CRASHED_AUTOMATIC,
|
||||
LaunchViewModel.LaunchCancelReason.GEODE_NOT_FOUND -> Icon(painterResource(R.drawable.icon_warning), contentDescription = null, modifier)
|
||||
else -> Icon(painterResource(R.drawable.icon_info), contentDescription = null, modifier)
|
||||
}
|
||||
@@ -187,6 +190,9 @@ fun mapCancelReasonToInfo(cancelReason: LaunchViewModel.LaunchCancelReason): Lau
|
||||
LaunchViewModel.LaunchCancelReason.LAST_LAUNCH_CRASHED -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.launcher_cancelled_crash)
|
||||
)
|
||||
LaunchViewModel.LaunchCancelReason.LAST_LAUNCH_CRASHED_AUTOMATIC -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.launcher_cancelled_crash_instant)
|
||||
)
|
||||
LaunchViewModel.LaunchCancelReason.GEODE_NOT_FOUND -> LaunchStatusInfo(
|
||||
title = stringResource(R.string.geode_download_title)
|
||||
)
|
||||
@@ -323,6 +329,7 @@ fun RetryButtonContents(reason: LaunchViewModel.LaunchCancelReason) {
|
||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||
Text(stringResource(R.string.launcher_cancelled_resume))
|
||||
}
|
||||
LaunchViewModel.LaunchCancelReason.LAST_LAUNCH_CRASHED_AUTOMATIC,
|
||||
LaunchViewModel.LaunchCancelReason.MANUAL,
|
||||
LaunchViewModel.LaunchCancelReason.AUTOMATIC -> {
|
||||
Icon(
|
||||
@@ -659,10 +666,6 @@ fun AltMainScreen(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
launchViewModel.beginLaunchFlow()
|
||||
}
|
||||
|
||||
val launchUIState by launchViewModel.uiState.collectAsState()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var launchInSafeMode by remember {
|
||||
|
||||
@@ -65,17 +65,25 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
}
|
||||
|
||||
enum class LaunchCancelReason {
|
||||
MANUAL, AUTOMATIC, LAST_LAUNCH_CRASHED, GEODE_NOT_FOUND, GAME_MISSING, GAME_OUTDATED;
|
||||
MANUAL, AUTOMATIC, LAST_LAUNCH_CRASHED, LAST_LAUNCH_CRASHED_AUTOMATIC,
|
||||
GEODE_NOT_FOUND, GAME_MISSING, GAME_OUTDATED;
|
||||
|
||||
fun allowsRetry() = when (this) {
|
||||
MANUAL,
|
||||
AUTOMATIC,
|
||||
LAST_LAUNCH_CRASHED,
|
||||
LAST_LAUNCH_CRASHED_AUTOMATIC,
|
||||
GEODE_NOT_FOUND -> true
|
||||
GAME_MISSING,
|
||||
GAME_OUTDATED -> false
|
||||
}
|
||||
|
||||
fun isCrash() = when (this) {
|
||||
LAST_LAUNCH_CRASHED,
|
||||
LAST_LAUNCH_CRASHED_AUTOMATIC -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun isGameInstallIssue() = when (this) {
|
||||
GAME_OUTDATED,
|
||||
GAME_MISSING -> true
|
||||
@@ -100,20 +108,7 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private val _uiState: MutableStateFlow<LaunchUIState>
|
||||
|
||||
init {
|
||||
val preferenceUtils = PreferenceUtils.get(application)
|
||||
val initialCancel = !preferenceUtils.getBoolean(PreferenceUtils.Key.UPDATE_AUTOMATICALLY)
|
||||
&& !preferenceUtils.getBoolean(PreferenceUtils.Key.LOAD_AUTOMATICALLY)
|
||||
|
||||
// fixes a single frame of progress being displayed when we're not actually going to do work
|
||||
_uiState = MutableStateFlow<LaunchUIState>(
|
||||
if (initialCancel) LaunchUIState.Cancelled(LaunchCancelReason.AUTOMATIC)
|
||||
else LaunchUIState.Initial
|
||||
)
|
||||
}
|
||||
|
||||
private val _uiState = MutableStateFlow<LaunchUIState>(LaunchUIState.Initial)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
val nextLauncherUpdate = ReleaseManager.get(application).availableLauncherUpdateTag
|
||||
@@ -153,7 +148,7 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
|
||||
fun currentCrashInfo(): LoadFailureInfo? {
|
||||
val currentState = _uiState.value
|
||||
if (currentState is LaunchUIState.Cancelled && currentState.reason == LaunchCancelReason.LAST_LAUNCH_CRASHED) {
|
||||
if (currentState is LaunchUIState.Cancelled && currentState.reason.isCrash()) {
|
||||
return loadFailure
|
||||
}
|
||||
|
||||
@@ -162,12 +157,12 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
|
||||
fun clearCrashInfo() {
|
||||
val currentState = _uiState.value
|
||||
if (currentState is LaunchUIState.Cancelled && currentState.reason == LaunchCancelReason.LAST_LAUNCH_CRASHED) {
|
||||
if (currentState is LaunchUIState.Cancelled && currentState.reason.isCrash()) {
|
||||
loadFailure = null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun preReadyCheck() {
|
||||
private fun preReadyCheck() {
|
||||
if (isCancelling) {
|
||||
// don't let the game start if cancelled
|
||||
return
|
||||
@@ -176,7 +171,8 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
val packageManager = application.packageManager
|
||||
|
||||
if (!GamePackageUtils.isGameInstalled(packageManager)) {
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_MISSING))
|
||||
_uiState.tryEmit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_MISSING))
|
||||
return
|
||||
}
|
||||
|
||||
if (ReleaseManager.get(application).isInUpdate) {
|
||||
@@ -186,17 +182,25 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
return
|
||||
}
|
||||
|
||||
if (loadFailure != null) {
|
||||
// last launch crashed, so cancel
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.LAST_LAUNCH_CRASHED))
|
||||
if (loadFailure != null) {
|
||||
// show a different message if this is the first thing users see
|
||||
val cancelType = if (_uiState.value == LaunchUIState.Initial) {
|
||||
LaunchCancelReason.LAST_LAUNCH_CRASHED_AUTOMATIC
|
||||
} else LaunchCancelReason.LAST_LAUNCH_CRASHED
|
||||
|
||||
_uiState.tryEmit(LaunchUIState.Cancelled(cancelType))
|
||||
return
|
||||
}
|
||||
|
||||
if (GamePackageUtils.getGameVersionCode(packageManager) < Constants.SUPPORTED_VERSION_CODE_MIN) {
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_OUTDATED))
|
||||
_uiState.tryEmit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_OUTDATED))
|
||||
return
|
||||
}
|
||||
|
||||
if (!LaunchUtils.isGeodeInstalled(application)) {
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GEODE_NOT_FOUND))
|
||||
_uiState.tryEmit(LaunchUIState.Cancelled(LaunchCancelReason.GEODE_NOT_FOUND))
|
||||
return
|
||||
}
|
||||
|
||||
// if forcing immediate launch, then act as if it's manually started (skips timers)
|
||||
@@ -212,13 +216,14 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
}
|
||||
|
||||
if (!readyTimerPassed && !hasManuallyStarted) {
|
||||
return _uiState.emit(LaunchUIState.Working)
|
||||
_uiState.tryEmit(LaunchUIState.Working)
|
||||
return
|
||||
}
|
||||
|
||||
_uiState.emit(LaunchUIState.Ready)
|
||||
_uiState.tryEmit(LaunchUIState.Ready)
|
||||
}
|
||||
|
||||
suspend fun beginLaunchFlow(isRestart: Boolean = false) {
|
||||
fun beginLaunchFlow(isRestart: Boolean = false) {
|
||||
if (isRestart) {
|
||||
hasManuallyStarted = true
|
||||
}
|
||||
@@ -234,17 +239,23 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
isCancelling = false
|
||||
|
||||
if (!GamePackageUtils.isGameInstalled(application.packageManager)) {
|
||||
return _uiState.emit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_MISSING))
|
||||
_uiState.tryEmit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_MISSING))
|
||||
return
|
||||
}
|
||||
|
||||
if (readyTimer == null) {
|
||||
initReadyTimer()
|
||||
}
|
||||
|
||||
val releaseManager = ReleaseManager.get(application)
|
||||
|
||||
val hasGeode = LaunchUtils.isGeodeInstalled(application)
|
||||
val shouldUpdate = PreferenceUtils.get(application).getBoolean(PreferenceUtils.Key.UPDATE_AUTOMATICALLY)
|
||||
|
||||
if ((shouldUpdate && !hasCheckedForUpdates) || !hasGeode) {
|
||||
ReleaseManager.get(application).checkForUpdates(false)
|
||||
releaseManager.checkForUpdates(false)
|
||||
} else {
|
||||
releaseManager.afterUseCachedUpdate()
|
||||
}
|
||||
|
||||
preReadyCheck()
|
||||
@@ -261,7 +272,7 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun cancelLaunch(isAutomatic: Boolean = false) {
|
||||
fun cancelLaunch(isAutomatic: Boolean = false) {
|
||||
// no need to double cancel
|
||||
if (_uiState.value is LaunchUIState.Cancelled) {
|
||||
return
|
||||
@@ -273,11 +284,14 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
||||
|
||||
val releaseManager = ReleaseManager.get(application)
|
||||
if (releaseManager.isInUpdate) {
|
||||
_uiState.emit(LaunchUIState.Cancelled(reason, true))
|
||||
_uiState.tryEmit(LaunchUIState.Cancelled(reason, true))
|
||||
|
||||
viewModelScope.launch {
|
||||
releaseManager.cancelUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
_uiState.emit(LaunchUIState.Cancelled(reason, false))
|
||||
_uiState.tryEmit(LaunchUIState.Cancelled(reason, false))
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
||||
@@ -266,7 +266,7 @@ class ReleaseManager private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkCachedLauncherUpdate() {
|
||||
fun checkCachedLauncherUpdate() {
|
||||
val lastUpdate = PreferenceUtils.get(applicationContext)
|
||||
.getString(PreferenceUtils.Key.LAST_LAUNCHER_UPDATE) ?: return
|
||||
|
||||
@@ -283,15 +283,25 @@ class ReleaseManager private constructor(
|
||||
return downloadLauncherUpdate(update)
|
||||
}
|
||||
|
||||
private suspend fun checkForNewRelease(isManual: Boolean = false) {
|
||||
fun shouldUseCache(): Boolean {
|
||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||
val lastCheckTime = sharedPreferences.getLong(PreferenceUtils.Key.LAST_UPDATE_CHECK_TIME)
|
||||
|
||||
// only check for updates if it's been over 15 minutes since last check
|
||||
val checkMinTime = Clock.System.now().minus(15.minutes).toEpochMilliseconds()
|
||||
if (!isManual && lastCheckTime > checkMinTime && !fileWasExternallyModified()) {
|
||||
return lastCheckTime > checkMinTime && !fileWasExternallyModified()
|
||||
}
|
||||
|
||||
fun afterUseCachedUpdate() {
|
||||
checkCachedLauncherUpdate()
|
||||
_uiState.value = ReleaseManagerState.Finished()
|
||||
_uiState.tryEmit(ReleaseManagerState.Finished())
|
||||
}
|
||||
|
||||
private suspend fun checkForNewRelease(isManual: Boolean = false) {
|
||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||
|
||||
if (!isManual && shouldUseCache()) {
|
||||
afterUseCachedUpdate()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -388,10 +398,14 @@ class ReleaseManager private constructor(
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun checkForUpdates(isManual: Boolean = false): StateFlow<ReleaseManagerState> {
|
||||
if (!isInUpdate) {
|
||||
if (isManual || !shouldUseCache()) {
|
||||
_uiState.value = ReleaseManagerState.InUpdateCheck
|
||||
updateJob = GlobalScope.launch {
|
||||
checkForNewRelease(isManual)
|
||||
}
|
||||
} else {
|
||||
afterUseCachedUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
return _uiState.asStateFlow()
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
<string name="launcher_cancelled_manual">Launch paused.</string>
|
||||
<string name="launcher_cancelled_navigated_away">Launch paused.</string>
|
||||
<string name="launcher_cancelled_crash">Launch paused due to a crash.</string>
|
||||
<string name="launcher_cancelled_crash_instant">Geode crashed.</string>
|
||||
<string name="launcher_cancelled_error">Geode cannot be used.</string>
|
||||
<string name="launcher_cancelled_outdated">A Geometry Dash update is required.</string>
|
||||
<string name="launcher_cancelled_restart">Restart</string>
|
||||
|
||||
Reference in new issue
Block a user