Compare commits
2 Commits
dd07ca4395
...
4b1ae0c473
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b1ae0c473 | |||
| 4a4e45fcd9 |
@@ -6,11 +6,12 @@ import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import se.hagstedt.kvitt.BuildConfig
|
||||
import se.hagstedt.kvitt.data.di.AppBuildInfo
|
||||
import se.hagstedt.kvitt.link.RELEASE_SERVER_URL
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Both variants: the app's version (§10.6) and build type for `:data`'s `DataModule`, which Hilt installs in debug and
|
||||
* release alike (it binds no `:core:model` interface, D-041).
|
||||
* Both variants: the app's version (§10.6), build type and release server ([RELEASE_SERVER_URL], D-121, D-122) for
|
||||
* `:data`'s `DataModule`, which Hilt installs in debug and release alike (it binds no `:core:model` interface, D-041).
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@@ -20,5 +21,6 @@ object AppBuildInfoModule {
|
||||
versionCode = BuildConfig.VERSION_CODE,
|
||||
versionName = BuildConfig.VERSION_NAME,
|
||||
debug = BuildConfig.DEBUG,
|
||||
releaseServerUrl = RELEASE_SERVER_URL,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ object FakeScenarios {
|
||||
clockSkew = false,
|
||||
syncRunning = false,
|
||||
update = UpdateState.None,
|
||||
about = AboutState("dev", 1, "https://icehack.asuscomm.com:8443", null, null),
|
||||
about = AboutState("dev", 1, "https://kvitt.example.org:8443", null, null),
|
||||
online = true,
|
||||
today = "2026-10-17",
|
||||
now = "2026-10-17T06:00:00.000Z",
|
||||
|
||||
@@ -116,7 +116,7 @@ object SettingsScenarios {
|
||||
devices = devices,
|
||||
invites = listOf(pendingDeviceInvite),
|
||||
lastSuccessAt = "2026-10-17T11:59:00.000Z",
|
||||
about = AboutState("1.1", 11, "https://icehack.asuscomm.com:8443", "1.1.0", 3),
|
||||
about = AboutState("1.1", 11, "https://kvitt.example.org:8443", "1.1.0", 3),
|
||||
today = "2026-10-17",
|
||||
now = "2026-10-17T12:02:00.000Z",
|
||||
defaultAccountId = huset.id,
|
||||
|
||||
@@ -87,8 +87,11 @@ import javax.inject.Provider
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Singleton
|
||||
|
||||
/** What `:app` provides: its version (§10.6) and build type (AC-2.8, AC-16.5). */
|
||||
data class AppBuildInfo(val versionCode: Int, val versionName: String, val debug: Boolean)
|
||||
/**
|
||||
* What `:app` provides: its version (§10.6), build type (AC-2.8, AC-16.5) and release server `https://<host>:8443`
|
||||
* (`RELEASE_SERVER_URL`, from the Gradle property `kvitt.publicHost`, D-121), which S-22 shows before pairing (D-122).
|
||||
*/
|
||||
data class AppBuildInfo(val versionCode: Int, val versionName: String, val debug: Boolean, val releaseServerUrl: String)
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@@ -110,9 +113,6 @@ annotation class DataScope
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DataModule {
|
||||
/** §8.5: release builds use https only (the app's network security config); the default server of AC-2.7. */
|
||||
const val DEFAULT_SERVER_URL = "https://icehack.asuscomm.com:8443"
|
||||
|
||||
@Provides @Singleton @DataScope
|
||||
fun scope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
|
||||
@@ -276,7 +276,7 @@ object DataModule {
|
||||
|
||||
@Provides @Singleton
|
||||
fun about(credentials: CredentialStore, session: SessionManager, api: KvittApi, gate: OnlineGate, build: AppBuildInfo): AboutRepositoryImpl =
|
||||
AboutRepositoryImpl(credentials, session.serverVersion, api, gate, build.versionName, build.versionCode, DEFAULT_SERVER_URL)
|
||||
AboutRepositoryImpl(credentials, session.serverVersion, api, gate, build.versionName, build.versionCode, build.releaseServerUrl)
|
||||
}
|
||||
|
||||
/** The `:core:model` interfaces bound to `:data`. Not installed automatically: `:app` includes it in its release graph. */
|
||||
|
||||
@@ -388,7 +388,8 @@ class AboutRepositoryImpl(
|
||||
private val gate: OnlineGate,
|
||||
private val versionName: String,
|
||||
private val versionCode: Int,
|
||||
private val defaultServerUrl: String,
|
||||
/** Shown while no server is paired: `AppBuildInfo.releaseServerUrl`, `https://<kvitt.publicHost>:8443` (D-122). */
|
||||
private val releaseServerUrl: String,
|
||||
) : AboutRepository {
|
||||
override fun about(): Flow<AboutState> {
|
||||
val health = flow {
|
||||
@@ -396,7 +397,7 @@ class AboutRepositoryImpl(
|
||||
emit((gate.run({ api.health() }) { it } as? OnlineResult.Ok)?.value)
|
||||
}
|
||||
return combine(credentials.record, serverVersion, health) { record, header, h ->
|
||||
AboutState(versionName, versionCode, record.baseUrl ?: defaultServerUrl, h?.serverVersion ?: header, h?.schemaVersion)
|
||||
AboutState(versionName, versionCode, record.baseUrl ?: releaseServerUrl, h?.serverVersion ?: header, h?.schemaVersion)
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ class RepositoriesTest {
|
||||
h.server.restHandler = { _, path ->
|
||||
if (path == "/health") HttpStatusCode.OK to """{"status":"ok","server_version":"1.2.0","schema_version":3,"db_ok":true}""" else null
|
||||
}
|
||||
val repo = AboutRepositoryImpl(h.credentials, kotlinx.coroutines.flow.MutableStateFlow("1.1.0"), h.api, h.gate, "1.1", 11, "https://default.test")
|
||||
val repo = AboutRepositoryImpl(h.credentials, kotlinx.coroutines.flow.MutableStateFlow("1.1.0"), h.api, h.gate, "1.1", 11, RELEASE_SERVER_URL)
|
||||
val state = repo.about().first { it.schemaVersion != null }
|
||||
assertEquals("1.2.0", state.serverVersion)
|
||||
assertEquals(3, state.schemaVersion)
|
||||
@@ -387,7 +387,7 @@ class RepositoriesTest {
|
||||
fun about_offlineOrFailingHealth_fallsBackToTheHeaderVersion() = runSync {
|
||||
val header = kotlinx.coroutines.flow.MutableStateFlow<String?>("1.1.0")
|
||||
h.monitor.state.value = false
|
||||
val repo = AboutRepositoryImpl(h.credentials, header, h.api, h.gate, "1.1", 11, "https://default.test")
|
||||
val repo = AboutRepositoryImpl(h.credentials, header, h.api, h.gate, "1.1", 11, RELEASE_SERVER_URL)
|
||||
val offline = repo.about().first()
|
||||
assertEquals("1.1.0", offline.serverVersion)
|
||||
assertNull(offline.schemaVersion)
|
||||
@@ -400,6 +400,14 @@ class RepositoriesTest {
|
||||
assertEquals("1.1.0", repo.about().first().serverVersion)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun about_unpaired_showsTheReleaseServerOfTheBuild() = runSync {
|
||||
h.credentials.clear()
|
||||
h.monitor.state.value = false
|
||||
val repo = AboutRepositoryImpl(h.credentials, kotlinx.coroutines.flow.MutableStateFlow<String?>(null), h.api, h.gate, "1.1", 11, RELEASE_SERVER_URL)
|
||||
assertEquals(RELEASE_SERVER_URL, repo.about().first().serverUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun categoryReorder_sendsOnePatchPerChangedSortOrder() = runSync {
|
||||
val patched = mutableListOf<Pair<String, Int?>>()
|
||||
@@ -519,5 +527,7 @@ class RepositoriesTest {
|
||||
|
||||
private companion object {
|
||||
const val OLDER = "0192a003-0000-7000-8000-000000000002"
|
||||
/** `AppBuildInfo.releaseServerUrl` of the tests: a fixed host, not `:app`'s `kvitt.publicHost` (D-122). */
|
||||
const val RELEASE_SERVER_URL = "https://kvitt.example.org:8443"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 25 KiB |
@@ -278,7 +278,9 @@ HTTP to the registry.
|
||||
### Publishing an APK: `infra/publish-apk.sh`
|
||||
|
||||
Version rule (SPEC §10.6): `version_code` = `git rev-list --count HEAD`, `version_name` = `YYYY.MM.DD-<short sha>`
|
||||
of HEAD's commit date (Europe/Stockholm), passed as `-Pkvitt.versionCode` and `-Pkvitt.versionName`. The image
|
||||
of HEAD's commit date (Europe/Stockholm), passed as `-Pkvitt.versionCode` and `-Pkvitt.versionName`; the script
|
||||
reads `KVITT_PUBLIC_HOST` from `infra/pi/.env` (refusing a missing file or key or a `CHANGE_ME` value) and passes it as
|
||||
`-Pkvitt.publicHost`, the app's release server and invite-link host (D-121). The image
|
||||
tags of the Gitea workflow (`v1.0.N`) are separate and say nothing about the APK. The script refuses a dirty tree,
|
||||
builds `:app:assembleRelease` through `tools/gradle-serial.sh` (JDK 25 on `PATH`), refuses an unsigned APK, and
|
||||
writes into `infra/pi/out/apk/`: `kvitt-<version_code>.apk`, `kvitt-<version_code>.mapping.txt` (the R8 mapping,
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
# 1. refuses a dirty working tree (the version is derived from HEAD)
|
||||
# 2. version (SPEC §10.6): version_code = `git rev-list --count HEAD`, version_name = YYYY.MM.DD-<short sha> of
|
||||
# HEAD's commit date (Europe/Stockholm), passed as -Pkvitt.versionCode / -Pkvitt.versionName
|
||||
# 3. :app:assembleRelease through tools/gradle-serial.sh (JDK 25 from Homebrew on PATH, JAVA_HOME untouched)
|
||||
# 4. writes into infra/pi/out/apk/: kvitt-<version_code>.apk, kvitt-<version_code>.mapping.txt (R8 mapping, for
|
||||
# 3. reads KVITT_PUBLIC_HOST from infra/pi/.env (read only; refuses a missing file or key, or a CHANGE_ME value) and
|
||||
# passes it as -Pkvitt.publicHost, the host of the app's release server and invite links (D-121)
|
||||
# 4. :app:assembleRelease through tools/gradle-serial.sh (JDK 25 from Homebrew on PATH, JAVA_HOME untouched)
|
||||
# 5. writes into infra/pi/out/apk/: kvitt-<version_code>.apk, kvitt-<version_code>.mapping.txt (R8 mapping, for
|
||||
# reading stack traces; never served), and latest.json (written to a temp file, then renamed)
|
||||
# 5. keeps the newest 5 APKs (and their mappings) in infra/pi/out/apk/
|
||||
# 6. keeps the newest 5 APKs (and their mappings) in infra/pi/out/apk/
|
||||
# Then the owner copies the folder to the Pi and runs /opt/kvitt/install-apk.sh there (infra/README.md).
|
||||
# Refuses an unsigned build: without the kvitt.release.* keys in android/local.properties the APK cannot be
|
||||
# installed over an earlier release (D-024, D-101).
|
||||
@@ -21,10 +23,22 @@ version_code="$(git rev-list --count HEAD)"
|
||||
short_sha="$(git rev-parse --short=7 HEAD)"
|
||||
commit_day="$(TZ=Europe/Stockholm git show -s --date=format-local:%Y.%m.%d --format=%cd HEAD)"
|
||||
version_name="$commit_day-$short_sha"
|
||||
echo "Release $version_name (version_code $version_code)"
|
||||
|
||||
# The public host the app is built for (D-121), the same value and checks as infra/pi/install.sh (D-120).
|
||||
env_file="$repo_root/infra/pi/.env"
|
||||
[[ -f "$env_file" ]] || { echo "publish-apk.sh: no infra/pi/.env; cp infra/pi/.env.example infra/pi/.env and set KVITT_PUBLIC_HOST." >&2; exit 1; }
|
||||
KVITT_PUBLIC_HOST="$(sed -n 's/^KVITT_PUBLIC_HOST=//p' "$env_file" | tail -n 1)"
|
||||
[[ -n "$KVITT_PUBLIC_HOST" ]] || { echo "publish-apk.sh: infra/pi/.env has no KVITT_PUBLIC_HOST (or it is empty)." >&2; exit 1; }
|
||||
[[ "$KVITT_PUBLIC_HOST" != CHANGE_ME* ]] || { echo "publish-apk.sh: KVITT_PUBLIC_HOST in infra/pi/.env is still $KVITT_PUBLIC_HOST." >&2; exit 1; }
|
||||
[[ "$KVITT_PUBLIC_HOST" =~ ^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?\.[A-Za-z]{2,}$ ]] \
|
||||
|| { echo "publish-apk.sh: KVITT_PUBLIC_HOST in infra/pi/.env must be a host name such as kvitt.example.org (no scheme, no port), not $KVITT_PUBLIC_HOST." >&2; exit 1; }
|
||||
[[ "$KVITT_PUBLIC_HOST" != icehack.asuscomm.com ]] \
|
||||
|| { echo "publish-apk.sh: KVITT_PUBLIC_HOST cannot be icehack.asuscomm.com, NPM's proxy host for Home Assistant (D-120)." >&2; exit 1; }
|
||||
|
||||
echo "Release $version_name (version_code $version_code) for https://$KVITT_PUBLIC_HOST:8443"
|
||||
|
||||
PATH="/opt/homebrew/opt/openjdk@25/bin:$PATH" tools/gradle-serial.sh :app:assembleRelease \
|
||||
"-Pkvitt.versionCode=$version_code" "-Pkvitt.versionName=$version_name"
|
||||
"-Pkvitt.versionCode=$version_code" "-Pkvitt.versionName=$version_name" "-Pkvitt.publicHost=$KVITT_PUBLIC_HOST"
|
||||
|
||||
out_dir="$repo_root/android/app/build/outputs/apk/release"
|
||||
apk="$out_dir/app-release.apk"
|
||||
@@ -80,6 +94,7 @@ done | sort -n -r | tail -n +6 | while read -r old; do
|
||||
done
|
||||
|
||||
echo "Staged in infra/pi/out/apk/: $file_name ($size_bytes bytes, sha256 $sha256) and latest.json."
|
||||
echo "Built for the release server https://$KVITT_PUBLIC_HOST:8443 (KVITT_PUBLIC_HOST in infra/pi/.env)."
|
||||
echo "Copy to the Pi and install (infra/README.md, Publishing an APK):"
|
||||
echo " scp infra/pi/out/apk/latest.json infra/pi/out/apk/$file_name infra/pi/out/apk/kvitt-$version_code.mapping.txt <ssh user>@<Kvitt Pi address>:/opt/kvitt/apk/"
|
||||
echo " ssh <ssh user>@<Kvitt Pi address> /opt/kvitt/install-apk.sh"
|
||||
|
||||