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}
-
+