feat(mobile): ekran "zaktualizuj" dla wersji starszych niz 0.4.x
Osobny punkt wejscia (update-required.tsx) z samym ekranem blokady, bez reszty aplikacji i bez przycisku "Pozniej". Publikowany tylko na kanaly OTA starych runtime'ow 1.0-1.3 (0.3.0 to dzis ~8% ruchu): stare APK nie dostaja poprawek natywnych, a ich bledy wracaly w zgloszeniach. Uzywa tylko modulow obecnych w kazdym APK. Runtime 1.0/1.1 (stary klucz podpisu) dostaje kroki z kodem synchronizacji, od 1.2 nowa wersja instaluje sie na wierzchu. publish_update.py --entry podmienia pole main w package.json na czas eksportu i odmawia publikacji na biezacy runtime. Emulator: prawdziwe APK 0.3.0 i 0.2.1 z ta paczka pokazuja ekran, pobieranie otwiera APK w przegladarce, kopiowanie kodu dziala, Wstecz zamyka apke, 0.4.2 instaluje sie na 0.3.0 i startuje normalnie. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7e48cdee06
commit
3b061a5807
3 changed files with 191 additions and 1 deletions
159
mobile/src/UpdateRequired.tsx
Normal file
159
mobile/src/UpdateRequired.tsx
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* Ekran "zaktualizuj aplikacje" dla starych APK (runtime 1.0-1.3, wersje sprzed 0.4.0).
|
||||
*
|
||||
* To NIE jest czesc zwyklej aplikacji: ta paczka jest budowana z osobnego punktu wejscia
|
||||
* (`update-required.tsx`, `publish_update.py --entry`) i publikowana tylko na kanaly OTA
|
||||
* starych runtime'ow. Stare wersje nie dostaja juz poprawek natywnych, a ich bledy wracaly
|
||||
* w zgloszeniach i Sentry (decyzja Jana 2026-10-06: blokada wszystkiego starszego niz 0.4.x,
|
||||
* okolo 8% ruchu). Nie ma przycisku "Pozniej" ani zadnej innej funkcji, celowo.
|
||||
*
|
||||
* Uzywa wylacznie modulow, ktore sa w kazdym APK od pierwszego wydania (react-native,
|
||||
* expo-updates, expo-secure-store, expo-clipboard), wiec nie wywali sie na starym kodzie
|
||||
* natywnym.
|
||||
*
|
||||
* Runtime 1.0 i 1.1 (0.2.1 i starsze) maja stary klucz podpisu, ktory przepadl: Android
|
||||
* nie zainstaluje nowej wersji na wierzchu, trzeba odinstalowac, co czysci telefon. Dla nich
|
||||
* kroki z kodem synchronizacji, jak w MigrationBanner. Od 1.2 nowa wersja instaluje sie
|
||||
* na wierzchu i dane zostaja.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Linking, Pressable, ScrollView, StatusBar, StyleSheet, Text, View } from 'react-native';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import * as Updates from 'expo-updates';
|
||||
|
||||
const VERSION_URL = 'https://api.goon-foss.org/version';
|
||||
const APK_FALLBACK = 'https://api.goon-foss.org/static/app-release.apk';
|
||||
const SITE_URL = 'https://goon-foss.org/';
|
||||
const OLD_KEY_RUNTIMES = ['1.0', '1.1'];
|
||||
|
||||
const C = {
|
||||
bg: '#14110f',
|
||||
card: '#1f1a17',
|
||||
fg: '#f3ede4',
|
||||
muted: '#a99f94',
|
||||
accent: '#e05a6d',
|
||||
};
|
||||
|
||||
export default function UpdateRequired() {
|
||||
const runtime = Updates.runtimeVersion || '';
|
||||
const oldKey = OLD_KEY_RUNTIMES.includes(runtime);
|
||||
const [apkUrl, setApkUrl] = React.useState(APK_FALLBACK);
|
||||
const [latest, setLatest] = React.useState<string | null>(null);
|
||||
const [code, setCode] = React.useState<string | null>(null);
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetch(VERSION_URL)
|
||||
.then((r) => r.json())
|
||||
.then((v: { version?: string; apk_url?: string | null }) => {
|
||||
if (v.apk_url && /^https?:\/\//.test(v.apk_url)) setApkUrl(v.apk_url);
|
||||
if (v.version) setLatest(v.version);
|
||||
})
|
||||
.catch(() => {});
|
||||
if (oldKey) {
|
||||
SecureStore.getItemAsync('goon.device_id')
|
||||
.then((id) => setCode(id))
|
||||
.catch(() => {});
|
||||
}
|
||||
}, [oldKey]);
|
||||
|
||||
const copy = React.useCallback(async () => {
|
||||
if (!code) return;
|
||||
try {
|
||||
const Clipboard = require('expo-clipboard');
|
||||
await Clipboard.setStringAsync(code);
|
||||
setCopied(true);
|
||||
} catch {
|
||||
// brak modulu: kod i tak da sie zaznaczyc i skopiowac recznie
|
||||
}
|
||||
}, [code]);
|
||||
|
||||
const open = (url: string) => Linking.openURL(url).catch(() => {});
|
||||
|
||||
return (
|
||||
<View style={s.root}>
|
||||
<StatusBar barStyle="light-content" backgroundColor={C.bg} />
|
||||
<ScrollView contentContainerStyle={s.content}>
|
||||
<Text style={s.title}>Update required</Text>
|
||||
<Text style={s.lead}>
|
||||
This version of Goon is no longer supported and does not get fixes anymore. Install
|
||||
the new version{latest ? ` (${latest})` : ''} to keep watching.
|
||||
</Text>
|
||||
|
||||
{oldKey ? (
|
||||
<>
|
||||
<Text style={s.lead}>
|
||||
Your version was signed with an old key, so Android will not install the new one
|
||||
over it. Uninstall first, then install the download. That wipes the phone, so copy
|
||||
your sync code before you start.
|
||||
</Text>
|
||||
{code ? (
|
||||
<>
|
||||
<Text style={s.step}>1. Copy your sync code</Text>
|
||||
<Pressable style={s.codeRow} onPress={copy}>
|
||||
<Text style={s.code} selectable numberOfLines={1}>
|
||||
{code}
|
||||
</Text>
|
||||
<Text style={s.copy}>{copied ? 'Copied' : 'Copy'}</Text>
|
||||
</Pressable>
|
||||
</>
|
||||
) : null}
|
||||
<Text style={s.step}>{code ? '2.' : '1.'} Download the new version</Text>
|
||||
<Pressable style={s.btn} onPress={() => open(apkUrl)}>
|
||||
<Text style={s.btnText}>Download the update</Text>
|
||||
</Pressable>
|
||||
<Text style={s.step}>{code ? '3.' : '2.'} Uninstall Goon, then open the file</Text>
|
||||
<Text style={s.note}>
|
||||
Long-press the app icon, Uninstall. Then open the .apk from your notifications or
|
||||
Downloads.
|
||||
</Text>
|
||||
{code ? (
|
||||
<>
|
||||
<Text style={s.step}>4. Paste the code back</Text>
|
||||
<Text style={s.note}>Settings, Backup & sync, Restore: paste the code, tap Import.</Text>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Pressable style={s.btn} onPress={() => open(apkUrl)}>
|
||||
<Text style={s.btnText}>Download the update</Text>
|
||||
</Pressable>
|
||||
<Text style={s.note}>
|
||||
Open the downloaded .apk from your notifications or Downloads and tap Install. It
|
||||
installs over this version, your favourites and settings stay.
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Pressable onPress={() => open(SITE_URL)} hitSlop={8}>
|
||||
<Text style={s.link}>Download does not start? Open goon-foss.org</Text>
|
||||
</Pressable>
|
||||
<Text style={s.footer}>Installed: runtime {runtime || '?'}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const s = StyleSheet.create({
|
||||
root: { flex: 1, backgroundColor: C.bg },
|
||||
content: { padding: 24, paddingTop: 72, paddingBottom: 48 },
|
||||
title: { color: C.fg, fontSize: 28, fontWeight: '800', marginBottom: 16 },
|
||||
lead: { color: C.fg, fontSize: 16, lineHeight: 23, marginBottom: 14 },
|
||||
step: { color: C.fg, fontSize: 16, fontWeight: '700', marginTop: 14, marginBottom: 8 },
|
||||
note: { color: C.muted, fontSize: 14, lineHeight: 20, marginTop: 8 },
|
||||
codeRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: C.card,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
code: { flex: 1, color: C.fg, fontSize: 15, fontFamily: 'monospace' },
|
||||
copy: { color: C.accent, fontWeight: '700', marginLeft: 12 },
|
||||
btn: { backgroundColor: C.accent, borderRadius: 12, paddingVertical: 15, alignItems: 'center', marginTop: 8 },
|
||||
btnText: { color: '#fff', fontSize: 17, fontWeight: '800' },
|
||||
link: { color: C.accent, fontSize: 14, marginTop: 28, textAlign: 'center' },
|
||||
footer: { color: C.muted, fontSize: 12, marginTop: 24, textAlign: 'center' },
|
||||
});
|
||||
7
mobile/update-required.tsx
Normal file
7
mobile/update-required.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Punkt wejscia paczki "zaktualizuj aplikacje" dla starych runtime'ow (1.0-1.3).
|
||||
// Zwykla aplikacja startuje z App.tsx; ten plik buduje tylko
|
||||
// `scripts/publish_update.py --entry update-required.tsx`. Opis w src/UpdateRequired.tsx.
|
||||
import { registerRootComponent } from 'expo';
|
||||
import UpdateRequired from './src/UpdateRequired';
|
||||
|
||||
registerRootComponent(UpdateRequired);
|
||||
|
|
@ -141,15 +141,39 @@ def main() -> int:
|
|||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--runtime", default=RUNTIME_DEFAULT)
|
||||
ap.add_argument("--skip-export", action="store_true", help="reuse existing dist/")
|
||||
ap.add_argument(
|
||||
"--entry",
|
||||
help="inny punkt wejscia niz App.tsx, np. update-required.tsx (ekran blokady starych wersji)",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
# Ekran blokady na biezacym kanale zablokowalby wszystkich uzytkownikow.
|
||||
if args.entry and args.runtime == RUNTIME_DEFAULT:
|
||||
print(f"ERROR: --entry {args.entry} na biezacym runtime {RUNTIME_DEFAULT} zablokowalby wszystkich. Przerwane.")
|
||||
return 1
|
||||
|
||||
if not args.skip_export:
|
||||
if DIST.exists():
|
||||
import shutil
|
||||
shutil.rmtree(DIST)
|
||||
# `--source-maps` dokłada `.hbc.map`, potrzebną do odczytania śladów JS
|
||||
# w Sentry. Mapa NIE jedzie na serwer, kasuje ją `strip_sourcemaps()`.
|
||||
# `--entry`: expo export bierze punkt wejscia z pola `main` w package.json, wiec
|
||||
# podmieniamy je na czas eksportu i zawsze przywracamy.
|
||||
pkg_path = MOBILE / "package.json"
|
||||
pkg_orig = pkg_path.read_text(encoding="utf-8")
|
||||
if args.entry:
|
||||
if not (MOBILE / args.entry).exists():
|
||||
print(f"ERROR: brak pliku mobile/{args.entry}")
|
||||
return 1
|
||||
pkg = json.loads(pkg_orig)
|
||||
pkg["main"] = args.entry
|
||||
pkg_path.write_text(json.dumps(pkg, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||
try:
|
||||
run(["npx", "expo", "export", "--platform", "android", "--source-maps"], cwd=MOBILE)
|
||||
finally:
|
||||
if args.entry:
|
||||
pkg_path.write_text(pkg_orig, encoding="utf-8")
|
||||
|
||||
upload_sourcemaps()
|
||||
strip_sourcemaps()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue