fix(player): przekierowanie z pierwszego adresu przechodzi (myvidplay -> playmogo), rodzina dooda jako jeden host; jeden wiersz ikon na ekranie strony i performera
- WebView: przed pierwszym onLoadEnd kazda nawigacja top-frame to lancuch redirectow serwera, nie popunder; po zaladowaniu przeskoki w obrebie rodziny DoodStream tez OK - SiteScenes: wyszukiwarka w obrebie strony + ikona tagow z licznikiem + X - PerformerScenes: licznik scen + ikony szukaj-wiecej/filtr w jednym wierszu, chipsy top-tagow wyciete (tagi sa w filtrze), zbedne zapytanie performer-tags usuniete - changelog 2026-09-07h, OTA 49cda698 na 1.3
This commit is contained in:
parent
e13463cba4
commit
b0350baff0
4 changed files with 73 additions and 79 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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={
|
||||
<View>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.subtitle}>
|
||||
{/* 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. */}
|
||||
<Toolbar>
|
||||
<Text style={[styles.subtitle, { flex: 1 }]} numberOfLines={1}>
|
||||
{scenesQuery.data
|
||||
? `${scenesTotal}${scenesSuffix} ${scenesTotal === 1 ? 'scene' : 'scenes'}`
|
||||
: ' '}
|
||||
</Text>
|
||||
</View>
|
||||
{/* 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). */}
|
||||
<View style={styles.actionRow}>
|
||||
<Pressable
|
||||
style={[
|
||||
styles.actionBtn,
|
||||
refreshMutation.isPending && styles.refreshBtnLoading,
|
||||
]}
|
||||
onPress={() => refreshMutation.mutate()}
|
||||
disabled={refreshMutation.isPending}
|
||||
>
|
||||
{refreshMutation.isPending ? (
|
||||
<ActivityIndicator color={theme.accent} size="small" />
|
||||
<ActivityIndicator color={theme.accentText} style={{ width: 44 }} />
|
||||
) : (
|
||||
<Text style={styles.actionBtnTextPrimary}>↻ Search more scenes</Text>
|
||||
<IconButton
|
||||
icon="magnify-scan"
|
||||
accessibilityLabel="Search more scenes"
|
||||
onPress={() => refreshMutation.mutate()}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={[styles.actionBtn, filterActiveCount > 0 && styles.actionBtnActive]}
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
accessibilityLabel="Filters"
|
||||
active={filterActiveCount > 0}
|
||||
badge={filterActiveCount}
|
||||
onPress={() => setFilterOpen(true)}
|
||||
>
|
||||
<Text style={styles.actionBtnTextPrimary}>
|
||||
Filter{filterActiveCount > 0 ? ` ${filterActiveCount}` : ''}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
{tab === 'scenes' && !!tagsQuery.data?.items.length && (
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={styles.tagRow}
|
||||
>
|
||||
{tagsQuery.data.items.map((t) => {
|
||||
const active = selectedTag?.slug === t.slug;
|
||||
return (
|
||||
<Pressable
|
||||
key={t.id}
|
||||
style={[styles.tagChip, active && styles.tagChipActive]}
|
||||
onPress={() =>
|
||||
setSelectedTag(active ? null : { slug: t.slug, name: t.name })
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={[styles.tagChipText, active && styles.tagChipTextActive]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{active ? `${t.name} ✕` : t.name}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
)}
|
||||
/>
|
||||
</Toolbar>
|
||||
</View>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
|
|
|
|||
|
|
@ -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<string | null>(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
|
||||
|
|
|
|||
|
|
@ -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<string[]>([]);
|
||||
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 (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.toolbar}>
|
||||
<Pressable
|
||||
style={[styles.filterBtn, selectedTags.length > 0 && styles.filterBtnActive]}
|
||||
<Toolbar>
|
||||
<SearchInput value={q} onChangeText={setQ} placeholder={`search ${name}…`} />
|
||||
<IconButton
|
||||
icon="tag-multiple-outline"
|
||||
accessibilityLabel="Filter by tags"
|
||||
active={selectedTags.length > 0}
|
||||
badge={selectedTags.length}
|
||||
onPress={() => setFilterOpen(true)}
|
||||
>
|
||||
<Text style={styles.filterBtnText}>
|
||||
Tags{selectedTags.length > 0 ? ` ${selectedTags.length}` : ''}
|
||||
</Text>
|
||||
</Pressable>
|
||||
/>
|
||||
{selectedTags.length > 0 ? (
|
||||
<Pressable style={styles.clearBtn} onPress={() => setSelectedTags([])}>
|
||||
<Text style={styles.clearBtnText}>Clear</Text>
|
||||
</Pressable>
|
||||
<IconButton icon="close" accessibilityLabel="Clear tags" onPress={() => setSelectedTags([])} />
|
||||
) : null}
|
||||
</View>
|
||||
</Toolbar>
|
||||
|
||||
<TagPickerModal
|
||||
visible={filterOpen}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue