diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index fea1089..3278fec 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -29,6 +29,14 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + id: '2026-09-07h', + date: 'September 2026', + items: [ + 'Movies on myvidplay and doply play again. Those hosts redirect to playmogo, and the player was treating that redirect as a pop-up and blocking it, so you got a blank screen. The human check on playmogo still needs your tap.', + 'Site and performer pages use the same single row of icons as the main sections: search plus tag filter on a site, "search more" plus filter next to the scene count on a performer. The tag chips row on performer pages is gone, tags live in the filter.', + ], + }, { id: '2026-09-07g', date: 'September 2026', diff --git a/mobile/src/screens/PerformerScenesScreen.tsx b/mobile/src/screens/PerformerScenesScreen.tsx index 540e2f0..a6c6906 100644 --- a/mobile/src/screens/PerformerScenesScreen.tsx +++ b/mobile/src/screens/PerformerScenesScreen.tsx @@ -28,6 +28,7 @@ import { ErrorBoundary } from '../ErrorBoundary'; import type { RootStackParamList } from '../navigation'; import { theme } from '../theme'; import type { SceneOut } from '../types'; +import { IconButton, Toolbar } from '../ui'; type Tab = 'scenes' | 'movies'; @@ -105,15 +106,8 @@ export function PerformerScenesScreen() { }, }); - // Top tagi/kategorie performera (chipsy w headerze, bug-report 1a4bf258, zastąpiły - // dev-only przycisk Re-scrape). Backend agreguje scene_tags po scenach z żywym - // playbackiem. Tap → TagScenes. Rescrape (bulk enrich miniaturek/tagów) został - // przeniesiony do flow per-scene (SceneDetail), na liście performera był devowy szum. - const tagsQuery = useQuery({ - queryKey: ['performer-tags', id], - queryFn: () => client.performerTags(id, 18), - staleTime: 5 * 60_000, - }); + // Chipsy top-tagow performera (bug-report 1a4bf258) zniknely 2026-09-07 razem z + // wierszem akcji: tagi sa w filtrze, a naglowek to jeden wiersz ikon jak na Scenes. const onHide = () => { Alert.alert( @@ -293,68 +287,32 @@ export function PerformerScenesScreen() { onRefresh={scenesQuery.refetch} ListHeaderComponent={ - - + {/* Jeden wiersz (2026-09-07): licznik scen po lewej, po prawej ikony + „szukaj wiecej" i filtr z licznikiem. Chipsy tagow pod spodem + zniknely, tagi sa w filtrze. */} + + {scenesQuery.data ? `${scenesTotal}${scenesSuffix} ${scenesTotal === 1 ? 'scene' : 'scenes'}` : ' '} - - {/* Bug-report 2026-05-17 (f3f019d0): "elementy obsługowe zajmują za - dużo ekranu" + 1a4bf258: "Re-scrape mógłby zniknąć, za to tagi/ - kategorie by mogły". Re-scrape (dev-only bulk enrich) usunięty; - Search zostaje, pod nim chipsy top-tagów performera (tap → TagScenes). */} - - refreshMutation.mutate()} - disabled={refreshMutation.isPending} - > - {refreshMutation.isPending ? ( - - ) : ( - ↻ Search more scenes - )} - - 0 && styles.actionBtnActive]} + {refreshMutation.isPending ? ( + + ) : ( + refreshMutation.mutate()} + /> + )} + 0} + badge={filterActiveCount} onPress={() => setFilterOpen(true)} - > - - Filter{filterActiveCount > 0 ? ` ${filterActiveCount}` : ''} - - - - {tab === 'scenes' && !!tagsQuery.data?.items.length && ( - - {tagsQuery.data.items.map((t) => { - const active = selectedTag?.slug === t.slug; - return ( - - setSelectedTag(active ? null : { slug: t.slug, name: t.name }) - } - > - - {active ? `${t.name} ✕` : t.name} - - - ); - })} - - )} + /> + } ListEmptyComponent={ diff --git a/mobile/src/screens/PlayerScreen.tsx b/mobile/src/screens/PlayerScreen.tsx index 7989d40..baa2140 100644 --- a/mobile/src/screens/PlayerScreen.tsx +++ b/mobile/src/screens/PlayerScreen.tsx @@ -1922,7 +1922,14 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) { // używany do allow-listy redirectów same-eTLD+1. DECLARED HERE (przed useEffectami // które go używają) żeby uniknąć ref-before-decl errors w lint. const initialHostRef = React.useRef(null); + // Czy WebView skonczyl ladowac JAKAKOLWIEK strone. Do tego momentu kazda nawigacja + // top-frame to lancuch przekierowan serwera z pierwszego adresu (myvidplay.com 301 -> + // playmogo.com), nie popunder: popunder potrzebuje zaladowanej strony z JS. Blokada + // cross-domain przed pierwszym zaladowaniem zostawiala pusty ekran na kazdym + // hosterze, ktory zmienil domene (zgloszenie 1278eb49, freeomovie/myvidplay). + const webFirstLoadDoneRef = React.useRef(false); React.useEffect(() => { + webFirstLoadDoneRef.current = false; try { initialHostRef.current = new URL(url).hostname; } catch { @@ -1989,6 +1996,11 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) { // WebView blokuje cross-origin nav (popunder ad opens new tab/page poza embeddem). // Pierwsze ładowanie (`url`) ma `navigationType=undefined` lub `other`, przepuszczamy. // Cokolwiek innego (klik, JS redirect) z innego origin → block i log. + // Ta sama lista co w doodstream.ts DOOD_HOSTS, tu jako regex po hoscie (bez importu + // modulu ladowanego dynamicznie w resolverze). + const isDoodHost = (h: string) => + /vik[i1]ngfile\.|playmogo\.|dood|d0000d|dooood|do0od|do7go|ds2play|doply\.|myvidplay\./i.test(h); + // initialHostRef już declared wyżej (przed useEffect który robi nav.replace). const onShouldStartLoad = React.useCallback( (req: { url: string; navigationType?: string; isTopFrame?: boolean }) => { @@ -2008,12 +2020,18 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) { } return true; } + // Przed pierwszym zaladowaniem strony to jeszcze przekierowania serwera z + // pierwszego adresu (patrz webFirstLoadDoneRef), przepusc. + if (!webFirstLoadDoneRef.current) return true; // Top-frame nav: pozwól na same-eTLD+1 (paradisehill.cc → en.paradisehill.cc, // pervl3.io → pervl5.io); blokuj cross-domain nav (adware popundery). try { const target = new URL(req.url).hostname.toLowerCase(); const initial = initialHostRef.current?.toLowerCase(); if (initial && target === initial) return true; + // Rodzina DoodStreama (myvidplay/doply -> playmogo) to jeden hoster pod kilkoma + // domenami; przeskok miedzy nimi po zaladowaniu (np. po Turnstile) tez jest legalny. + if (initial && isDoodHost(target) && isDoodHost(initial)) return true; if (initial) { const tParts = target.split('.'); const iParts = initial.split('.'); @@ -2074,6 +2092,9 @@ function EmbedWebViewPlayer({ params }: { params: RouteParams }) { injectedJavaScriptBeforeContentLoaded={INJECTED_JS} onMessage={onMessage} onShouldStartLoadWithRequest={onShouldStartLoad} + onLoadEnd={() => { + webFirstLoadDoneRef.current = true; + }} onHttpError={onHttpError} setSupportMultipleWindows={false} allowsInlineMediaPlayback diff --git a/mobile/src/screens/SiteScenesScreen.tsx b/mobile/src/screens/SiteScenesScreen.tsx index 9c098bf..0fd2f3d 100644 --- a/mobile/src/screens/SiteScenesScreen.tsx +++ b/mobile/src/screens/SiteScenesScreen.tsx @@ -29,6 +29,7 @@ import { usePreferences } from '../PreferencesContext'; import type { RootStackParamList } from '../navigation'; import { theme } from '../theme'; import type { SceneOut, TagOut } from '../types'; +import { IconButton, SearchInput, Toolbar } from '../ui'; export function SiteScenesScreen() { const client = useClient(); @@ -46,6 +47,13 @@ export function SiteScenesScreen() { // stronach Sites". Multi-select AND (passed as comma-CSV do `tags` query). const [selectedTags, setSelectedTags] = useState([]); const [filterOpen, setFilterOpen] = useState(false); + // Szukanie w obrebie strony (2026-09-07): ten sam wiersz narzedzi co na Scenes. + const [q, setQ] = useState(''); + const [debouncedQ, setDebouncedQ] = useState(''); + React.useEffect(() => { + const t = setTimeout(() => setDebouncedQ(q.trim()), 350); + return () => clearTimeout(t); + }, [q]); const PER_PAGE = 50; const { @@ -58,10 +66,11 @@ export function SiteScenesScreen() { hasNextPage, isFetchingNextPage, } = useInfiniteQuery({ - queryKey: ['site-scenes', origin, selectedTags.sort().join(',')], + queryKey: ['site-scenes', origin, selectedTags.sort().join(','), debouncedQ], queryFn: ({ pageParam = 1 }) => client.listScenes({ origin, + q: debouncedQ || undefined, tags: selectedTags.length > 0 ? selectedTags : undefined, sort: 'release_date', // 0 = nie nakładaj progu długości. User wszedł JAWNIE w tę pozycję, więc @@ -87,21 +96,19 @@ export function SiteScenesScreen() { return ( - - 0 && styles.filterBtnActive]} + + + 0} + badge={selectedTags.length} onPress={() => setFilterOpen(true)} - > - - Tags{selectedTags.length > 0 ? ` ${selectedTags.length}` : ''} - - + /> {selectedTags.length > 0 ? ( - setSelectedTags([])}> - Clear - + setSelectedTags([])} /> ) : null} - +