separate launcher details for update check

This commit is contained in:
Chloe committed 2026-02-01 21:24:19 -07:00
1 parent 9d4c27ac92
commit 1d7cd9594d
6 files changed
+97 -63

No files matched your search

@@ -56,7 +56,7 @@ fun determineDisplayedCards(context: Context): List<LaunchNotificationType> {
val releaseManager = ReleaseManager.get(context) val releaseManager = ReleaseManager.get(context)
val loadAutomatically = PreferenceUtils.get(context).getBoolean(PreferenceUtils.Key.LOAD_AUTOMATICALLY) val loadAutomatically = PreferenceUtils.get(context).getBoolean(PreferenceUtils.Key.LOAD_AUTOMATICALLY)
val availableUpdate = releaseManager.availableLauncherUpdate.value val availableUpdate = releaseManager.availableLauncherUpdateTag.value
if (loadAutomatically && availableUpdate != null) { if (loadAutomatically && availableUpdate != null) {
cards.add(LaunchNotificationType.LAUNCHER_UPDATE_AVAILABLE) cards.add(LaunchNotificationType.LAUNCHER_UPDATE_AVAILABLE)
} }
@@ -116,7 +116,7 @@ class LaunchViewModel(private val application: Application): ViewModel() {
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
val nextLauncherUpdate = ReleaseManager.get(application).availableLauncherUpdate val nextLauncherUpdate = ReleaseManager.get(application).availableLauncherUpdateTag
var loadFailure: LoadFailureInfo? = null var loadFailure: LoadFailureInfo? = null
var launchArguments: LaunchArguments? = null var launchArguments: LaunchArguments? = null
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
@@ -18,6 +19,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -253,7 +255,11 @@ fun LauncherUpdateDialog(isCancelling: Boolean, onDismiss: () -> Unit) {
fun LauncherUpdateInformation(onDismiss: () -> Unit) { fun LauncherUpdateInformation(onDismiss: () -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val nextRelease = remember { ReleaseManager.get(context).availableLauncherUpdate.value } val releaseManager = remember { ReleaseManager.get(context) }
val nextReleaseName by releaseManager.availableLauncherUpdateTag.collectAsState()
val nextRelease by releaseManager.availableLauncherUpdateDetails.collectAsState()
val sheetState = rememberModalBottomSheetState() val sheetState = rememberModalBottomSheetState()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@@ -273,75 +279,88 @@ fun LauncherUpdateInformation(onDismiss: () -> Unit) {
}) })
} }
if (nextRelease != null) { val nextReleaseNameSaved = nextReleaseName
if (nextReleaseNameSaved != null) {
ModalBottomSheet(onDismissRequest = { onDismiss() }, sheetState = sheetState) { ModalBottomSheet(onDismissRequest = { onDismiss() }, sheetState = sheetState) {
Box(Modifier.verticalScroll(rememberScrollState())) { Box(Modifier.verticalScroll(rememberScrollState())) {
Column(modifier = Modifier.padding(horizontal = 24.dp)) { Column(modifier = Modifier.padding(horizontal = 24.dp)) {
Text( Text(
stringResource(R.string.launcher_update_name, nextRelease.release.tagName), stringResource(R.string.launcher_update_name, nextReleaseNameSaved),
style = Typography.headlineSmall, style = Typography.headlineSmall,
) )
val releasedTime = remember { val nextRelease = nextRelease
DateFormat.getDateInstance().format( if (nextRelease != null) {
Date.from(nextRelease.release.publishedAt.toJavaInstant()) val releasedTime = remember {
) DateFormat.getDateInstance().format(
} Date.from(nextRelease.release.publishedAt.toJavaInstant())
Text(
stringResource(R.string.launcher_update_released, "$releasedTime"),
style = Typography.labelLarge
)
Row(modifier = Modifier.padding(vertical = 12.dp)) {
Button(
onClick = {
updateJob = coroutineScope.launch {
cancellingUpdate = false
beginUpdate = true
installLauncherUpdate(context)
}
},
) {
Icon(painterResource(R.drawable.icon_download), contentDescription = null)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.launcher_install))
}
val uriHandler = LocalUriHandler.current
IconButton(onClick = { uriHandler.openUri(nextRelease.release.htmlUrl) }) {
Icon(
painterResource(R.drawable.icon_link),
stringResource(R.string.launcher_update_external_link)
) )
} }
}
if (nextRelease.release.body != null) {
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
Text( Text(
stringResource(R.string.launcher_update_changelog), stringResource(R.string.launcher_update_released, "$releasedTime"),
style = Typography.titleLarge style = Typography.labelLarge
) )
// every new release of the markdown library adds another arg to it Row(modifier = Modifier.padding(vertical = 12.dp)) {
CompositionLocalProvider(LocalBulletListHandler provides { _, _, _, _, _ -> "•  " }) { Button(
Markdown( onClick = {
content = nextRelease.release.body.replace("\r", ""), updateJob = coroutineScope.launch {
typography = markdownTypography( cancellingUpdate = false
textLink = TextLinkStyles( beginUpdate = true
style = MaterialTheme.typography.bodyLarge.copy(
textDecoration = TextDecoration.Underline, installLauncherUpdate(context)
color = MaterialTheme.colorScheme.primary, }
).toSpanStyle() },
), ) {
), Icon(painterResource(R.drawable.icon_download), contentDescription = null)
flavour = GFMFlavourDescriptor(), Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.launcher_install))
}
val uriHandler = LocalUriHandler.current
IconButton(onClick = { uriHandler.openUri(nextRelease.release.htmlUrl) }) {
Icon(
painterResource(R.drawable.icon_link),
stringResource(R.string.launcher_update_external_link)
)
}
}
if (nextRelease.release.body != null) {
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
Text(
stringResource(R.string.launcher_update_changelog),
style = Typography.titleLarge
) )
// every new release of the markdown library adds another arg to it
CompositionLocalProvider(LocalBulletListHandler provides { _, _, _, _, _ -> "•  " }) {
Markdown(
content = nextRelease.release.body.replace("\r", ""),
typography = markdownTypography(
textLink = TextLinkStyles(
style = MaterialTheme.typography.bodyLarge.copy(
textDecoration = TextDecoration.Underline,
color = MaterialTheme.colorScheme.primary,
).toSpanStyle()
),
),
flavour = GFMFlavourDescriptor(),
)
}
}
} else {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
) {
CircularProgressIndicator()
} }
} }
@@ -48,8 +48,19 @@ data class LoaderPayload<T>(
data class DownloadableAsset(val url: String, val filename: String, val size: Long? = null) data class DownloadableAsset(val url: String, val filename: String, val size: Long? = null)
abstract class Downloadable { abstract class Downloadable {
/**
* Human-readable representation of the current version.
*/
abstract fun getDescription(): String abstract fun getDescription(): String
/**
* Unique value that refers to the current version, used for comparison.
*/
abstract fun getDescriptor(): Long abstract fun getDescriptor(): Long
/**
* Release download link for the current platform
*/
abstract fun getDownload(): DownloadableAsset? abstract fun getDownload(): DownloadableAsset?
} }
@@ -84,8 +84,11 @@ 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<DownloadableLauncherRelease?>(null) private val _availableLauncherUpdateTag = MutableStateFlow<String?>(null)
val availableLauncherUpdate = _availableLauncherUpdate.asStateFlow() val availableLauncherUpdateTag = _availableLauncherUpdateTag.asStateFlow()
private val _availableLauncherUpdateDetails = MutableStateFlow<DownloadableLauncherRelease?>(null)
val availableLauncherUpdateDetails = _availableLauncherUpdateDetails.asStateFlow()
private fun sendError(e: Exception) { private fun sendError(e: Exception) {
_uiState.value = ReleaseManagerState.Failure(e) _uiState.value = ReleaseManagerState.Failure(e)
@@ -252,12 +255,13 @@ class ReleaseManager private constructor(
val launcherRelease = getLatestLauncherUpdate() ?: return val launcherRelease = getLatestLauncherUpdate() ?: return
if (launcherRelease.release.tagName != BuildConfig.VERSION_NAME) { if (launcherRelease.release.tagName != BuildConfig.VERSION_NAME) {
_availableLauncherUpdate.value = launcherRelease _availableLauncherUpdateTag.value = launcherRelease.getDescription()
_availableLauncherUpdateDetails.value = launcherRelease
} }
} }
suspend fun downloadLatestLauncherUpdate(): File? { suspend fun downloadLatestLauncherUpdate(): File? {
val update = _availableLauncherUpdate.value val update = _availableLauncherUpdateDetails.value
?: getLatestLauncherUpdate() ?: getLatestLauncherUpdate()
?: return null ?: return null
@@ -53,7 +53,7 @@ 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 val nextLauncherUpdate = ReleaseManager.get(application).availableLauncherUpdateTag
var hasPerformedCheck = false var hasPerformedCheck = false
private set private set