mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Add update checker
This commit is contained in:
1
.github/workflows/Preview.yml
vendored
1
.github/workflows/Preview.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
61
src/main/kotlin/ca/gosyer/data/update/UpdateChecker.kt
Normal file
61
src/main/kotlin/ca/gosyer/data/update/UpdateChecker.kt
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/kotlin/ca/gosyer/data/update/UpdatePreferences.kt
Normal file
16
src/main/kotlin/ca/gosyer/data/update/UpdatePreferences.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/kotlin/ca/gosyer/data/update/model/GithubRelease.kt
Normal file
17
src/main/kotlin/ca/gosyer/data/update/model/GithubRelease.kt
Normal 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
|
||||||
|
)
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user