diff --git a/mobile/src/api.ts b/mobile/src/api.ts index f4675a5..20094d8 100644 --- a/mobile/src/api.ts +++ b/mobile/src/api.ts @@ -436,13 +436,14 @@ export class GoonClient { return this.request(`/scenes/${sceneId}/enrich-duration`, { method: 'POST' }); } - async enrichSceneThumbnail(sceneId: string): Promise<{ + async enrichSceneThumbnail(sceneId: string, force = false): Promise<{ scene_id: string; thumbnail_url: string | null; tube_used: string | null; sources_updated: number; }> { - return this.request(`/scenes/${sceneId}/enrich-thumbnail`, { method: 'POST' }); + const qs = force ? '?force=true' : ''; + return this.request(`/scenes/${sceneId}/enrich-thumbnail${qs}`, { method: 'POST' }); } async enrichSceneStudio(sceneId: string): Promise<{ diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index 3dcfc23..2ef1763 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -16,6 +16,15 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + id: '2026-06-13', + date: 'June 2026', + items: [ + '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.', + ], + }, { id: '2026-06-12', date: 'June 2026', diff --git a/mobile/src/screens/AppLockSettingsScreen.tsx b/mobile/src/screens/AppLockSettingsScreen.tsx index 2621703..181c310 100644 --- a/mobile/src/screens/AppLockSettingsScreen.tsx +++ b/mobile/src/screens/AppLockSettingsScreen.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { ActivityIndicator, Alert, + Linking, Pressable, ScrollView, StyleSheet, @@ -266,11 +267,26 @@ export function AppLockSettingsScreen() { {APP_VERSION} + Linking.openURL(REPO_URL).catch(() => {})} + > + + Source code + Open source (MIT) — audit it or self-host + + {REPO_LABEL} › + ); } +// Publiczne repo OSS (zgłoszenie usera 4c5066b8: "no source code repo linked"). +// Sygnał zaufania dla sideloadowanej apki 18+: audyt kodu / self-host / kontrybucja. +const REPO_URL = 'https://github.com/goon-foss/goon'; +const REPO_LABEL = 'goon-foss/goon'; + const styles = StyleSheet.create({ root: { flex: 1, backgroundColor: theme.bg }, center: { flex: 1, backgroundColor: theme.bg, alignItems: 'center', justifyContent: 'center' }, @@ -320,6 +336,7 @@ const styles = StyleSheet.create({ }, chipActive: { backgroundColor: theme.accent, borderColor: theme.accent }, versionValue: { color: theme.fg, fontSize: 15, fontWeight: '700', fontVariant: ['tabular-nums'] }, + linkValue: { color: theme.accent, fontSize: 15, fontWeight: '700' }, chipText: { color: theme.muted, fontSize: 13 }, chipTextActive: { color: '#fff', fontWeight: '700' }, }); diff --git a/mobile/src/screens/SceneDetailScreen.tsx b/mobile/src/screens/SceneDetailScreen.tsx index fb4a0d3..1588765 100644 --- a/mobile/src/screens/SceneDetailScreen.tsx +++ b/mobile/src/screens/SceneDetailScreen.tsx @@ -119,6 +119,21 @@ export function SceneDetailScreen() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [data?.id]); + // Ręczny refresh miniatury (force) — zgłoszenie d3376a71: zepsuta/stała miniaturka + // (np. rotting sxyprn/trafficdeposit). Nadpisuje istniejącą świeżą ze strony tube'a. + const refreshThumbMutation = useMutation({ + mutationFn: () => client.enrichSceneThumbnail(id, true), + onSuccess: (out) => { + queryClient.invalidateQueries({ queryKey: ['scene', id] }); + queryClient.invalidateQueries({ queryKey: ['scenes'] }); + Alert.alert( + 'Thumbnail', + out.thumbnail_url ? 'Refreshed from the source page.' : 'Could not fetch a fresh thumbnail.', + ); + }, + onError: (e) => Alert.alert('Thumbnail', e instanceof Error ? e.message : String(e)), + }); + // Auto-enrich tags: search-only scrapery nie pobierają tagów z detail page. // Pornhat ma `js-ajax-tag` data-setup JSON; xhamster/xvideos/youporn/inne mają // dedicated tag_extract patterny. SceneDetail wywołuje 1 fetch → upsert do DB. @@ -223,6 +238,18 @@ export function SceneDetailScreen() { + {data.playback_sources.some((s) => s.origin?.startsWith('tube:')) && ( + refreshThumbMutation.mutate()} + disabled={refreshThumbMutation.isPending} + > + + {refreshThumbMutation.isPending ? 'Refreshing thumbnail…' : '↻ Refresh thumbnail'} + + + )} + {(data.code || data.director) && ( {data.code ? code · {data.code} : null} @@ -854,6 +881,17 @@ const styles = StyleSheet.create({ fontSize: 13, }, tagHint: { color: theme.mutedDim, fontSize: 11, marginTop: 6 }, + refreshThumbBtn: { + alignSelf: 'flex-start', + paddingVertical: 6, + paddingHorizontal: 12, + borderRadius: 8, + borderWidth: 1, + borderColor: theme.border, + backgroundColor: theme.card, + marginBottom: 12, + }, + refreshThumbText: { color: theme.muted, fontSize: 13, fontWeight: '600' }, pillSource: { borderColor: theme.accent,