feat: check for updates page in settings

Add a full-screen update checker accessible from a button at the
bottom of the settings page. It queries the fork's GitHub latest
release and can download + install the APK in place.

- UpdateRepository (core/data): fetch releases/latest via GitHub API,
  parse tag/description/APK asset, stream APK download to cacheDir
- VersionComparator (core/domain): compare 4.4.6-ba7opf.N style tags
  (base version first, then build number; v-prefix tolerant) + tests
- IMainContainer/MainContainer: inject updateRepo
- UpdateCheckerScreen (feature/settings): shows current version,
  checks for updates, displays latest version + release notes, and a
  Download & install button; FileProvider hands the APK to the
  system installer
- SettingsScreen: bottom entry button, full-screen page toggle
- AndroidManifest: REQUEST_INSTALL_PACKAGES permission + FileProvider
- i18n strings (en/zh)
This commit is contained in:
atsunatsu committed 2026-08-29 16:25:51 +08:00
1 parent 1904a6b3e6
commit 604c2ec59d
15 files changed
+551 -6

No files matched your search

@@ -35,6 +35,7 @@ import com.rtbishop.look4sat.core.data.repository.SatelliteRepo
import com.rtbishop.look4sat.core.data.repository.SelectionRepo
import com.rtbishop.look4sat.core.data.repository.SensorsRepo
import com.rtbishop.look4sat.core.data.repository.SettingsRepo
import com.rtbishop.look4sat.core.data.repository.UpdateRepository
import com.rtbishop.look4sat.core.data.source.LocalSource
import com.rtbishop.look4sat.core.data.source.RemoteSource
import com.rtbishop.look4sat.core.data.usecase.AddToCalendar
@@ -79,6 +80,7 @@ class MainContainer(private val context: Context) : IMainContainer {
override val satelliteRepo = provideSatelliteRepo()
override val databaseRepo = provideDatabaseRepo()
override val amSatRepo by lazy { AmSatRepository(remoteSource, appScope) }
override val updateRepo by lazy { UpdateRepository(remoteSource) }
override val radioTrackingService: IRadioTrackingService by lazy {
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
RadioTrackingService(appScope, manager, satelliteRepo, settingsRepo)
@@ -0,0 +1,67 @@
package com.rtbishop.look4sat.core.data.repository
import com.rtbishop.look4sat.core.domain.model.LatestRelease
import com.rtbishop.look4sat.core.domain.repository.IUpdateRepository
import com.rtbishop.look4sat.core.domain.source.IRemoteSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.io.File
class UpdateRepository(
private val remoteSource: IRemoteSource
) : IUpdateRepository {
override suspend fun getLatestRelease(): LatestRelease? = withContext(Dispatchers.IO) {
val result = remoteSource.getNetworkStream(LATEST_RELEASE_URL)
val stream = result.stream ?: return@withContext null
try {
val json = stream.bufferedReader().use { it.readText() }
parseRelease(json)
} catch (e: Exception) {
println("UpdateRepository parse failure: $e")
null
}
}
override suspend fun downloadApk(url: String, dest: File): Boolean = withContext(Dispatchers.IO) {
if (url.isBlank()) return@withContext false
val result = remoteSource.getNetworkStream(url)
val stream = result.stream ?: return@withContext false
try {
dest.outputStream().use { out -> stream.use { it.copyTo(out) } }
true
} catch (e: Exception) {
println("UpdateRepository download failure: $e")
false
}
}
private fun parseRelease(json: String): LatestRelease? {
val obj = JSONObject(json)
val tag = obj.optString("tag_name")
if (tag.isBlank()) return null
val assets = obj.optJSONArray("assets")
var apkUrl: String? = null
if (assets != null) {
for (i in 0 until assets.length()) {
val asset = assets.optJSONObject(i) ?: continue
val name = asset.optString("name", "")
if (name.endsWith(".apk")) {
apkUrl = asset.optString("browser_download_url", "").ifBlank { null }
if (apkUrl != null) break
}
}
}
return LatestRelease(
versionTag = tag,
title = obj.optString("name", ""),
body = obj.optString("body", ""),
apkUrl = apkUrl
)
}
private companion object {
const val LATEST_RELEASE_URL = "https://api.github.com/repos/atsunatsu/Look4Sat/releases/latest"
}
}