mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-21 16:32:39 +00:00
add launcher update check
This commit is contained in:
8 files changed
+139
-11
No files matched your search
@@ -28,6 +28,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.LocalUriHandler
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -94,6 +95,43 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LauncherUpdateIndicator(modifier: Modifier = Modifier, openTo: String, onDismiss: () -> Unit) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
|
ElevatedCard(modifier) {
|
||||||
|
Column(
|
||||||
|
// buttons add enough padding already, lower to compensate
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
top = 20.dp,
|
||||||
|
start = 12.dp,
|
||||||
|
end = 12.dp,
|
||||||
|
bottom = 8.dp
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.launcher_update_available),
|
||||||
|
modifier = Modifier.padding(horizontal = 10.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(modifier = Modifier.align(Alignment.End)) {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(R.string.launcher_update_dismiss))
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.size(4.dp))
|
||||||
|
|
||||||
|
TextButton(onClick = { uriHandler.openUri(openTo) },) {
|
||||||
|
Text(stringResource(R.string.launcher_download))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdateProgressIndicator(
|
fun UpdateProgressIndicator(
|
||||||
message: String,
|
message: String,
|
||||||
@@ -322,6 +360,22 @@ fun UpdateCard(releaseViewModel: ReleaseViewModel, modifier: Modifier = Modifier
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val nextUpdate by releaseViewModel.nextLauncherUpdate.collectAsState()
|
||||||
|
val nextUpdateValue = nextUpdate
|
||||||
|
|
||||||
|
if (!releaseViewModel.isInUpdate && nextUpdateValue != null) {
|
||||||
|
val updateUrl = nextUpdateValue.getLauncherDownload()?.browserDownloadUrl
|
||||||
|
?: nextUpdateValue.htmlUrl
|
||||||
|
|
||||||
|
LauncherUpdateIndicator(
|
||||||
|
modifier = modifier,
|
||||||
|
openTo = updateUrl,
|
||||||
|
onDismiss = {
|
||||||
|
releaseViewModel.dismissLauncherUpdate()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLaunch(context: Context) {
|
fun onLaunch(context: Context) {
|
||||||
@@ -474,7 +528,8 @@ fun MainScreen(
|
|||||||
|
|
||||||
UpdateCard(
|
UpdateCard(
|
||||||
releaseViewModel,
|
releaseViewModel,
|
||||||
modifier = Modifier.padding(12.dp)
|
modifier = Modifier
|
||||||
|
.padding(12.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ class LogViewModel: ViewModel() {
|
|||||||
private val _lineState = mutableStateListOf<LogLine>()
|
private val _lineState = mutableStateListOf<LogLine>()
|
||||||
val lineState = _lineState
|
val lineState = _lineState
|
||||||
|
|
||||||
private var _isLoading = MutableStateFlow(false)
|
private val _isLoading = MutableStateFlow(false)
|
||||||
var isLoading = _isLoading.asStateFlow()
|
val isLoading = _isLoading.asStateFlow()
|
||||||
|
|
||||||
var logJob: Job? = null
|
private var logJob: Job? = null
|
||||||
|
|
||||||
var filterCrashes = false
|
var filterCrashes = false
|
||||||
private set
|
private set
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class Release(
|
|||||||
val tagName: String,
|
val tagName: String,
|
||||||
val createdAt: Instant,
|
val createdAt: Instant,
|
||||||
val publishedAt: Instant,
|
val publishedAt: Instant,
|
||||||
val assets: List<Asset>
|
val assets: List<Asset>,
|
||||||
|
val htmlUrl: String
|
||||||
) {
|
) {
|
||||||
fun getDescription(): String {
|
fun getDescription(): String {
|
||||||
if (tagName == "nightly") {
|
if (tagName == "nightly") {
|
||||||
@@ -42,7 +43,7 @@ class Release(
|
|||||||
return createdAt.epochSeconds
|
return createdAt.epochSeconds
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAndroidDownload(): Asset? {
|
fun getGeodeDownload(): Asset? {
|
||||||
// try to find an asset that matches the architecture first
|
// try to find an asset that matches the architecture first
|
||||||
val platform = LaunchUtils.platformName
|
val platform = LaunchUtils.platformName
|
||||||
|
|
||||||
@@ -51,4 +52,22 @@ class Release(
|
|||||||
it.name.endsWith(releaseSuffix)
|
it.name.endsWith(releaseSuffix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getLauncherDownload(): Asset? {
|
||||||
|
val platform = LaunchUtils.platformName
|
||||||
|
val use32BitPlatform = platform == "android32"
|
||||||
|
|
||||||
|
return assets.find { asset ->
|
||||||
|
/* you know it's good when you pull out the truth table
|
||||||
|
* u32bp | contains | found
|
||||||
|
* 1 | 1 | 1
|
||||||
|
* 0 | 1 | 0
|
||||||
|
* 1 | 0 | 0
|
||||||
|
* 0 | 0 | 1
|
||||||
|
* surprise! it's an xnor
|
||||||
|
*/
|
||||||
|
|
||||||
|
(asset.name.contains("android32")) == use32BitPlatform
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,8 @@ package com.geode.launcher.updater
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import com.geode.launcher.BuildConfig
|
||||||
import com.geode.launcher.utils.DownloadUtils
|
import com.geode.launcher.utils.DownloadUtils
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
@@ -77,6 +79,9 @@ class ReleaseManager private constructor(
|
|||||||
val isInUpdate: Boolean
|
val isInUpdate: Boolean
|
||||||
get() = _uiState.value !is ReleaseManagerState.Failure && _uiState.value !is ReleaseManagerState.Finished
|
get() = _uiState.value !is ReleaseManagerState.Failure && _uiState.value !is ReleaseManagerState.Finished
|
||||||
|
|
||||||
|
private val _availableLauncherUpdate = MutableStateFlow<Release?>(null)
|
||||||
|
val availableLauncherUpdate = _availableLauncherUpdate.asStateFlow()
|
||||||
|
|
||||||
private fun sendError(e: Exception) {
|
private fun sendError(e: Exception) {
|
||||||
_uiState.value = ReleaseManagerState.Failure(e)
|
_uiState.value = ReleaseManagerState.Failure(e)
|
||||||
|
|
||||||
@@ -87,6 +92,15 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dismissLauncherUpdate() {
|
||||||
|
val update = availableLauncherUpdate.value ?: return
|
||||||
|
|
||||||
|
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||||
|
sharedPreferences.setString(PreferenceUtils.Key.LAST_DISMISSED_UPDATE, update.tagName)
|
||||||
|
|
||||||
|
_availableLauncherUpdate.value = null
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getLatestRelease(): Release? {
|
private suspend fun getLatestRelease(): Release? {
|
||||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||||
val useNightly = sharedPreferences.getBoolean(PreferenceUtils.Key.RELEASE_CHANNEL)
|
val useNightly = sharedPreferences.getBoolean(PreferenceUtils.Key.RELEASE_CHANNEL)
|
||||||
@@ -95,7 +109,7 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun performUpdate(release: Release) {
|
private suspend fun performUpdate(release: Release) {
|
||||||
val releaseAsset = release.getAndroidDownload()
|
val releaseAsset = release.getGeodeDownload()
|
||||||
if (releaseAsset == null) {
|
if (releaseAsset == null) {
|
||||||
val noAssetException = Exception("missing Android download")
|
val noAssetException = Exception("missing Android download")
|
||||||
_uiState.value = ReleaseManagerState.Failure(noAssetException)
|
_uiState.value = ReleaseManagerState.Failure(noAssetException)
|
||||||
@@ -153,6 +167,24 @@ class ReleaseManager private constructor(
|
|||||||
return originalFileHash != currentFileHash
|
return originalFileHash != currentFileHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkLauncherUpdate(launcherUpdate: Release) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
println("ignoring launcher update check - in debug mode!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||||
|
val lastDismissedVersion = sharedPreferences.getString(PreferenceUtils.Key.LAST_DISMISSED_UPDATE)
|
||||||
|
|
||||||
|
if (!lastDismissedVersion.isNullOrEmpty() && launcherUpdate.tagName == lastDismissedVersion) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (launcherUpdate.tagName != BuildConfig.VERSION_NAME) {
|
||||||
|
_availableLauncherUpdate.value = launcherUpdate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun checkForNewRelease(allowOverwriting: Boolean = false) {
|
private suspend fun checkForNewRelease(allowOverwriting: Boolean = false) {
|
||||||
val release = try {
|
val release = try {
|
||||||
getLatestRelease()
|
getLatestRelease()
|
||||||
@@ -161,6 +193,17 @@ class ReleaseManager private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val launcherRelease = try {
|
||||||
|
releaseRepository.getLatestLauncherRelease()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sendError(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (launcherRelease != null) {
|
||||||
|
checkLauncherUpdate(launcherRelease)
|
||||||
|
}
|
||||||
|
|
||||||
if (release == null) {
|
if (release == null) {
|
||||||
_uiState.value = ReleaseManagerState.Finished()
|
_uiState.value = ReleaseManagerState.Finished()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ class ReleaseRepository(private val httpClient: OkHttpClient) {
|
|||||||
.addHeader(GITHUB_API_HEADER, GITHUB_API_VERSION)
|
.addHeader(GITHUB_API_HEADER, GITHUB_API_VERSION)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
println("fetching url $url")
|
|
||||||
|
|
||||||
val call = httpClient.newCall(request)
|
val call = httpClient.newCall(request)
|
||||||
val response = call.executeCoroutine()
|
val response = call.executeCoroutine()
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.AP
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -52,6 +53,8 @@ class ReleaseViewModel(private val application: Application): ViewModel() {
|
|||||||
val isInUpdate
|
val isInUpdate
|
||||||
get() = ReleaseManager.get(application).isInUpdate
|
get() = ReleaseManager.get(application).isInUpdate
|
||||||
|
|
||||||
|
val nextLauncherUpdate = ReleaseManager.get(application).availableLauncherUpdate
|
||||||
|
|
||||||
var hasPerformedCheck = false
|
var hasPerformedCheck = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -77,7 +80,7 @@ class ReleaseViewModel(private val application: Application): ViewModel() {
|
|||||||
fun useGlobalCheckState() {
|
fun useGlobalCheckState() {
|
||||||
hasPerformedCheck = true
|
hasPerformedCheck = true
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val releaseFlow = ReleaseManager.get(application)
|
val releaseFlow = ReleaseManager.get(application)
|
||||||
.uiState
|
.uiState
|
||||||
|
|
||||||
@@ -88,11 +91,17 @@ class ReleaseViewModel(private val application: Application): ViewModel() {
|
|||||||
fun runReleaseCheck(isManual: Boolean = false) {
|
fun runReleaseCheck(isManual: Boolean = false) {
|
||||||
hasPerformedCheck = true
|
hasPerformedCheck = true
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val releaseFlow = ReleaseManager.get(application)
|
val releaseFlow = ReleaseManager.get(application)
|
||||||
.checkForUpdates(isManual)
|
.checkForUpdates(isManual)
|
||||||
|
|
||||||
syncUiState(releaseFlow)
|
syncUiState(releaseFlow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dismissLauncherUpdate() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
ReleaseManager.get(application).dismissLauncherUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -99,6 +99,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
|||||||
THEME,
|
THEME,
|
||||||
BLACK_BACKGROUND,
|
BLACK_BACKGROUND,
|
||||||
CURRENT_RELEASE_MODIFIED,
|
CURRENT_RELEASE_MODIFIED,
|
||||||
|
LAST_DISMISSED_UPDATE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
||||||
@@ -120,6 +121,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
|||||||
Key.THEME -> "PreferenceTheme"
|
Key.THEME -> "PreferenceTheme"
|
||||||
Key.BLACK_BACKGROUND -> "PreferenceBlackBackground"
|
Key.BLACK_BACKGROUND -> "PreferenceBlackBackground"
|
||||||
Key.CURRENT_RELEASE_MODIFIED -> "PreferenceReleaseModifiedHash"
|
Key.CURRENT_RELEASE_MODIFIED -> "PreferenceReleaseModifiedHash"
|
||||||
|
Key.LAST_DISMISSED_UPDATE -> "PreferenceLastDismissedUpdate"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
<string name="launcher_unsupported_version_description">
|
<string name="launcher_unsupported_version_description">
|
||||||
The current version of Geometry Dash is not compatible with the version supported by the launcher (%2$s). A launcher update is required to fix this.\nLaunch will continue, but crashes may occur!
|
The current version of Geometry Dash is not compatible with the version supported by the launcher (%2$s). A launcher update is required to fix this.\nLaunch will continue, but crashes may occur!
|
||||||
</string>
|
</string>
|
||||||
|
<string name="launcher_update_available">A new launcher update is available!</string>
|
||||||
|
<string name="launcher_update_dismiss">Dismiss</string>
|
||||||
|
|
||||||
<!-- Updater strings -->
|
<!-- Updater strings -->
|
||||||
<string name="release_fetch_failed">Update failed.\n%1$s</string>
|
<string name="release_fetch_failed">Update failed.\n%1$s</string>
|
||||||
|
|||||||
Reference in new issue
Block a user