add launcher update check

This commit is contained in:
qimiko committed 2024-01-23 03:15:39 -07:00
1 parent 199b6f142b
commit 7f64236e89
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"
} }
} }
+2
View File
@@ -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>