feat(offline): implicit net-state model, two-tier version gate, unified lobby #249
@@ -0,0 +1,172 @@
|
||||
# Manual signed-APK build for the standalone Android app (RuStore). Runs ONLY from master, ONLY on
|
||||
# workflow_dispatch with confirm=build — the same deliberate-manual shape as prod-deploy.yaml, never on
|
||||
# a PR. It builds the native-flavoured SPA, bundles the offline dictionaries into the APK assets, and
|
||||
# assembles a release APK, uploaded as a run artifact (RuStore upload stays manual for the MVP).
|
||||
#
|
||||
# Signing degrades gracefully: with the ANDROID_KEYSTORE_* secrets present the APK is signed; without
|
||||
# them build.gradle produces an UNSIGNED release APK (so a dry run still proves the whole pipeline).
|
||||
# The keystore is a publication prerequisite — see deploy/README.md (Android build/release runbook) and
|
||||
# ANDROID_PLAN.md §E. The toolchain is self-provisioned here (JDK 21 + a cached Android SDK), so the
|
||||
# runner host needs nothing pre-installed.
|
||||
name: android-build
|
||||
run-name: "android build ${{ github.sha }}"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
confirm:
|
||||
description: 'Type "build" to confirm an APK build from master.'
|
||||
required: true
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NO_COLOR: "1"
|
||||
# The dictionary release, one source of truth (same Gitea variable the backend image + CI use). It
|
||||
# both fetches the DAWGs and labels the bundled files (VITE_DICT_VERSION must equal __DICT_VERSION__).
|
||||
DICT_VERSION: ${{ vars.DICT_VERSION }}
|
||||
VITE_DICT_VERSION: ${{ vars.DICT_VERSION }}
|
||||
# Hide in-app purchases in the RuStore MVP (RuStore, not Google Play — VITE_GP_BUILD stays unset).
|
||||
VITE_PAYMENTS_DISABLED: "1"
|
||||
# The update overlay's store target; empty until publication (the button no-ops, and the version gate
|
||||
# is dormant in the MVP so it never fires). Set the ANDROID_RUSTORE_URL variable when the app is live.
|
||||
VITE_RUSTORE_URL: ${{ vars.ANDROID_RUSTORE_URL }}
|
||||
# Host-executor runner: the Android SDK is pre-installed on the host. Override ANDROID_SDK_DIR if it
|
||||
# lives elsewhere (the default matches deploy/README.md's install path).
|
||||
ANDROID_HOME: ${{ vars.ANDROID_SDK_DIR || '/opt/android-sdk' }}
|
||||
ANDROID_SDK_ROOT: ${{ vars.ANDROID_SDK_DIR || '/opt/android-sdk' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.ref == 'refs/heads/master' && inputs.confirm == 'build' }}
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Host-executor runner: the Android SDK is pre-installed on the host (JDK 21 comes from setup-java
|
||||
# below). Fail fast + legibly if the runner user cannot read/execute it or a needed package is
|
||||
# missing — this doubles as the runner-access check (deploy/README.md).
|
||||
- name: Verify the host Android SDK
|
||||
run: |
|
||||
sm="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
|
||||
if [ ! -x "$sm" ]; then
|
||||
echo "::error::sdkmanager not found/executable at $sm — set the ANDROID_SDK_DIR variable if the SDK lives elsewhere, or grant the runner user read+exec: sudo chmod -R a+rX \"$ANDROID_HOME\""; exit 1
|
||||
fi
|
||||
for pkg in "platforms/android-36" "build-tools"; do
|
||||
if [ ! -d "$ANDROID_HOME/$pkg" ]; then
|
||||
echo "::error::missing $ANDROID_HOME/$pkg — run: \"$sm\" 'platforms;android-36' 'build-tools;36.0.0'"; exit 1
|
||||
fi
|
||||
done
|
||||
echo "Android SDK OK at $ANDROID_HOME"; "$sm" --version
|
||||
|
||||
- name: Compute version + native build env
|
||||
id: prep
|
||||
env:
|
||||
PUBLIC_BASE_URL: ${{ vars.PROD_PUBLIC_BASE_URL }}
|
||||
run: |
|
||||
# A store release must sit on an exact vMAJOR.MINOR.PATCH tag (G tags before dispatch) so the
|
||||
# versionCode is deterministic and strictly increasing across uploads. Refuse anything else
|
||||
# rather than derive a versionCode from a "-N-gSHA" describe.
|
||||
desc="$(git describe --tags --exact-match 2>/dev/null || true)"
|
||||
case "$desc" in
|
||||
v[0-9]*.[0-9]*.[0-9]*) ;;
|
||||
*) echo "::error::HEAD is not on a clean vX.Y.Z tag (git describe --exact-match = '${desc:-none}'); tag the release first"; exit 1 ;;
|
||||
esac
|
||||
v="${desc#v}"
|
||||
IFS=. read -r MA MI PA <<< "$v"
|
||||
# 10# forces base-10 so a zero-padded part is never read as octal.
|
||||
code=$(( 10#$MA * 1000000 + 10#$MI * 1000 + 10#$PA ))
|
||||
# The native SPA talks to the production origin (reuse the prod public base URL); strip any
|
||||
# trailing slash so the Connect endpoint never doubles it.
|
||||
gateway="${PUBLIC_BASE_URL%/}"
|
||||
{
|
||||
echo "tag=$desc"
|
||||
echo "name=$v"
|
||||
echo "code=$code"
|
||||
echo "gateway=$gateway"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "release $desc -> versionName $v versionCode $code, gateway $gateway"
|
||||
|
||||
# Same release + fetch as the Go/UI jobs in ci.yaml — the bundled dicts come from this tarball,
|
||||
# NOT the scrabble-solver sibling (ui is a Node project outside go.work).
|
||||
- name: Fetch dictionary DAWGs
|
||||
run: |
|
||||
mkdir -p "${GITHUB_WORKSPACE}/dawg"
|
||||
curl -fsSL -o /tmp/dawg.tar.gz "https://gitea.iliadenisov.ru/developer/scrabble-dictionary/releases/download/${DICT_VERSION}/scrabble-dawg-${DICT_VERSION}.tar.gz"
|
||||
tar xzf /tmp/dawg.tar.gz -C "${GITHUB_WORKSPACE}/dawg"
|
||||
ls -la "${GITHUB_WORKSPACE}/dawg"
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm install -g pnpm@11.0.9
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ui
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build the SPA (native flavour)
|
||||
working-directory: ui
|
||||
env:
|
||||
VITE_GATEWAY_URL: ${{ steps.prep.outputs.gateway }}
|
||||
VITE_APP_VERSION: ${{ steps.prep.outputs.tag }}
|
||||
run: pnpm run build
|
||||
|
||||
# Copy the release DAWGs into dist/dict/<variant>@<version>.dawg for the offline-first bundled tier
|
||||
# (after the build, before cap sync copies dist/ into the native assets).
|
||||
- name: Bundle the dictionaries
|
||||
working-directory: ui
|
||||
env:
|
||||
DICT_DIR: ${{ github.workspace }}/dawg
|
||||
run: node scripts/bundle-dicts.mjs
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
|
||||
# Local cap binary (dodges the corepack pre-flight flake); syncs dist/ (incl. dict/) + native deps.
|
||||
- name: Sync the native project
|
||||
working-directory: ui
|
||||
run: node_modules/.bin/cap sync android
|
||||
|
||||
- name: Decode the release keystore
|
||||
id: keystore
|
||||
env:
|
||||
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
run: |
|
||||
if [ -n "$ANDROID_KEYSTORE_BASE64" ]; then
|
||||
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > "${GITHUB_WORKSPACE}/release.jks"
|
||||
echo "file=${GITHUB_WORKSPACE}/release.jks" >> "$GITHUB_OUTPUT"
|
||||
echo "keystore decoded -> signed release build"
|
||||
else
|
||||
echo "file=" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning::ANDROID_KEYSTORE_BASE64 is not set — building an UNSIGNED release APK (not installable/publishable)"
|
||||
fi
|
||||
|
||||
- name: Assemble the release APK
|
||||
working-directory: ui/android
|
||||
env:
|
||||
ANDROID_KEYSTORE_FILE: ${{ steps.keystore.outputs.file }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
run: ./gradlew assembleRelease -PversionCode=${{ steps.prep.outputs.code }} -PversionName=${{ steps.prep.outputs.name }} --console=plain
|
||||
|
||||
- name: Upload the APK artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: erudit-${{ steps.prep.outputs.name }}-apk
|
||||
path: ui/android/app/build/outputs/apk/release/*.apk
|
||||
if-no-files-found: error
|
||||
+51
-12
@@ -86,7 +86,7 @@ Kept current as parts land so a fresh session resumes without re-deriving. Verif
|
||||
gate in `connectsrv` — `Execute` returns `result_code="update_required"` before the registry lookup,
|
||||
`Subscribe` returns `FailedPrecondition`; fail-open on an absent/garbled header. Client:
|
||||
`X-Client-Version` on every call (`transport.ts headers()`), a terminal `update.svelte.ts` store +
|
||||
`UpdateOverlay.svelte` (native → `VITE_STORE_URL`, web → reload), `retry.ts` maps
|
||||
`UpdateOverlay.svelte` (native → `VITE_RUSTORE_URL`, web → reload), `retry.ts` maps
|
||||
`FailedPrecondition → update_required`, the `__update` mock hook. `gofmt`/`vet` clean, Go
|
||||
`clientver`/config/`connectsrv` tests green, `svelte-check` 0, `vitest` 591, e2e 232 (incl.
|
||||
`update.spec.ts`), client build clean. Silent reconciliation seam deferred to D (owner); in-app
|
||||
@@ -135,7 +135,35 @@ Kept current as parts land so a fresh session resumes without re-deriving. Verif
|
||||
flow, so the behaviour **must change** — a native guest should still see / resume their local games when
|
||||
online. Owner agreed it needs changing but wants to design it **at the very end of the Android work (before
|
||||
release — see §G)**; out of D.3/D.4 scope, tracked here so it is not lost.
|
||||
- **E–G — pending.**
|
||||
- **E. CI — signed APK artifact — ✅ CODE-COMPLETE & locally-proven (2026-07-12).** New manual workflow
|
||||
`.gitea/workflows/android-build.yaml` (`workflow_dispatch` + `confirm=build`, `if` gated to `master` —
|
||||
mirrors `prod-deploy.yaml`; never on a PR) builds the native-flavoured SPA, bundles the offline dicts, and
|
||||
assembles a release APK uploaded as a run artifact. Like `prod-deploy`, it is **provable only by a dispatch
|
||||
from `master`** (that is §G), so E was verified as far as possible now: locally via `assembleDebug` + a
|
||||
**keyless** `assembleRelease` (→ `app-release-unsigned.apk`, `versionCode 1017000` / `versionName 1.17.0`
|
||||
for a `v1.17.0` tag — confirmed in `output-metadata.json`), `svelte-check` 0, YAML structure checked.
|
||||
As-built:
|
||||
- **`ui/android/app/build.gradle`:** `versionCode`/`versionName` read `-PversionCode`/`-PversionName` (defaults
|
||||
keep local `assembleDebug` building). **CORRECTION vs the plan snippet — use `=` assignment**
|
||||
(`versionCode = (…).toInteger()`), NOT the command form `versionCode (…).toInteger()`: the latter binds
|
||||
`.toInteger()` to the DSL setter's null return (`> Value is null`). A guarded `signingConfigs.release` reads
|
||||
the keystore from env and is attached **only when the keystore file exists** — so **no keystore ⇒ an UNSIGNED
|
||||
release APK, not a failure** (a dispatch proves the pipeline before the keystore exists).
|
||||
- **Toolchain:** the **CI runner is a host-executor on a remote Debian host**. JDK 21 comes from
|
||||
`actions/setup-java`; the **Android SDK is host-provisioned** (pre-installed at `ANDROID_SDK_DIR`, default
|
||||
`/opt/android-sdk`), with a fail-fast **Verify the host Android SDK** pre-flight that also checks the `runner`
|
||||
user's read/exec access (`platforms;android-36` + `build-tools`). Unit tests need no new tooling (they ride
|
||||
the existing `unit`/`ui` jobs).
|
||||
- **Env:** `VITE_GATEWAY_URL` reuses `vars.PROD_PUBLIC_BASE_URL` (trailing slash stripped); `VITE_DICT_VERSION` /
|
||||
`DICT_VERSION` = the `DICT_VERSION` var; `VITE_PAYMENTS_DISABLED=1`. **`VITE_STORE_URL` was renamed to
|
||||
`VITE_RUSTORE_URL`** (owner) — left **empty** via the (unset) `vars.ANDROID_RUSTORE_URL`, so the update button
|
||||
no-ops until publication (`UpdateOverlay.svelte` already guards `if (url)`; the gate is dormant in the MVP).
|
||||
- **versionCode from an exact tag:** the workflow refuses anything but a clean `git describe --exact-match`
|
||||
`vX.Y.Z` (deterministic + monotonic store versionCode); §G tags before dispatch.
|
||||
- **Delivery:** `actions/upload-artifact@v4` (assumes Gitea 1.26 artifact support — verify at the §G dispatch).
|
||||
Secrets to set before a **signed** build (§G / publication): `ANDROID_KEYSTORE_BASE64`,
|
||||
`ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD`.
|
||||
- **F–G — pending.**
|
||||
|
||||
Open logistics (not code): **mandatory icon rebrand (future)** — author ONE vector master and generate
|
||||
*every* icon from it (web `favicon.svg` / PWA `icon-*` / maskable, Android adaptive, future iOS). Today
|
||||
@@ -334,7 +362,7 @@ is: scaffold → native-correct → gate → offline-first → CI → docs → r
|
||||
`wallet.purchasesSoon`, `data-testid="purchases-hidden"` — not an empty tab and not a store link; the
|
||||
same note can replace the RuStore stub in the later Google Play anti-steering variant.
|
||||
`purchasesHidden()` also carries a mock-only `?nopay` force (mirrors `?gp`) so the e2e drives the
|
||||
state without a separate build. Add `VITE_PAYMENTS_DISABLED?: string`, `VITE_STORE_URL?: string`,
|
||||
state without a separate build. Add `VITE_PAYMENTS_DISABLED?: string`, `VITE_RUSTORE_URL?: string`,
|
||||
`VITE_DICT_VERSION?: string` to `ui/src/vite-env.d.ts`.
|
||||
4. **Native env matrix** — see Build & env.
|
||||
|
||||
@@ -426,7 +454,7 @@ purchase actions are absent; `pnpm check` + `pnpm test:unit` pass.
|
||||
`case Code.FailedPrecondition: return new GatewayError('update_required', e.message);`.
|
||||
- **`ui/src/components/UpdateOverlay.svelte`** (new, mirror `MaintenanceOverlay.svelte`): non-dismissable;
|
||||
shown when `updateRequired.active`; one button — native (`clientChannel()` android/ios) →
|
||||
`window.open(import.meta.env.VITE_STORE_URL, '_system')`; web → `location.reload()`. i18n keys
|
||||
`window.open(import.meta.env.VITE_RUSTORE_URL, '_system')`; web → `location.reload()`. i18n keys
|
||||
`update.title/body/action` (add siblings to the maintenance keys).
|
||||
- **`ui/src/App.svelte`**: import + place `<UpdateOverlay />` right after `<MaintenanceOverlay />` (line 139).
|
||||
- **Mock e2e hook** — `ui/src/lib/gateway.ts` mock branch, next to `__maint`: `__update = { on: reportUpdateRequired }`.
|
||||
@@ -585,7 +613,7 @@ silently establishes a server guest (Profile + online play light up). Emulator s
|
||||
→ "Native Android build", with the extra `DICT_DIR=<release> VITE_DICT_VERSION=<ver> node
|
||||
scripts/bundle-dicts.mjs` between `pnpm build` and `cap sync`.
|
||||
|
||||
### E. CI — signed APK artifact
|
||||
### E. CI — signed APK artifact — ✅ CODE-COMPLETE (workflow written + `build.gradle` wired + locally proven; the signed dispatch is §G)
|
||||
|
||||
New **manual** workflow `.gitea/workflows/android-build.yaml` (mirror `prod-deploy.yaml`'s
|
||||
`workflow_dispatch` + `confirm` gate; from `master`; not on PRs). Steps:
|
||||
@@ -603,6 +631,9 @@ New **manual** workflow `.gitea/workflows/android-build.yaml` (mirror `prod-depl
|
||||
5. Setup JDK 21 (`actions/setup-java@v4`, temurin 21).
|
||||
6. Install Android SDK via `sdkmanager` (pin `platform-tools`, `platforms;android-36`,
|
||||
`build-tools;36.0.0`); cache the SDK. **No emulator** — assemble only; on-device smoke is manual.
|
||||
*As built:* the runner is a **host-executor** on a remote Debian host, so the **SDK is host-provisioned**
|
||||
(`ANDROID_SDK_DIR` var, default `/opt/android-sdk`) and a **Verify the host Android SDK** step fail-fasts on a
|
||||
missing package / no runner-user access; JDK 21 still comes from `actions/setup-java`.
|
||||
7. `npx cap sync android`.
|
||||
8. Decode the keystore from `ANDROID_KEYSTORE_BASE64`; compute `versionCode`/`versionName` from the
|
||||
tag (scheme below).
|
||||
@@ -617,9 +648,12 @@ for the MVP.
|
||||
**`versionCode`/`versionName` scheme** (deterministic from the tag): `v{MA}.{MI}.{PA}` →
|
||||
`versionCode = MA*1_000_000 + MI*1_000 + PA` (e.g. `v1.17.0` → `1017000`), `versionName = "{MA}.{MI}.{PA}"`.
|
||||
Wire in `ui/android/app/build.gradle` `defaultConfig`:
|
||||
`versionCode (project.findProperty('versionCode') ?: '1').toInteger()`,
|
||||
`versionName (project.findProperty('versionName') ?: '0.0.0')`, plus a `signingConfigs.release`
|
||||
reading env/props (guarded so a local `assembleDebug` still builds unsigned).
|
||||
`versionCode = (project.findProperty('versionCode') ?: '1').toInteger()`,
|
||||
`versionName = (project.findProperty('versionName') ?: '0.0.0').toString()`, plus a guarded
|
||||
`signingConfigs.release` reading env/props. **Use `=` assignment, not the command form**
|
||||
`versionCode (…).toInteger()` — the latter binds `.toInteger()` to the DSL setter's null return
|
||||
(`> Value is null`). The signing block attaches only when `ANDROID_KEYSTORE_FILE` exists, so a keyless
|
||||
`assembleRelease` (and every `assembleDebug`) builds UNSIGNED instead of failing.
|
||||
|
||||
### F. Docs (bake in the same PR)
|
||||
|
||||
@@ -634,8 +668,11 @@ reading env/props (guarded so a local `assembleDebug` still builds unsigned).
|
||||
test, and the **manual on-device Android smoke checklist** (installs; airplane-mode cold launch to
|
||||
guest lobby; local vs_ai move; 2-player hotseat; Back button; share link resolves to the gateway;
|
||||
network on → online lights up; overlay when `GATEWAY_MIN_CLIENT_VERSION` is bumped).
|
||||
- Config/README docs: new vars `GATEWAY_MIN_CLIENT_VERSION`, `VITE_STORE_URL`,
|
||||
`VITE_PAYMENTS_DISABLED`, `VITE_DICT_VERSION`, and the native `VITE_GATEWAY_URL` value.
|
||||
- Config/README docs: new vars `GATEWAY_MIN_CLIENT_VERSION`, `VITE_RUSTORE_URL`,
|
||||
`VITE_PAYMENTS_DISABLED`, `VITE_DICT_VERSION`, and the native `VITE_GATEWAY_URL` value. Gitea CI vars/secrets
|
||||
for the APK build: `ANDROID_RUSTORE_URL` (var, empty until publish), `PROD_PUBLIC_BASE_URL` (reused as the
|
||||
gateway origin), and the release-signing secrets `ANDROID_KEYSTORE_BASE64` / `ANDROID_KEYSTORE_PASSWORD` /
|
||||
`ANDROID_KEY_ALIAS` / `ANDROID_KEY_PASSWORD`.
|
||||
- `deploy/README.md`: the Android build/release runbook (keystore + secrets, dispatch, RuStore
|
||||
upload) **and** the discipline rule — bump `GATEWAY_MIN_CLIENT_VERSION` in the same prod deploy that
|
||||
ships an incompatible wire change.
|
||||
@@ -675,8 +712,10 @@ target, built later.
|
||||
## Build & env matrix
|
||||
|
||||
**Native Android build** (`pnpm build` → `bundle-dicts` → `cap sync`):
|
||||
- `VITE_GATEWAY_URL=https://erudit-game.ru` — absolute origin (the native-critical var).
|
||||
- `VITE_STORE_URL=https://www.rustore.ru/catalog/app/ru.eruditgame.app` — the update-overlay target.
|
||||
- `VITE_GATEWAY_URL` — absolute gateway origin; the workflow reuses `vars.PROD_PUBLIC_BASE_URL`
|
||||
(`https://erudit-game.ru`, trailing slash stripped). The native-critical var.
|
||||
- `VITE_RUSTORE_URL` — the update-overlay store target. **Empty in the MVP** (set the `ANDROID_RUSTORE_URL`
|
||||
Gitea variable once published); the gate is dormant so the button never fires yet.
|
||||
- `VITE_APP_VERSION=$(git describe --tags)` — feeds `__APP_VERSION__` = the `X-Client-Version` header.
|
||||
- `VITE_DICT_VERSION=<release dict version>` — the bundled-dict version the offline path requests.
|
||||
- `VITE_PAYMENTS_DISABLED=1` — hide purchases in the MVP. (`VITE_GP_BUILD` unset — RuStore, not GP.)
|
||||
|
||||
@@ -3,12 +3,22 @@ apply plugin: 'com.android.application'
|
||||
android {
|
||||
namespace = "ru.eruditgame.app"
|
||||
compileSdk = rootProject.ext.compileSdkVersion
|
||||
|
||||
// Release signing is supplied by the android-build CI workflow via env: it decodes the keystore
|
||||
// secret to a file and points ANDROID_KEYSTORE_FILE at it. A local build or a keyless CI run leaves
|
||||
// it unset, so the release APK is produced UNSIGNED rather than failing — assembleDebug and
|
||||
// assembleRelease both build without the keystore. The keystore is never committed (see .gitignore).
|
||||
def keystoreFile = System.getenv('ANDROID_KEYSTORE_FILE')
|
||||
def hasKeystore = keystoreFile != null && !keystoreFile.isEmpty() && file(keystoreFile).exists()
|
||||
|
||||
defaultConfig {
|
||||
applicationId "ru.eruditgame.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
// Deterministic from the release tag by the android-build workflow (vMA.MI.PA ->
|
||||
// MA*1_000_000 + MI*1_000 + PA); the defaults keep a local assembleDebug building.
|
||||
versionCode = (project.findProperty('versionCode') ?: '1').toInteger()
|
||||
versionName = (project.findProperty('versionName') ?: '0.0.0').toString()
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
@@ -16,10 +26,27 @@ android {
|
||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
// Populated only when the workflow provided a keystore (see hasKeystore above); left empty
|
||||
// otherwise so it is never referenced with a null storeFile.
|
||||
if (hasKeystore) {
|
||||
storeFile file(keystoreFile)
|
||||
storePassword System.getenv('ANDROID_KEYSTORE_PASSWORD')
|
||||
keyAlias System.getenv('ANDROID_KEY_ALIAS')
|
||||
keyPassword System.getenv('ANDROID_KEY_PASSWORD')
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
// Sign only when a keystore was supplied; a keyless release build stays unsigned instead
|
||||
// of failing.
|
||||
if (hasKeystore) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Non-dismissable "update required" cover. Shown when the gateway has refused a foreground call
|
||||
// as too old (update.svelte.ts — the client-version gate). Unlike the maintenance overlay this is
|
||||
// terminal: an installed build cannot become compatible without an actual update, so its one
|
||||
// action takes the user to the fix — the store listing on a native build (VITE_STORE_URL, opened
|
||||
// action takes the user to the fix — the store listing on a native build (VITE_RUSTORE_URL, opened
|
||||
// in the system browser / store app) or a plain reload on the web (which fetches the current
|
||||
// client). Mirrors MaintenanceOverlay.svelte's look.
|
||||
import { updateRequired } from '../lib/update.svelte';
|
||||
@@ -13,7 +13,7 @@
|
||||
const ch = clientChannel();
|
||||
if (ch === 'android' || ch === 'ios') {
|
||||
// '_system' hands the URL to the OS (the store app / external browser) rather than the WebView.
|
||||
const url = import.meta.env.VITE_STORE_URL;
|
||||
const url = import.meta.env.VITE_RUSTORE_URL;
|
||||
if (url) window.open(url, '_system');
|
||||
} else {
|
||||
location.reload();
|
||||
|
||||
Vendored
+3
-2
@@ -7,8 +7,9 @@ interface ImportMetaEnv {
|
||||
readonly VITE_GATEWAY_URL?: string;
|
||||
/** "1" hides the in-app-currency purchase actions in the thin native MVP that defers store billing. */
|
||||
readonly VITE_PAYMENTS_DISABLED?: string;
|
||||
/** Store listing URL the native update overlay opens for its "update" action (e.g. the RuStore page). */
|
||||
readonly VITE_STORE_URL?: string;
|
||||
/** RuStore listing URL the native update overlay opens for its "update" action. Empty until the app
|
||||
* is published (the button then no-ops); the version gate is dormant in the MVP so it never fires yet. */
|
||||
readonly VITE_RUSTORE_URL?: string;
|
||||
/** Bundled-dictionary version the native offline path requests; matches the packaged DAWG files. */
|
||||
readonly VITE_DICT_VERSION?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user