/** * Baner jednorazowej migracji na nowo podpisane APK. * * Dlaczego w ogóle: klucz podpisujący stare buildy przepadł, a Android pozwala nadpisać * aplikację tylko buildem z tym samym podpisem. Nowa wersja wymaga więc odinstalowania * starej, co czyści SecureStore razem z `goon.device_id` — czyli kodem synchronizacji. * Dane (ulubione, zapisane wyszukiwania, ukryte, postęp) zostają na serwerze pod tym id, * ale bez kodu nie ma jak ich odzyskać. Dlatego kod jest w banerze na wierzchu, a nie * schowany w ustawieniach: liczy się, żeby user skopiował go PRZED odinstalowaniem. * * Zwinięty pasek siedzi nad listą scen (nie w ListHeaderComponent — tam znikałby przy * wyszukiwaniu i odjeżdżał przy scrollu). Rozwija się w modal z krokami. */ import React from 'react'; import { Linking, Modal, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import * as Clipboard from 'expo-clipboard'; import { MIGRATION_NOTICE } from '../migrationNotice'; import { dismissMigrationNotice, getDeviceId, isMigrationNoticeHidden, snoozeMigrationNotice, } from '../storage'; import { theme } from '../theme'; export function MigrationBanner() { const [show, setShow] = React.useState(false); const [open, setOpen] = React.useState(false); const [deviceId, setDeviceId] = React.useState(''); const [copied, setCopied] = React.useState(false); React.useEffect(() => { if (!MIGRATION_NOTICE.enabled) return; let cancelled = false; (async () => { try { const [hidden, id] = await Promise.all([isMigrationNoticeHidden(), getDeviceId()]); if (cancelled || hidden) return; setDeviceId(id); setShow(true); } catch { // brak SecureStore → nie ma czym migrować, nie strasz banerem } })(); return () => { cancelled = true; }; }, []); const copy = React.useCallback(async () => { await Clipboard.setStringAsync(deviceId); setCopied(true); }, [deviceId]); const later = React.useCallback(() => { setOpen(false); setShow(false); snoozeMigrationNotice().catch(() => {}); }, []); const done = React.useCallback(() => { setOpen(false); setShow(false); dismissMigrationNotice().catch(() => {}); }, []); if (!show) return null; return ( <> setOpen(true)}> Update {MIGRATION_NOTICE.version} needs a manual install Copy your sync code first, or your favourites do not come across. Tap for the steps. › Update {MIGRATION_NOTICE.version} This one has to be installed by hand: the new build is signed with a different key, so Android will not put it on top of the current app. You uninstall Goon first, then install the download. That wipes what is stored on the phone, so copy your sync code now and paste it back afterwards. 1. Copy your sync code {deviceId || '…'} {copied ? 'Copied' : 'Copy'} Paste it somewhere safe (notes, a message to yourself). Without it your favourites, saved searches, hidden list and watch progress cannot be found again. 2. Download the new version Linking.openURL(MIGRATION_NOTICE.downloadUrl).catch(() => {})} > Open {MIGRATION_NOTICE.downloadUrl.replace(/^https?:\/\//, '').replace(/\/$/, '')} 3. Uninstall Goon, then open the downloaded file Long-press the app icon, Uninstall. Then open the .apk from your notifications or Downloads. If Android asks, allow installing from your browser. 4. Paste the code back Settings, Backup & sync, Restore from another device: paste the code, tap Import. Everything comes back. Sorry for the hassle. Signing keys cannot be recovered, so this is a one-off: updates after this one go back to installing themselves. Done, do not show again Remind me tomorrow ); } const styles = StyleSheet.create({ bar: { flexDirection: 'row', alignItems: 'center', gap: 10, marginHorizontal: 12, marginBottom: 10, paddingVertical: 10, paddingHorizontal: 12, borderRadius: 12, borderWidth: 1, borderColor: theme.accent, backgroundColor: theme.bgElevated, }, barTitle: { color: theme.fg, fontSize: 14, fontWeight: '700' }, barHint: { color: theme.muted, fontSize: 12, marginTop: 2, lineHeight: 16 }, barChevron: { color: theme.accent, fontSize: 22, fontWeight: '700' }, backdrop: { flex: 1, backgroundColor: 'rgba(0,0,0,0.7)', alignItems: 'center', justifyContent: 'center', padding: 24, }, card: { width: '100%', maxHeight: '85%', backgroundColor: theme.bgElevated, borderRadius: 16, borderColor: theme.border, borderWidth: 1, padding: 20, }, title: { color: theme.fg, fontSize: 20, fontWeight: '800', marginBottom: 12 }, scroll: { flexGrow: 0 }, lead: { color: theme.fg, fontSize: 14, lineHeight: 20, marginBottom: 16 }, step: { color: theme.accent, fontSize: 12, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.5, marginTop: 8, marginBottom: 8, }, codeRow: { flexDirection: 'row', alignItems: 'center', gap: 10, backgroundColor: theme.card, borderRadius: 10, borderWidth: 1, borderColor: theme.border, paddingVertical: 10, paddingHorizontal: 12, }, code: { color: theme.fg, fontSize: 13, flex: 1 }, copyBtn: { color: theme.accent, fontSize: 13, fontWeight: '800' }, note: { color: theme.muted, fontSize: 13, lineHeight: 19, marginTop: 8 }, dlBtn: { backgroundColor: theme.card, borderRadius: 10, borderWidth: 1, borderColor: theme.accent, paddingVertical: 12, alignItems: 'center', }, dlText: { color: theme.fg, fontSize: 14, fontWeight: '700' }, sorry: { color: theme.mutedDim, fontSize: 12, lineHeight: 18, marginTop: 18 }, primaryBtn: { marginTop: 14, backgroundColor: theme.accent, borderRadius: 10, paddingVertical: 12, alignItems: 'center', }, primaryText: { color: theme.fg, fontSize: 15, fontWeight: '700' }, secondaryBtn: { marginTop: 8, paddingVertical: 10, alignItems: 'center' }, secondaryText: { color: theme.muted, fontSize: 14, fontWeight: '600' }, });