Add update checker

This commit is contained in:
Syer10
2021-12-04 22:05:10 -05:00
parent aee287ec31
commit 6891708df7
9 changed files with 148 additions and 1 deletions

View File

@@ -86,6 +86,7 @@ jobs:
-Pcompose.desktop.mac.notarization.appleID=${{ secrets.APPLE_ID }} -Pcompose.desktop.mac.notarization.appleID=${{ secrets.APPLE_ID }}
-Pcompose.desktop.mac.notarization.password=${{ secrets.APPLE_PASSWORD }} -Pcompose.desktop.mac.notarization.password=${{ secrets.APPLE_PASSWORD }}
-Pidentity="${{ secrets.APPLE_IDENTITY }}" -Pidentity="${{ secrets.APPLE_IDENTITY }}"
-Ppreview="${{ env.COMMIT_COUNT }}"
--stacktrace --stacktrace
# Upload runner package tar.gz/zip as artifact # Upload runner package tar.gz/zip as artifact

View File

@@ -230,9 +230,11 @@ buildConfig {
buildConfigField("String", "NAME", project.name.wrap()) buildConfigField("String", "NAME", project.name.wrap())
buildConfigField("String", "VERSION", project.version.toString().wrap()) buildConfigField("String", "VERSION", project.version.toString().wrap())
buildConfigField("int", "MIGRATION_CODE", migrationCode.toString()) buildConfigField("int", "MIGRATION_CODE", migrationCode.toString())
buildConfigField("boolean", "DEBUG", project.hasProperty("debugApp").toString())
buildConfigField("boolean", "IS_PREVIEW", project.hasProperty("preview").toString())
buildConfigField("int", "PREVIEW_BUILD", project.properties["preview"]?.toString() ?: 0.toString())
// Tachidesk // Tachidesk
buildConfigField("boolean", "DEBUG", project.hasProperty("debugApp").toString())
buildConfigField("String", "TACHIDESK_SP_VERSION", tachideskVersion.wrap()) buildConfigField("String", "TACHIDESK_SP_VERSION", tachideskVersion.wrap())
buildConfigField("int", "SERVER_CODE", serverCode.toString()) buildConfigField("int", "SERVER_CODE", serverCode.toString())
} }

View File

@@ -32,6 +32,8 @@ import ca.gosyer.data.server.interactions.SourceInteractionHandler
import ca.gosyer.data.translation.ResourceProvider import ca.gosyer.data.translation.ResourceProvider
import ca.gosyer.data.translation.XmlResourceBundle import ca.gosyer.data.translation.XmlResourceBundle
import ca.gosyer.data.ui.UiPreferences import ca.gosyer.data.ui.UiPreferences
import ca.gosyer.data.update.UpdateChecker
import ca.gosyer.data.update.UpdatePreferences
import io.kamel.core.config.KamelConfig import io.kamel.core.config.KamelConfig
import toothpick.ktp.binding.bind import toothpick.ktp.binding.bind
import toothpick.ktp.binding.module import toothpick.ktp.binding.module
@@ -71,6 +73,10 @@ val DataModule = module {
.toProviderInstance { MigrationPreferences(preferenceFactory.create("migration")) } .toProviderInstance { MigrationPreferences(preferenceFactory.create("migration")) }
.providesSingleton() .providesSingleton()
bind<UpdatePreferences>()
.toProviderInstance { UpdatePreferences(preferenceFactory.create("update")) }
.providesSingleton()
bind<Http>() bind<Http>()
.toProvider(HttpProvider::class) .toProvider(HttpProvider::class)
.providesSingleton() .providesSingleton()
@@ -114,4 +120,7 @@ val DataModule = module {
bind<Migrations>() bind<Migrations>()
.toClass<Migrations>() .toClass<Migrations>()
.singleton() .singleton()
bind<UpdateChecker>()
.toClass<UpdateChecker>()
.singleton()
} }

View File

@@ -0,0 +1,61 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.data.update
import ca.gosyer.build.BuildConfig
import ca.gosyer.data.server.Http
import ca.gosyer.data.update.model.GithubRelease
import ca.gosyer.util.lang.launch
import ca.gosyer.util.lang.withIOContext
import io.ktor.client.request.get
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import javax.inject.Inject
class UpdateChecker @Inject constructor(
private val updatePreferences: UpdatePreferences,
private val client: Http
) {
val updateFound = MutableSharedFlow<GithubRelease>()
@OptIn(DelicateCoroutinesApi::class)
fun checkForUpdates() {
if (!updatePreferences.enabled().get()) return
launch {
val latestRelease = withIOContext {
client.get<GithubRelease>("https://api.github.com/repos/$GITHUB_REPO/releases/latest")
}
if (isNewVersion(latestRelease.version)) {
updateFound.emit(latestRelease)
}
}
}
// Thanks to Tachiyomi for inspiration
private fun isNewVersion(versionTag: String): Boolean {
// Removes prefixes like "r" or "v"
val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
return if (BuildConfig.IS_PREVIEW) {
// Preview builds: based on releases in "Suwayomi/Tachidesk-JUI-preview" repo
// tagged as something like "r123"
newVersion.toInt() > BuildConfig.PREVIEW_BUILD
} else {
// Release builds: based on releases in "Suwayomi/Tachidesk-JUI" repo
// tagged as something like "v1.1.2"
newVersion != BuildConfig.VERSION
}
}
companion object {
private val GITHUB_REPO = if (BuildConfig.IS_PREVIEW) {
"Suwayomi/Tachidesk-JUI-preview"
} else {
"Suwayomi/Tachidesk-JUI"
}
}
}

View File

