mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-24 16:59:22 +00:00
show update errors when launch cancelled
This commit is contained in:
3 files changed
+149
-54
No files matched your search
@@ -29,6 +29,11 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@@ -44,6 +49,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalResources
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@@ -55,11 +61,15 @@ import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
|||||||
import com.geode.launcher.ui.theme.LocalTheme
|
import com.geode.launcher.ui.theme.LocalTheme
|
||||||
import com.geode.launcher.ui.theme.Theme
|
import com.geode.launcher.ui.theme.Theme
|
||||||
import com.geode.launcher.ui.theme.launcherTitleStyle
|
import com.geode.launcher.ui.theme.launcherTitleStyle
|
||||||
|
import com.geode.launcher.updater.ReleaseManager
|
||||||
import com.geode.launcher.utils.Constants
|
import com.geode.launcher.utils.Constants
|
||||||
import com.geode.launcher.utils.GamePackageUtils
|
import com.geode.launcher.utils.GamePackageUtils
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.net.ConnectException
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
class AltMainActivity : ComponentActivity() {
|
class AltMainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -157,9 +167,11 @@ fun mapCancelReasonToInfo(cancelReason: LaunchViewModel.LaunchCancelReason): Lau
|
|||||||
LaunchViewModel.LaunchCancelReason.MANUAL-> LaunchStatusInfo(
|
LaunchViewModel.LaunchCancelReason.MANUAL-> LaunchStatusInfo(
|
||||||
title = null
|
title = null
|
||||||
)
|
)
|
||||||
|
/*
|
||||||
else -> LaunchStatusInfo(
|
else -> LaunchStatusInfo(
|
||||||
title = stringResource(R.string.launcher_cancelled_manual)
|
title = stringResource(R.string.launcher_cancelled_manual)
|
||||||
)
|
)
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +433,53 @@ fun GeodeLogo(modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GeodeUpdateIndicator(snackbarHostState: SnackbarHostState, onRetry: () -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val resources = LocalResources.current
|
||||||
|
|
||||||
|
val releaseState = ReleaseManager.get(context).uiState.value
|
||||||
|
|
||||||
|
if (releaseState !is ReleaseManager.ReleaseManagerState.Failure) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val message = when (releaseState.exception) {
|
||||||
|
is UnknownHostException, is ConnectException ->
|
||||||
|
stringResource(R.string.release_fetch_no_internet_short)
|
||||||
|
is ReleaseManager.UpdateException -> {
|
||||||
|
when (releaseState.exception.reason) {
|
||||||
|
ReleaseManager.UpdateException.Reason.EXTERNAL_FILE_IN_USE -> stringResource(
|
||||||
|
R.string.release_fetch_external_in_use_short
|
||||||
|
)
|
||||||
|
else -> stringResource(R.string.release_fetch_generic_short)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is CancellationException -> null
|
||||||
|
else -> stringResource(R.string.release_fetch_generic_short)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(message) {
|
||||||
|
val result = snackbarHostState.showSnackbar(
|
||||||
|
message = message,
|
||||||
|
actionLabel = resources.getString(R.string.release_fetch_button_retry),
|
||||||
|
duration = SnackbarDuration.Indefinite,
|
||||||
|
withDismissAction = true
|
||||||
|
)
|
||||||
|
|
||||||
|
when (result) {
|
||||||
|
SnackbarResult.ActionPerformed -> {
|
||||||
|
onRetry()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AltMainScreen(
|
fun AltMainScreen(
|
||||||
launchViewModel: LaunchViewModel = viewModel(factory = LaunchViewModel.Factory)
|
launchViewModel: LaunchViewModel = viewModel(factory = LaunchViewModel.Factory)
|
||||||
@@ -437,70 +496,81 @@ fun AltMainScreen(
|
|||||||
|
|
||||||
var showErrorInfo by remember { mutableStateOf(false) }
|
var showErrorInfo by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Box(
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
Scaffold(
|
||||||
.safeDrawingPadding(),
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
|
||||||
contentAlignment = Alignment.Center
|
) { innerPadding ->
|
||||||
) {
|
Box(
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.safeDrawingPadding()
|
||||||
|
.padding(innerPadding),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
GeodeLogo()
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
LaunchProgressCard(
|
GeodeLogo()
|
||||||
launchUIState,
|
|
||||||
crashInfo = launchViewModel.currentCrashInfo(),
|
|
||||||
onCancel = {
|
|
||||||
launchInSafeMode = false
|
|
||||||
coroutineScope.launch {
|
|
||||||
launchViewModel.cancelLaunch()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onResume = { safeMode ->
|
|
||||||
launchInSafeMode = safeMode
|
|
||||||
|
|
||||||
launchViewModel.clearCrashInfo()
|
Column(
|
||||||
coroutineScope.launch {
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
launchViewModel.beginLaunchFlow(true)
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
}
|
) {
|
||||||
},
|
LaunchProgressCard(
|
||||||
onMore = {
|
launchUIState,
|
||||||
showErrorInfo = true
|
crashInfo = launchViewModel.currentCrashInfo(),
|
||||||
},
|
onCancel = {
|
||||||
extraOptions = {
|
launchInSafeMode = false
|
||||||
ExtraOptions(
|
coroutineScope.launch {
|
||||||
onSettings = {
|
launchViewModel.cancelLaunch()
|
||||||
coroutineScope.launch {
|
|
||||||
launchViewModel.cancelLaunch(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
onSettings(context)
|
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
},
|
onResume = { safeMode ->
|
||||||
safeModeEnabled = launchInSafeMode
|
launchInSafeMode = safeMode
|
||||||
)
|
|
||||||
|
|
||||||
// only show launcher update in a case where a user won't see it ingame
|
launchViewModel.clearCrashInfo()
|
||||||
if (launchUIState is LaunchViewModel.LaunchUIState.Cancelled) {
|
coroutineScope.launch {
|
||||||
val gameVersion = remember { GamePackageUtils.getGameVersionCodeOrNull(context.packageManager) }
|
launchViewModel.beginLaunchFlow(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onMore = {
|
||||||
|
showErrorInfo = true
|
||||||
|
},
|
||||||
|
extraOptions = {
|
||||||
|
ExtraOptions(
|
||||||
|
onSettings = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
launchViewModel.cancelLaunch(true)
|
||||||
|
}
|
||||||
|
|
||||||
if (gameVersion != null && gameVersion < Constants.SUPPORTED_VERSION_CODE_MIN_WARNING) {
|
onSettings(context)
|
||||||
UnsupportedVersionWarning()
|
}
|
||||||
}
|
)
|
||||||
|
},
|
||||||
|
safeModeEnabled = launchInSafeMode
|
||||||
|
)
|
||||||
|
|
||||||
val nextLauncherUpdate by launchViewModel.nextLauncherUpdate.collectAsState()
|
// only show launcher update in a case where a user won't see it ingame
|
||||||
if (nextLauncherUpdate != null) {
|
if (launchUIState is LaunchViewModel.LaunchUIState.Cancelled) {
|
||||||
LauncherUpdateIndicator()
|
val gameVersion = remember { GamePackageUtils.getGameVersionCodeOrNull(context.packageManager) }
|
||||||
|
|
||||||
|
if (gameVersion != null && gameVersion < Constants.SUPPORTED_VERSION_CODE_MIN_WARNING) {
|
||||||
|
UnsupportedVersionWarning()
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextLauncherUpdate by launchViewModel.nextLauncherUpdate.collectAsState()
|
||||||
|
if (nextLauncherUpdate != null) {
|
||||||
|
LauncherUpdateIndicator()
|
||||||
|
}
|
||||||
|
|
||||||
|
GeodeUpdateIndicator(snackbarHostState) {
|
||||||
|
launchViewModel.retryUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
|||||||
private var isCancelling = false
|
private var isCancelling = false
|
||||||
private var hasManuallyStarted = false
|
private var hasManuallyStarted = false
|
||||||
|
|
||||||
|
private var restoreUpdateState: LaunchUIState? = null
|
||||||
|
|
||||||
private suspend fun determineUpdateStatus() {
|
private suspend fun determineUpdateStatus() {
|
||||||
ReleaseManager.get(application).uiState.takeWhile {
|
ReleaseManager.get(application).uiState.takeWhile {
|
||||||
it is ReleaseManager.ReleaseManagerState.InUpdateCheck || it is ReleaseManager.ReleaseManagerState.InDownload
|
it is ReleaseManager.ReleaseManagerState.InUpdateCheck || it is ReleaseManager.ReleaseManagerState.InDownload
|
||||||
@@ -119,6 +121,13 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
|||||||
|
|
||||||
// flow terminates once update check is finished
|
// flow terminates once update check is finished
|
||||||
hasCheckedForUpdates = true
|
hasCheckedForUpdates = true
|
||||||
|
|
||||||
|
val currentState = restoreUpdateState
|
||||||
|
if (currentState != null) {
|
||||||
|
_uiState.emit(currentState)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
preReadyCheck()
|
preReadyCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +223,17 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
|||||||
preReadyCheck()
|
preReadyCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun retryUpdate() {
|
||||||
|
ReleaseManager.get(application).checkForUpdates(true)
|
||||||
|
if (ReleaseManager.get(application).isInUpdate) {
|
||||||
|
restoreUpdateState = _uiState.value
|
||||||
|
viewModelScope.launch {
|
||||||
|
determineUpdateStatus()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun cancelLaunch(isAutomatic: Boolean = false) {
|
suspend fun cancelLaunch(isAutomatic: Boolean = false) {
|
||||||
// no need to double cancel
|
// no need to double cancel
|
||||||
if (_uiState.value is LaunchUIState.Cancelled) {
|
if (_uiState.value is LaunchUIState.Cancelled) {
|
||||||
|
|||||||
@@ -93,6 +93,11 @@
|
|||||||
<string name="release_fetch_try_later">Please try again later.</string>
|
<string name="release_fetch_try_later">Please try again later.</string>
|
||||||
<string name="release_fetch_manual_check_required">Externally managed library in use, please manually check for updates if you want to overwrite it.</string>
|
<string name="release_fetch_manual_check_required">Externally managed library in use, please manually check for updates if you want to overwrite it.</string>
|
||||||
|
|
||||||
|
<string name="release_fetch_external_in_use_short">Update failed. External library in use.</string>
|
||||||
|
<string name="release_fetch_no_internet_short">Update failed. Internet not connected.</string>
|
||||||
|
<string name="release_fetch_generic_short">Update failed. Please try again later.</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- GeometryDashActivity strings -->
|
<!-- GeometryDashActivity strings -->
|
||||||
<string name="load_failed_link_error">Failed to link libraries</string>
|
<string name="load_failed_link_error">Failed to link libraries</string>
|
||||||
<string name="load_failed_link_error_description">Game libraries could not be found. Try to:</string>
|
<string name="load_failed_link_error_description">Game libraries could not be found. Try to:</string>
|
||||||
|
|||||||
Reference in new issue
Block a user