Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

dependenciesInfo {
includeInApk = false
includeInBundle = false
}

signingConfigs {
create("release") {
val ksFile = System.getenv("KEYSTORE_FILE")
Expand Down
81 changes: 81 additions & 0 deletions app/src/main/kotlin/app/floatdeck/data/UpdateChecker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package app.floatdeck.data

import android.content.Context
import app.floatdeck.BuildConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.net.URL

data class ReleaseInfo(
val tagName: String,
val name: String,
val htmlUrl: String,
val body: String,
)

/** 检查 GitHub Releases 是否有新版本。 */
object UpdateChecker {

private const val REPO_API = "https://api.github.com/repos/kxxoling/FloatDeck/releases/latest"

/** 获取安装来源包名,null 表示侧载。 */
fun getInstallerPackageName(context: Context): String? {
return try {
context.packageManager.getInstallerPackageName(context.packageName)
} catch (_: Exception) {
null
}
}

/** 是否应该检查更新(仅侧载用户)。 */
fun shouldCheckForUpdate(context: Context): Boolean {
val installer = getInstallerPackageName(context) ?: return true
return installer !in setOf(
"org.fdroid.fdroid",
"com.android.vending",
"com.amazon.venezia",
"com.huawei.appmarket",
"com.xiaomi.market",
"com.samsung.android.onestore",
)
}

/** 检查 GitHub 最新 Release,返回 null 表示当前已是最新。 */
suspend fun checkForUpdate(): ReleaseInfo? =
withContext(Dispatchers.IO) {
try {
val json = URL(REPO_API).readText()
val obj = JSONObject(json)
val remoteTag = obj.optString("tag_name", "") ?: return@withContext null
val currentVersion = BuildConfig.VERSION_NAME

if (isNewer(remoteTag, currentVersion)) {
ReleaseInfo(
tagName = remoteTag,
name = obj.optString("name", remoteTag),
htmlUrl = obj.optString("html_url", ""),
body = obj.optString("body", ""),
)
} else {
null
}
} catch (_: Exception) {
null
}
}

/** 简单版本比较:去除 'v' 前缀后比较语义化版本。 */
private fun isNewer(remote: String, current: String): Boolean {
val r = remote.removePrefix("v").split(".").mapNotNull { it.toIntOrNull() }
val c = current.removePrefix("v").split(".").mapNotNull { it.toIntOrNull() }
val maxLen = maxOf(r.size, c.size)
for (i in 0 until maxLen) {
val rv = r.getOrElse(i) { 0 }
val cv = c.getOrElse(i) { 0 }
if (rv > cv) return true
if (rv < cv) return false
}
return false
}
}
69 changes: 69 additions & 0 deletions app/src/main/kotlin/app/floatdeck/settings/SettingsActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
Expand Down Expand Up @@ -53,6 +56,7 @@ import app.floatdeck.data.RemoteTemplateLoader
import app.floatdeck.data.SettingsRepository
import app.floatdeck.data.TemplateDef
import app.floatdeck.data.TemplateLoadException
import app.floatdeck.data.UpdateChecker
import app.floatdeck.service.FloatDeckWallpaperService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -444,6 +448,71 @@ fun SettingsScreen(
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp),
)

// Update check (side-load users only)
if (UpdateChecker.shouldCheckForUpdate(context)) {
var updateChecking by remember { mutableStateOf(false) }
var updateAvailable by remember { mutableStateOf<app.floatdeck.data.ReleaseInfo?>(null) }

Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
updateChecking = true
scope.launch {
val release = UpdateChecker.checkForUpdate()
updateChecking = false
if (release != null) {
updateAvailable = release
} else {
Toast.makeText(
context,
context.getString(R.string.already_latest),
Toast.LENGTH_SHORT,
).show()
}
}
},
enabled = !updateChecking,
) {
Text(stringResource(R.string.check_for_updates))
}
if (updateChecking) {
CircularProgressIndicator(modifier = Modifier.padding(top = 8.dp))
}

if (updateAvailable != null) {
AlertDialog(
onDismissRequest = { updateAvailable = null },
title = { Text(stringResource(R.string.update_available)) },
text = {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text(updateAvailable!!.tagName)
if (updateAvailable!!.body.isNotBlank()) {
Spacer(modifier = Modifier.height(8.dp))
Text(updateAvailable!!.body)
}
}
},
confirmButton = {
TextButton(
onClick = {
context.startActivity(
Intent(Intent.ACTION_VIEW, Uri.parse(updateAvailable!!.htmlUrl)),
)
updateAvailable = null
},
) {
Text(stringResource(R.string.view_release))
}
},
dismissButton = {
TextButton(onClick = { updateAvailable = null }) {
Text(stringResource(android.R.string.cancel))
}
},
)
}
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@
<string name="error_missing_portrait">Missing portrait file: %s</string>
<string name="error_dir_json_not_found">template.json not found in directory</string>
<string name="error_dir_json_invalid">template.json format is invalid</string>
<string name="check_for_updates">Check for Updates</string>
<string name="already_latest">Already up to date</string>
<string name="update_available">Update Available</string>
<string name="view_release">View Release</string>
</resources>
1 change: 1 addition & 0 deletions fastlane/metadata/android/en-US/changelogs/1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Initial release.
Binary file added fastlane/metadata/android/en-US/images/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading