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.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
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
|
||||
fun UpdateProgressIndicator(
|
||||
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) {
|
||||
@@ -474,7 +528,8 @@ fun MainScreen(
|
||||
|
||||
UpdateCard(
|
||||
releaseViewModel,
|
||||
modifier = Modifier.padding(12.dp)
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ class LogViewModel: ViewModel() {
|
||||
private val _lineState = mutableStateListOf<LogLine>()
|
||||
val lineState = _lineState
|
||||
|
||||
private var _isLoading = MutableStateFlow(false)
|
||||
var isLoading = _isLoading.asStateFlow()
|
||||
private val _isLoading = MutableStateFlow(false)
|
||||
val isLoading = _isLoading.asStateFlow()
|
||||
|
||||
var logJob: Job? = null
|
||||
private var logJob: Job? = null
|
||||
|
||||
var filterCrashes = false
|
||||
private set
|
||||
|
||||
@@ -23,7 +23,8 @@ class Release(
|
||||
val tagName: String,
|
||||
val createdAt: Instant,
|
||||
val publishedAt: Instant,
|
||||
val assets: List<Asset>
|
||||
val assets: List<Asset>,
|
||||
val htmlUrl: String
|
||||
) {
|
||||
fun getDescription(): String {
|
||||
if (tagName == "nightly") {
|
||||
@@ -42,7 +43,7 @@ class Release(
|
||||
return createdAt.epochSeconds
|
||||
}
|
||||
|
||||
fun getAndroidDownload(): Asset? {
|
||||
fun getGeodeDownload(): Asset? {
|
||||
// try to find an asset that matches the architecture first
|
||||
val platform = LaunchUtils.platformName
|
||||
|
||||
@@ -51,4 +52,22 @@ class Release(
|
||||
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.util.Log
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.geode.launcher.BuildConfig
|
||||
import com.geode.launcher.utils.DownloadUtils
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
@@ -77,6 +79,9 @@ class ReleaseManager private constructor(
|
||||
val isInUpdate: Boolean
|
||||
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) {
|
||||
_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? {
|
||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||
val useNightly = sharedPreferences.getBoolean(PreferenceUtils.Key.RELEASE_CHANNEL)
|
||||
@@ -95,7 +109,7 @@ class ReleaseManager private constructor(
|
||||
}
|
||||
|
||||
private suspend fun performUpdate(release: Release) {
|
||||
val releaseAsset = release.getAndroidDownload()
|
||||
val releaseAsset = release.getGeodeDownload()
|
||||
if (releaseAsset == null) {
|
||||
val noAssetException = Exception("missing Android download")
|
||||
_uiState.value = ReleaseManagerState.Failure(noAssetException)
|
||||
@@ -153,6 +167,24 @@ class ReleaseManager private constructor(
|
||||
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) {
|
||||
val release = try {
|
||||
getLatestRelease()
|
||||
@@ -161,6 +193,17 @@ class ReleaseManager private constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val launcherRelease = try {
|
||||
releaseRepository.getLatestLauncherRelease()
|
||||
} catch (e: Exception) {
|
||||
sendError(e)
|
||||
return
|
||||
}
|
||||
|
||||
if (launcherRelease != null) {
|
||||
checkLauncherUpdate(launcherRelease)
|
||||
}
|
||||
|
||||
if (release == null) {
|
||||
_uiState.value = ReleaseManagerState.Finished()
|
||||
return
|
||||
|
||||
@@ -43,8 +43,6 @@ class ReleaseRepository(private val httpClient: OkHttpClient) {
|
||||
.addHeader(GITHUB_API_HEADER, GITHUB_API_VERSION)
|
||||
.build()
|
||||
|
||||
println("fetching url $url")
|
||||
|
||||
val call = httpClient.newCall(request)
|
||||
val response = call.executeCoroutine()
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.AP
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -52,6 +53,8 @@ class ReleaseViewModel(private val application: Application): ViewModel() {
|
||||
val isInUpdate
|
||||
get() = ReleaseManager.get(application).isInUpdate
|
||||
|
||||
val nextLauncherUpdate = ReleaseManager.get(application).availableLauncherUpdate
|
||||
|
||||
var hasPerformedCheck = false
|
||||
private set
|
||||
|
||||
@@ -77,7 +80,7 @@ class ReleaseViewModel(private val application: Application): ViewModel() {
|
||||
fun useGlobalCheckState() {
|
||||
hasPerformedCheck = true
|
||||
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val releaseFlow = ReleaseManager.get(application)
|
||||
.uiState
|
||||
|
||||
@@ -88,11 +91,17 @@ class ReleaseViewModel(private val application: Application): ViewModel() {
|
||||
fun runReleaseCheck(isManual: Boolean = false) {
|
||||
hasPerformedCheck = true
|
||||
|
||||
viewModelScope.launch {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val releaseFlow = ReleaseManager.get(application)
|
||||
.checkForUpdates(isManual)
|
||||
|
||||
syncUiState(releaseFlow)
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissLauncherUpdate() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
ReleaseManager.get(application).dismissLauncherUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
||||
THEME,
|
||||
BLACK_BACKGROUND,
|
||||
CURRENT_RELEASE_MODIFIED,
|
||||
LAST_DISMISSED_UPDATE
|
||||
}
|
||||
|
||||
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
||||
@@ -120,6 +121,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
||||
Key.THEME -> "PreferenceTheme"
|
||||
Key.BLACK_BACKGROUND -> "PreferenceBlackBackground"
|
||||
Key.CURRENT_RELEASE_MODIFIED -> "PreferenceReleaseModifiedHash"
|
||||
Key.LAST_DISMISSED_UPDATE -> "PreferenceLastDismissedUpdate"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user