From b80cdadbb4e349e3e200389768d06745e0d51f56 Mon Sep 17 00:00:00 2001 From: Danielle Voznyy Date: Fri, 31 May 2024 14:27:53 -0400 Subject: [PATCH] feat: Update checked, with support for stable and snapshot builds feat: Show launchy version in top bar --- gradle.properties | 2 +- .../com/mineinabyss/launchy/data/Constants.kt | 3 + .../launchy/logic/GithubUpdateChecker.kt | 57 +++++++++++++++++++ .../com/mineinabyss/launchy/ui/TopBar.kt | 3 +- .../mineinabyss/launchy/ui/screens/Screens.kt | 25 +++++++- 5 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/mineinabyss/launchy/logic/GithubUpdateChecker.kt diff --git a/gradle.properties b/gradle.properties index f3198da..f0b47e7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ group=com.mineinabyss -version=2.0.0-beta.1 +version=2.0.0-beta.2 idofrontVersion=0.24.3 diff --git a/src/main/kotlin/com/mineinabyss/launchy/data/Constants.kt b/src/main/kotlin/com/mineinabyss/launchy/data/Constants.kt index e03f891..f4ad58d 100644 --- a/src/main/kotlin/com/mineinabyss/launchy/data/Constants.kt +++ b/src/main/kotlin/com/mineinabyss/launchy/data/Constants.kt @@ -5,4 +5,7 @@ import androidx.compose.ui.unit.dp object Constants { val SETTINGS_HORIZONTAL_PADDING = 10.dp val SETTINGS_PRIMARY_BUTTON_WIDTH = 140.dp + + val APP_VERSION = System.getProperty("jpackage.app-version") ?: null + val GITHUB_REPO = "MineInAbyss/launchy" } diff --git a/src/main/kotlin/com/mineinabyss/launchy/logic/GithubUpdateChecker.kt b/src/main/kotlin/com/mineinabyss/launchy/logic/GithubUpdateChecker.kt new file mode 100644 index 0000000..ba42475 --- /dev/null +++ b/src/main/kotlin/com/mineinabyss/launchy/logic/GithubUpdateChecker.kt @@ -0,0 +1,57 @@ +package com.mineinabyss.launchy.logic + +import com.mineinabyss.launchy.data.Constants +import com.mineinabyss.launchy.data.Formats +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlinx.serialization.Serializable + +sealed interface AppUpdateState { + data object Unknown : AppUpdateState + data object NoUpdate : AppUpdateState + data class UpdateAvailable(val release: GithubRelease) : AppUpdateState +} + +object GithubUpdateChecker { + suspend fun checkForUpdates(): AppUpdateState = runCatching { + val currVersion = Constants.APP_VERSION ?: return AppUpdateState.Unknown + val currTag = "v$currVersion" + val current = Downloader.httpClient.get { + url("https://api.github.com/repos/${Constants.GITHUB_REPO}/releases/tags/$currTag") + } + val currentRelease = if (current.status != HttpStatusCode.OK) null + else Formats.json.decodeFromString(current.bodyAsText()) + + val latest = if (currentRelease?.prerelease == true) getLatestPrerelease() + else getLatestStableRelease() + println("Current $currentRelease") + println("Latest $latest") + return if (latest.tag_name == currTag) AppUpdateState.NoUpdate else AppUpdateState.UpdateAvailable(latest) + }.getOrDefault(AppUpdateState.Unknown) + + suspend fun getLatestPrerelease(): GithubRelease { + val response = Downloader.httpClient.get { + url("https://api.github.com/repos/${Constants.GITHUB_REPO}/releases") + } + + val releases = Formats.json.decodeFromString>(response.bodyAsText()) + return releases.maxBy { it.published_at } + } + + suspend fun getLatestStableRelease(): GithubRelease { + val response = Downloader.httpClient.get { + url("https://api.github.com/repos/${Constants.GITHUB_REPO}/releases/latest") + } + return Formats.json.decodeFromString(response.bodyAsText()) + } +} + + +@Serializable +data class GithubRelease( + val published_at: String, + val tag_name: String, + val html_url: String, + val prerelease: Boolean, +) diff --git a/src/main/kotlin/com/mineinabyss/launchy/ui/TopBar.kt b/src/main/kotlin/com/mineinabyss/launchy/ui/TopBar.kt index aee00f9..487205d 100644 --- a/src/main/kotlin/com/mineinabyss/launchy/ui/TopBar.kt +++ b/src/main/kotlin/com/mineinabyss/launchy/ui/TopBar.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.WindowPlacement import com.mineinabyss.launchy.LocalLaunchyState +import com.mineinabyss.launchy.data.Constants import com.mineinabyss.launchy.ui.elements.BetterWindowDraggableArea import com.mineinabyss.launchy.ui.state.TopBarState @@ -128,7 +129,7 @@ fun LaunchyTitle() { tint = MaterialTheme.colorScheme.primary ) Text( - "Launchy", + "Launchy - ${Constants.APP_VERSION ?: "dev"}", fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.primary ) diff --git a/src/main/kotlin/com/mineinabyss/launchy/ui/screens/Screens.kt b/src/main/kotlin/com/mineinabyss/launchy/ui/screens/Screens.kt index 794e4d7..dae0914 100644 --- a/src/main/kotlin/com/mineinabyss/launchy/ui/screens/Screens.kt +++ b/src/main/kotlin/com/mineinabyss/launchy/ui/screens/Screens.kt @@ -5,6 +5,8 @@ import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.* import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.LocalTextStyle +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Update import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -12,6 +14,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.mineinabyss.launchy.LocalLaunchyState +import com.mineinabyss.launchy.logic.AppUpdateState +import com.mineinabyss.launchy.logic.DesktopHelpers +import com.mineinabyss.launchy.logic.GithubUpdateChecker import com.mineinabyss.launchy.state.InProgressTask import com.mineinabyss.launchy.state.modpack.GameInstanceState import com.mineinabyss.launchy.ui.AppTopBar @@ -31,6 +36,7 @@ import com.mineinabyss.launchy.ui.state.TopBar var screen: Screen by mutableStateOf(Screen.Default) var dialog: Dialog by mutableStateOf(Dialog.None) +var updateAvailable: AppUpdateState by mutableStateOf(AppUpdateState.Unknown) private val ModpackStateProvider = compositionLocalOf { error("No local modpack provided") } @@ -41,7 +47,20 @@ val LocalGameInstanceState: GameInstanceState @Composable fun Screens() = Scaffold( - snackbarHost = { SnackbarHost(snackbarHostState) } + snackbarHost = { SnackbarHost(snackbarHostState) }, + floatingActionButton = { + val update = updateAvailable + Row { + AnimatedVisibility(update is AppUpdateState.UpdateAvailable) { + if (update !is AppUpdateState.UpdateAvailable) return@AnimatedVisibility + ExtendedFloatingActionButton( + text = { Text("Update available") }, + icon = { Icon(Icons.Rounded.Update, "") }, + onClick = { DesktopHelpers.browse(update.release.html_url) }, + ) + } + } + } ) { val state = LocalLaunchyState val packState = state.instanceState @@ -66,6 +85,10 @@ fun Screens() = Scaffold( LaunchedEffect(isDefault, state.ui.preferHue) { if (isDefault) currentHue = state.ui.preferHue } + LaunchedEffect(Unit) { + updateAvailable = GithubUpdateChecker.checkForUpdates() + } + AppTopBar( state = TopBar,