fix(mobile): show Refresh thumbnail when the hero image actually fails to load
The button keyed on thumbnail_url presence, but a URL can be present yet broken (hqfap 404 → blank hero, no button — report ef0c6a5a). Tie it to the hero Image load state (onLoad ok / onError broken / no url none) and show Refresh only when the image is broken or missing. Reconciles 26c114ed (hidden for good previews) with ef0c6a5a. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
81d617efc2
commit
b66dd99eba
1 changed files with 38 additions and 23 deletions
|
|
@ -134,6 +134,14 @@ export function SceneDetailScreen() {
|
|||
onError: (e) => Alert.alert('Thumbnail', e instanceof Error ? e.message : String(e)),
|
||||
});
|
||||
|
||||
// Stan ładowania hero-miniatury: 'ok' (załadowała się — domyślnie, brak migotania
|
||||
// przycisku), 'none' (brak URL), 'broken' (onError). Steruje widocznością przycisku
|
||||
// Refresh. Reset przy zmianie sceny.
|
||||
const [thumbState, setThumbState] = React.useState<'none' | 'ok' | 'broken'>('ok');
|
||||
React.useEffect(() => {
|
||||
setThumbState('ok');
|
||||
}, [id]);
|
||||
|
||||
// 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.
|
||||
|
|
@ -235,19 +243,15 @@ export function SceneDetailScreen() {
|
|||
|
||||
return (
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.scrollContent}>
|
||||
<Hero data={data} />
|
||||
<Hero data={data} onThumbState={setThumbState} />
|
||||
|
||||
<View style={styles.body}>
|
||||
{/* Refresh tylko gdy BRAK dobrej miniatury (brak w ogóle lub rotting sxyprn/
|
||||
trafficdeposit) — feedback 26c114ed: na scenach z dobrą miniaturą przycisk
|
||||
był zbędnym szumem. Dla zepsutych/stałych nadal się pojawia (cel d3376a71). */}
|
||||
{(() => {
|
||||
const hasTube = data.playback_sources.some((s) => s.origin?.startsWith('tube:'));
|
||||
const hasGoodThumb = data.playback_sources.some(
|
||||
(s) => s.thumbnail_url && !/trafficdeposit|sxyprn|\/proxy\/sxyprn-thumb/i.test(s.thumbnail_url),
|
||||
);
|
||||
if (!hasTube || hasGoodThumb) return null;
|
||||
return (
|
||||
{/* Refresh pokazujemy gdy wyświetlana miniatura faktycznie się NIE ładuje
|
||||
(brak URL lub obraz padł — onError), nie tylko po obecności URL. Łączy
|
||||
feedback 26c114ed (nie na każdej scenie z dobrą miniaturą) i ef0c6a5a
|
||||
(thumbnail_url bywa obecny, ale obraz 404 → user widzi puste pole). */}
|
||||
{data.playback_sources.some((s) => s.origin?.startsWith('tube:')) &&
|
||||
thumbState !== 'ok' ? (
|
||||
<Pressable
|
||||
style={styles.refreshThumbBtn}
|
||||
onPress={() => refreshThumbMutation.mutate()}
|
||||
|
|
@ -257,8 +261,7 @@ export function SceneDetailScreen() {
|
|||
{refreshThumbMutation.isPending ? 'Refreshing thumbnail…' : '↻ Refresh thumbnail'}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})()}
|
||||
) : null}
|
||||
|
||||
{(data.code || data.director) && (
|
||||
<View style={styles.metaRow}>
|
||||
|
|
@ -379,7 +382,13 @@ export function SceneDetailScreen() {
|
|||
);
|
||||
}
|
||||
|
||||
function Hero({ data }: { data: SceneOut }) {
|
||||
function Hero({
|
||||
data,
|
||||
onThumbState,
|
||||
}: {
|
||||
data: SceneOut;
|
||||
onThumbState?: (s: 'none' | 'ok' | 'broken') => void;
|
||||
}) {
|
||||
const { width } = useWindowDimensions();
|
||||
const heroHeight = Math.round(width * (9 / 16));
|
||||
|
||||
|
|
@ -388,6 +397,10 @@ function Hero({ data }: { data: SceneOut }) {
|
|||
const staticUrl = data.playback_sources.find((s) => s.thumbnail_url)?.thumbnail_url;
|
||||
const url = animatedUrl ?? staticUrl;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!url) onThumbState?.('none');
|
||||
}, [url, onThumbState]);
|
||||
|
||||
const minutes = data.duration_sec ? Math.floor(data.duration_sec / 60) : null;
|
||||
const meta = [
|
||||
data.release_date ?? null,
|
||||
|
|
@ -415,6 +428,8 @@ function Hero({ data }: { data: SceneOut }) {
|
|||
style={StyleSheet.absoluteFill}
|
||||
contentFit="cover"
|
||||
transition={200}
|
||||
onLoad={() => onThumbState?.('ok')}
|
||||
onError={() => onThumbState?.('broken')}
|
||||
/>
|
||||
<LinearGradient
|
||||
colors={['transparent', 'rgba(8,9,15,0.55)', theme.bg]}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue