diff --git a/mobile/src/SceneActionsContext.tsx b/mobile/src/SceneActionsContext.tsx index 3336d93..76a0e57 100644 --- a/mobile/src/SceneActionsContext.tsx +++ b/mobile/src/SceneActionsContext.tsx @@ -132,7 +132,7 @@ export function SceneActionsProvider({ children }: { children: React.ReactNode } - Pick the original — tap the scene to merge "{pendingDuplicate.title}" into + Pick the original, tap the scene to merge "{pendingDuplicate.title}" into Cancel diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index 3b42bfd..c9111f0 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -227,7 +227,7 @@ export const CHANGELOG: ChangelogEntry[] = [ id: '2026-06-26', date: 'June 2026', items: [ - 'Source code moved to our own server (git.goon-foss.org) — still fully open source (MIT). The Settings link now points there.', + 'Source code moved to our own server (git.goon-foss.org). Still fully open source (MIT). The Settings link now points there.', ], }, { @@ -241,14 +241,14 @@ export const CHANGELOG: ChangelogEntry[] = [ id: '2026-06-22b', date: 'June 2026', items: [ - 'New: a quick tutorial on first launch — tabs, search, long-press actions and player gestures. Replay it anytime from Settings ⚙.', + 'New: a quick tutorial on first launch: tabs, search, long-press actions and player gestures. Replay it anytime from Settings ⚙.', ], }, { id: '2026-06-22', date: 'June 2026', items: [ - 'Sites now show a 0-5★ rating — how fresh, how well-described, and how reliably each source plays. Tap the stars for the breakdown.', + 'Sites now show a 0-5★ rating: how fresh, how well-described, and how reliably each source plays. Tap the stars for the breakdown.', 'Dropped two sources whose videos stopped playing (they served a "server down" clip).', 'latestpornvideo now keeps up with its newest videos.', ], @@ -295,7 +295,7 @@ export const CHANGELOG: ChangelogEntry[] = [ id: '2026-06-19', date: 'June 2026', items: [ - 'Smoother scrolling on long scene lists — no more lag or phone slowdown.', + 'Smoother scrolling on long scene lists, no more lag or phone slowdown.', 'fpo.xxx scenes now play the actual video instead of an ad.', ], }, @@ -304,7 +304,7 @@ export const CHANGELOG: ChangelogEntry[] = [ date: 'June 2026', items: [ 'Grid columns: pick 1, 2 or 3 thumbnails per row in Settings (1 = bigger previews).', - 'Filter scenes by minimum length — hide short clips (5/10/20/30+ min).', + 'Filter scenes by minimum length, hide short clips (5/10/20/30+ min).', 'Save searches: tap ☆ Save next to the search box, then reuse them as chips.', 'Privacy: the app is hidden in Recents and blocks screenshots again.', 'Smoother startup when an update is downloading (no more double PIN prompt).', @@ -314,16 +314,16 @@ export const CHANGELOG: ChangelogEntry[] = [ id: '2026-06-13', date: 'June 2026', items: [ - 'Source code link added in Settings — the app is open source (MIT).', + 'Source code link added in Settings. The app is open source (MIT).', 'New: a Refresh thumbnail button on a scene when its preview is broken or stale.', - 'Fewer dead links — deleted videos on some sites are now detected automatically.', + 'Fewer dead links: deleted videos on some sites are now detected automatically.', ], }, { id: '2026-06-12', date: 'June 2026', items: [ - 'Favorites now has a Scenes tab — every scene you heart shows up there.', + 'Favorites now has a Scenes tab, every scene you heart shows up there.', 'Your bug reports can get replies: tap the ? button, then "Your messages".', 'More reliable playback and lower data use on several sites.', 'Clearer message when a source is blocked in your region or by your network.', diff --git a/mobile/src/components/MigrationBanner.tsx b/mobile/src/components/MigrationBanner.tsx index 1369f6f..b0b0679 100644 --- a/mobile/src/components/MigrationBanner.tsx +++ b/mobile/src/components/MigrationBanner.tsx @@ -15,7 +15,7 @@ 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 { MIGRATION_NOTICE, needsManualMigration } from '../migrationNotice'; import { dismissMigrationNotice, getDeviceId, @@ -31,7 +31,9 @@ export function MigrationBanner() { const [copied, setCopied] = React.useState(false); React.useEffect(() => { - if (!MIGRATION_NOTICE.enabled) return; + // Warunek na runtime, nie tylko na kanał OTA: ten sam bundle bywa publikowany na 1.2, + // a tam baner byłby szkodliwy (kazałby migrować z wersji, na którą user właśnie przeszedł). + if (!needsManualMigration()) return; let cancelled = false; (async () => { try { diff --git a/mobile/src/components/OnboardingModal.tsx b/mobile/src/components/OnboardingModal.tsx index 4cbc54f..2e83c91 100644 --- a/mobile/src/components/OnboardingModal.tsx +++ b/mobile/src/components/OnboardingModal.tsx @@ -32,7 +32,7 @@ const SLIDES: Slide[] = [ icon: '👋', title: 'Welcome to Goon', lines: [ - 'Scenes and movies pulled together from 30+ sites — browse and play in one place.', + 'Scenes and movies pulled together from 30+ sites, browse and play in one place.', 'Quick tour, about 30 seconds. Swipe or tap Next.', ], }, @@ -40,9 +40,9 @@ const SLIDES: Slide[] = [ icon: '🗂️', title: 'Three tabs up top', lines: [ - 'Scenes — individual clips.', - 'Movies — full-length, often multi-part.', - 'Sites — browse by source, each rated 0–5★.', + 'Scenes: individual clips.', + 'Movies: full-length, often multi-part.', + 'Sites: browse by source, each rated 0-5★.', ], }, { @@ -70,23 +70,23 @@ const SLIDES: Slide[] = [ 'Tap once → show or hide the controls.', 'Double-tap left / right → skip back / forward 15s.', 'Swipe across the video → scrub to any point.', - 'It starts muted — tap the speaker for sound.', + 'It starts muted, tap the speaker for sound.', ], }, { icon: '⭐', title: 'Make it yours', lines: [ - 'Heart scenes, movies and performers — they collect in Favorites.', + 'Heart scenes, movies and performers, they collect in Favorites.', 'Settings ⚙ → Hidden content hides tags, performers or studios. Grid columns and a PIN lock live there too.', - 'Hit a problem? The ? button sends a report — and you can read replies.', + 'Hit a problem? The ? button sends a report, and you can read replies.', ], }, { icon: '✅', title: "You're set", lines: [ - 'On Sites, the ★ rating tells you what’s fresh and what actually plays — tap the stars for the breakdown.', + 'On Sites, the ★ rating tells you what’s fresh and what actually plays. Tap the stars for the breakdown.', 'Replay this tour anytime: Settings ⚙ → Replay tutorial.', ], }, diff --git a/mobile/src/lib/donate.ts b/mobile/src/lib/donate.ts index 42256be..a4a51d8 100644 --- a/mobile/src/lib/donate.ts +++ b/mobile/src/lib/donate.ts @@ -43,7 +43,7 @@ export const DONATE_COINS: DonateCoin[] = [ id: 'btc', ticker: 'BTC', name: 'Bitcoin', - blurb: 'Mainstream, easy to send. On-chain is public — use a fresh wallet to donate anonymously.', + blurb: 'Mainstream, easy to send. On-chain is public, so use a fresh wallet to donate anonymously.', address: 'bc1qcaa4rv89rtt39y92rpff5rlysmtkful9ck6mml', color: '#F7931A', qrScheme: 'bitcoin:', diff --git a/mobile/src/migrationNotice.ts b/mobile/src/migrationNotice.ts index eb82f98..3295fa3 100644 --- a/mobile/src/migrationNotice.ts +++ b/mobile/src/migrationNotice.ts @@ -10,14 +10,32 @@ * * Stąd baner: pokazuje kod PRZED odinstalowaniem i prowadzi przez kolejne kroki. * - * Trafia tylko do APK na runtime 1.1. Nowe APK ma runtime 1.2, czyli inny kanał OTA, - * więc nigdy tego banera nie zobaczy — nie trzeba go potem wyłączać zdalnie. + * Baner ma trafiać WYŁĄCZNIE do instalacji sprzed 0.2.2, czyli tych na runtime 1.1. + * Sam podział na kanały OTA to za mało: chronił tylko dopóki kanał 1.2 był pusty, a przy + * pierwszej publikacji na 1.2 ten sam bundle trafiłby do nowej wersji i kazałby jej + * użytkownikom instalować ręcznie coś, co już mają. Stąd twardy warunek na runtime niżej. */ +import * as Updates from 'expo-updates'; + +/** Kanał OTA instalacji sprzed 0.2.2. Tylko tam baner ma sens. */ +const LEGACY_RUNTIME = '1.1'; + export const MIGRATION_NOTICE = { - /** Master switch — false chowa baner całkowicie (publikacja OTA na runtime 1.1). */ + /** Master switch. Ustaw false, gdy migracja się dopali i baner ma zniknąć. */ enabled: true, /** Wersja do zainstalowania. Pokazywana w banerze. */ version: '0.2.2', /** Strona pobierania. Celowo strona, nie plik: link przeżyje zmianę nazwy APK. */ downloadUrl: 'https://goon-foss.org/', } as const; + +/** Czy ta instalacja w ogóle wymaga ręcznej migracji. Fail-closed: gdy nie da się + * odczytać runtime'u (dev client, brak expo-updates), NIE straszymy banerem. */ +export function needsManualMigration(): boolean { + if (!MIGRATION_NOTICE.enabled) return false; + try { + return Updates.runtimeVersion === LEGACY_RUNTIME; + } catch { + return false; + } +} diff --git a/mobile/src/screens/AppLockSettingsScreen.tsx b/mobile/src/screens/AppLockSettingsScreen.tsx index 8e5f1fb..03b5d95 100644 --- a/mobile/src/screens/AppLockSettingsScreen.tsx +++ b/mobile/src/screens/AppLockSettingsScreen.tsx @@ -448,7 +448,7 @@ export function AppLockSettingsScreen() { > Source code - Open source (MIT) — audit it or self-host + Open source (MIT), audit it or self-host {REPO_LABEL} › diff --git a/mobile/src/screens/BlacklistScreen.tsx b/mobile/src/screens/BlacklistScreen.tsx index c1086d9..02326c8 100644 --- a/mobile/src/screens/BlacklistScreen.tsx +++ b/mobile/src/screens/BlacklistScreen.tsx @@ -78,7 +78,7 @@ export function BlacklistScreen() { renderItem={() => ( - Scenes tagged with — or from — anything you hide here stop showing up anywhere in + Scenes tagged with, or from, anything you hide here stop showing up anywhere in the app. Add a tag below (e.g. a category you never want to see). diff --git a/mobile/src/screens/LoginScreen.tsx b/mobile/src/screens/LoginScreen.tsx index 56d5577..be8fee1 100644 --- a/mobile/src/screens/LoginScreen.tsx +++ b/mobile/src/screens/LoginScreen.tsx @@ -75,7 +75,7 @@ export function LoginScreen({ onAuthenticated }: Props) { {advanced ? 'Point the app at your own self-hosted backend. Credentials are stored encrypted in secure-storage on this device only.' - : 'Connects to the public goon instance — no setup needed.'} + : 'Connects to the public goon instance, no setup needed.'} {advanced && ( diff --git a/mobile/src/screens/MovieDetailScreen.tsx b/mobile/src/screens/MovieDetailScreen.tsx index 980a7e0..fdf5def 100644 --- a/mobile/src/screens/MovieDetailScreen.tsx +++ b/mobile/src/screens/MovieDetailScreen.tsx @@ -219,7 +219,7 @@ function WatchChip({ playbackId: pb.id, entityKind: 'movie', durationSec: pb.duration_sec ?? null, - title: `${title} — ${(p.raw as any)?.part_label ?? p.quality}`, + title: `${title}: ${(p.raw as any)?.part_label ?? p.quality}`, mode: (p.direct_url || p.stream_url) ? 'video' : 'webview', headers: pIsDirect && p.headers ? p.headers : undefined, fallbackProxyUrl: pIsDirect ? p.stream_url || undefined : undefined, diff --git a/mobile/src/screens/PerformersScreen.tsx b/mobile/src/screens/PerformersScreen.tsx index ad81f6a..afa53b1 100644 --- a/mobile/src/screens/PerformersScreen.tsx +++ b/mobile/src/screens/PerformersScreen.tsx @@ -72,7 +72,7 @@ export function PerformersScreen() { setOrder('name')} - label="A–Z" + label="A-Z" /> diff --git a/mobile/src/screens/PlayerScreen.tsx b/mobile/src/screens/PlayerScreen.tsx index d578fac..b9e7446 100644 --- a/mobile/src/screens/PlayerScreen.tsx +++ b/mobile/src/screens/PlayerScreen.tsx @@ -1031,7 +1031,7 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) { {isGoneError(playerError?.message) - ? 'The host returned 404/410 — this video was removed.' + ? 'The host returned 404/410, this video was removed.' : (playerError?.message ?? 'The stream did not start.')} @@ -1582,7 +1582,7 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) { Alert.alert( 'Source not loading', 'This source may be blocked in your region or by your network/ISP. ' + - 'It usually works on other connections — try another source on the scene, or a VPN.', + 'It usually works on other connections. Try another source on the scene, or a VPN.', [{ text: 'OK' }], ); }, 25000); @@ -1835,7 +1835,7 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) { Source no longer available - The host returned {httpDead} — this video was removed. + The host returned {httpDead}, this video was removed. {canMark && ( diff --git a/mobile/src/screens/SceneDetailScreen.tsx b/mobile/src/screens/SceneDetailScreen.tsx index 2f07dc4..f2cf8d3 100644 --- a/mobile/src/screens/SceneDetailScreen.tsx +++ b/mobile/src/screens/SceneDetailScreen.tsx @@ -724,7 +724,7 @@ function PlaybackButton({ const embedLinks = unique.filter((l) => !l.stream_url && !!l.embed_url); if (directLinks.length === 0 && embedLinks.length === 0) { - Alert.alert('No stream', 'porn-app did not return any stream URL — falling back to the page.'); + Alert.alert('No stream', 'porn-app did not return any stream URL, falling back to the page.'); await openUrl(source.page_url); return; } diff --git a/mobile/src/screens/ScenesFilterModal.tsx b/mobile/src/screens/ScenesFilterModal.tsx index f2e8cc6..019d65f 100644 --- a/mobile/src/screens/ScenesFilterModal.tsx +++ b/mobile/src/screens/ScenesFilterModal.tsx @@ -205,7 +205,7 @@ export function ScenesFilterModal({ style={styles.search} value={draft.origin} onChangeText={(v) => setDraft({ ...draft, origin: v })} - placeholder="e.g. hqporner, porntrex, xnxx — empty = all" + placeholder="e.g. hqporner, porntrex, xnxx (empty = all)" placeholderTextColor={theme.muted} autoCapitalize="none" autoCorrect={false} diff --git a/mobile/src/screens/SitesScreen.tsx b/mobile/src/screens/SitesScreen.tsx index 672bf46..3bc3e01 100644 --- a/mobile/src/screens/SitesScreen.tsx +++ b/mobile/src/screens/SitesScreen.tsx @@ -180,7 +180,7 @@ function formatRelativeTime(iso: string | null): string | null { // Rząd gwiazdek 0-5. 0 = offline (czerwony label zamiast gwiazdek). Brak oceny → null. function Stars({ value, size = 13 }: { value: number | null | undefined; size?: number }) { - if (value == null) return — not rated; + if (value == null) return not rated; if (value <= 0) return ● OFFLINE; const full = '★'.repeat(value); const empty = '☆'.repeat(5 - value); @@ -266,7 +266,7 @@ function RatingModal({ source, onClose }: { source: SourceOut | null; onClose: ( {source ? prettySiteName(source.display_name) : ''} {!r ? ( - Not rated yet — check back soon. + Not rated yet, check back soon. ) : ( <> @@ -288,7 +288,7 @@ function RatingModal({ source, onClose }: { source: SourceOut | null; onClose: ( {basis === 'telemetry' ? `Measured from real playback: ${successRate ?? 0}% success over ${attempts} plays (7d).` - : 'Estimated from how this source streams (no playback data yet — collecting).'} + : 'Estimated from how this source streams (no playback data yet, collecting).'} )} diff --git a/mobile/src/screens/TagsScreen.tsx b/mobile/src/screens/TagsScreen.tsx index 3528b46..03abf51 100644 --- a/mobile/src/screens/TagsScreen.tsx +++ b/mobile/src/screens/TagsScreen.tsx @@ -72,7 +72,7 @@ export function TagsScreen() { setOrder('name')} - label="A–Z" + label="A-Z" />