feat(mobile): source-code link in Settings + Refresh thumbnail button

- AppLockSettings: a "Source code" row linking the public OSS repo (report 4c5066b8) -
  a trust signal for a sideloaded FOSS app (audit / self-host / contribute).
- SceneDetail: a "Refresh thumbnail" button (force) for scenes whose preview is broken
  or stale (report d3376a71).
- changelog: new What's New entry for this batch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtrzupek 2026-06-13 19:04:11 +02:00
parent e512665d26
commit 9269b02a4c
4 changed files with 67 additions and 2 deletions

View file

@ -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<{

View file

@ -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',

View file

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import {
ActivityIndicator,
Alert,
Linking,
Pressable,
ScrollView,
StyleSheet,
@ -266,11 +267,26 @@ export function AppLockSettingsScreen() {
</View>
<Text style={styles.versionValue}>{APP_VERSION}</Text>
</View>
<Pressable
style={styles.row}
onPress={() => Linking.openURL(REPO_URL).catch(() => {})}
>
<View style={{ flex: 1 }}>
<Text style={styles.label}>Source code</Text>
<Text style={styles.hint}>Open source (MIT) audit it or self-host</Text>
</View>
<Text style={styles.linkValue}>{REPO_LABEL} </Text>
</Pressable>
</View>
</ScrollView>
);
}
// 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' },
});

View file

@ -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() {
<Hero data={data} />
<View style={styles.body}>
{data.playback_sources.some((s) => s.origin?.startsWith('tube:')) && (
<Pressable
style={styles.refreshThumbBtn}
onPress={() => refreshThumbMutation.mutate()}
disabled={refreshThumbMutation.isPending}
>
<Text style={styles.refreshThumbText}>
{refreshThumbMutation.isPending ? 'Refreshing thumbnail…' : '↻ Refresh thumbnail'}
</Text>
</Pressable>
)}
{(data.code || data.director) && (
<View style={styles.metaRow}>
{data.code ? <Text style={styles.metaDim}>code · {data.code}</Text> : 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,