@@ -0,0 +1,16 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.data.update
import ca.gosyer.common.prefs.Preference
import ca.gosyer.common.prefs.PreferenceStore
class UpdatePreferences(private val preferenceStore: PreferenceStore) {
fun enabled(): Preference<Boolean> {
return preferenceStore.getBoolean("enabled", true)
}
}

View File

@@ -0,0 +1,17 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.data.update.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GithubRelease(
@SerialName("tag_name") val version: String,
@SerialName("body") val info: String,
@SerialName("html_url") val releaseLink: String
)

View File

@@ -12,6 +12,7 @@ import androidx.compose.material.Surface
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -21,6 +22,7 @@ import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type import androidx.compose.ui.input.key.type
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.window.Notification
import androidx.compose.ui.window.Tray import androidx.compose.ui.window.Tray
import androidx.compose.ui.window.Window import androidx.compose.ui.window.Window
import androidx.compose.ui.window.awaitApplication import androidx.compose.ui.window.awaitApplication
@@ -35,6 +37,7 @@ import ca.gosyer.data.server.ServerService.ServerResult
import ca.gosyer.data.translation.XmlResourceBundle import ca.gosyer.data.translation.XmlResourceBundle
import ca.gosyer.data.ui.UiPreferences import ca.gosyer.data.ui.UiPreferences
import ca.gosyer.data.ui.model.ThemeMode import ca.gosyer.data.ui.model.ThemeMode
import ca.gosyer.data.update.UpdateChecker
import ca.gosyer.ui.base.WindowDialog import ca.gosyer.ui.base.WindowDialog
import ca.gosyer.ui.base.components.LoadingScreen import ca.gosyer.ui.base.components.LoadingScreen
import ca.gosyer.ui.base.prefs.asStateIn import ca.gosyer.ui.base.prefs.asStateIn
@@ -55,7 +58,9 @@ import io.kamel.image.config.LocalKamelConfig
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
import org.jetbrains.skiko.SystemTheme import org.jetbrains.skiko.SystemTheme
import org.jetbrains.skiko.currentSystemTheme import org.jetbrains.skiko.currentSystemTheme
import toothpick.configuration.Configuration import toothpick.configuration.Configuration
@@ -89,6 +94,7 @@ suspend fun main() {
val serverService = scope.getInstance<ServerService>() val serverService = scope.getInstance<ServerService>()
val uiPreferences = scope.getInstance<UiPreferences>() val uiPreferences = scope.getInstance<UiPreferences>()
val updateChecker = scope.getInstance<UpdateChecker>()
// Call setDefault before getting a resource bundle // Call setDefault before getting a resource bundle
val language = uiPreferences.language().get() val language = uiPreferences.language().get()
@@ -165,6 +171,21 @@ suspend fun main() {
} }
) )
LaunchedEffect(Unit) {
launch {
updateChecker.checkForUpdates()
updateChecker.updateFound.collect {
trayState.sendNotification(
Notification(
resources.getStringA("new_update_title"),
resources.getString("new_update_message", it.version),
Notification.Type.Info
)
)
}
}
}
Window( Window(
onCloseRequest = { onCloseRequest = {
if (confirmExit.value) { if (confirmExit.value) {

View File

@@ -9,15 +9,30 @@ package ca.gosyer.ui.settings
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import ca.gosyer.data.update.UpdatePreferences
import ca.gosyer.ui.base.components.MenuController import ca.gosyer.ui.base.components.MenuController
import ca.gosyer.ui.base.components.Toolbar import ca.gosyer.ui.base.components.Toolbar
import ca.gosyer.ui.base.prefs.SwitchPreference
import ca.gosyer.ui.base.resources.stringResource import ca.gosyer.ui.base.resources.stringResource
import ca.gosyer.ui.base.vm.ViewModel
import ca.gosyer.ui.base.vm.viewModel
import javax.inject.Inject
class SettingsAdvancedViewModel @Inject constructor(
updatePreferences: UpdatePreferences,
) : ViewModel() {
val updatesEnabled = updatePreferences.enabled().asStateFlow()
}
@Composable @Composable
fun SettingsAdvancedScreen(menuController: MenuController) { fun SettingsAdvancedScreen(menuController: MenuController) {
val vm = viewModel<SettingsAdvancedViewModel>()
Column { Column {
Toolbar(stringResource("settings_advanced_screen"), menuController, true) Toolbar(stringResource("settings_advanced_screen"), menuController, true)
LazyColumn { LazyColumn {
item {
SwitchPreference(preference = vm.updatesEnabled, title = stringResource("update_checker"))
}
} }
} }
} }

View File

@@ -12,6 +12,8 @@
<string name="unable_to_start_server">Cannot start server</string> <string name="unable_to_start_server">Cannot start server</string>
<string name="confirm_exit">Confirm exit</string> <string name="confirm_exit">Confirm exit</string>
<string name="confirm_exit_message">Are you sure you want to exit?</string> <string name="confirm_exit_message">Are you sure you want to exit?</string>
<string name="new_update_title">Tachidesk-JUI update available</string>
<string name="new_update_message">%1$s is now available!</string>
<!-- Actions --> <!-- Actions -->
<string name="action_yes">Yes</string> <string name="action_yes">Yes</string>
@@ -219,4 +221,7 @@
<string name="digest_auth">Digest auth</string> <string name="digest_auth">Digest auth</string>
<string name="auth_username">Auth username</string> <string name="auth_username">Auth username</string>
<string name="auth_password">Auth password</string> <string name="auth_password">Auth password</string>
<!-- Advanced Settings -->
<string name="update_checker">Check for updates</string>
</resources> </resources>