feat(mobile): movies — performer filter + 3-column grid
Two Movies-list reports. (1) 1044cd34 'do movies have a metadata base for performers/categories/studio/year': yes — 90% have year, 92% studio, 81% performers, 93% tags, and the filter already covered studio/genre/year. Added the missing dimension: a performer search-and-select in MovieFiltersSheet (backend listMovies + api.ts already accepted performer_ids; only the UI was missing). (2) 0200956f 'use the space better': Movies grid goes 2 -> 3 columns (poster card is flex:1, scales fine) so ~50% more films per screen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
960bc75be4
commit
4afebacad8
3 changed files with 95 additions and 2 deletions
|
|
@ -16,6 +16,14 @@ export type ChangelogEntry = {
|
|||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
id: '2026-06-21b',
|
||||
date: 'June 2026',
|
||||
items: [
|
||||
'Filter movies by performer (Movies → Filter → Performers).',
|
||||
'Movies grid fits more per screen (3 columns).',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2026-06-21',
|
||||
date: 'June 2026',
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export interface MovieFilters {
|
|||
has_playback: boolean;
|
||||
tag_slugs: string[];
|
||||
studio_slugs: string[];
|
||||
performer_ids: string[];
|
||||
}
|
||||
|
||||
export const DEFAULT_MOVIE_FILTERS: MovieFilters = {
|
||||
|
|
@ -43,6 +44,7 @@ export const DEFAULT_MOVIE_FILTERS: MovieFilters = {
|
|||
has_playback: false,
|
||||
tag_slugs: [],
|
||||
studio_slugs: [],
|
||||
performer_ids: [],
|
||||
};
|
||||
|
||||
const SORT_OPTIONS: { value: MoviesSort; label: string }[] = [
|
||||
|
|
@ -126,6 +128,35 @@ export function MovieFiltersSheet({ visible, value, onChange, onClose }: Props)
|
|||
}));
|
||||
};
|
||||
|
||||
// Performers — long-tail, więc search-by-q (nie top-N jak studia/genres). `pnames`
|
||||
// zapamiętuje id→imię z wyników, żeby zaznaczone chipy pokazywały nazwę.
|
||||
const [perfQ, setPerfQ] = useState('');
|
||||
const [pnames, setPnames] = useState<Record<string, string>>({});
|
||||
const performersQuery = useQuery({
|
||||
queryKey: ['movie-perf-search', perfQ],
|
||||
queryFn: () => client.listPerformers({ q: perfQ, order: 'scene_count', per_page: 30 }),
|
||||
enabled: visible && perfQ.trim().length > 0,
|
||||
});
|
||||
React.useEffect(() => {
|
||||
const items = performersQuery.data?.items;
|
||||
if (!items?.length) return;
|
||||
setPnames((m) => {
|
||||
const next = { ...m };
|
||||
for (const p of items) next[p.id] = p.canonical_name;
|
||||
return next;
|
||||
});
|
||||
}, [performersQuery.data]);
|
||||
|
||||
const togglePerformer = (id: string, name: string) => {
|
||||
setPnames((m) => ({ ...m, [id]: name }));
|
||||
setDraft((d) => ({
|
||||
...d,
|
||||
performer_ids: d.performer_ids.includes(id)
|
||||
? d.performer_ids.filter((x) => x !== id)
|
||||
: [...d.performer_ids, id],
|
||||
}));
|
||||
};
|
||||
|
||||
const apply = () => {
|
||||
const yf = parseInt(yearFromText, 10);
|
||||
const yt = parseInt(yearToText, 10);
|
||||
|
|
@ -263,6 +294,56 @@ export function MovieFiltersSheet({ visible, value, onChange, onClose }: Props)
|
|||
<Text style={styles.empty}>no studios yet</Text>
|
||||
)}
|
||||
|
||||
<Text style={styles.label}>
|
||||
Performers {draft.performer_ids.length > 0 ? `(${draft.performer_ids.length})` : ''}
|
||||
</Text>
|
||||
{draft.performer_ids.length > 0 ? (
|
||||
<View style={styles.chipRow}>
|
||||
{draft.performer_ids.map((id) => (
|
||||
<TouchableOpacity
|
||||
key={id}
|
||||
style={[styles.chip, styles.chipActive]}
|
||||
onPress={() => togglePerformer(id, pnames[id] || '')}
|
||||
>
|
||||
<Text style={[styles.chipText, styles.chipTextActive]}>
|
||||
{pnames[id] || 'performer'} ✕
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
<TextInput
|
||||
style={[styles.input, { marginTop: 8 }]}
|
||||
value={perfQ}
|
||||
onChangeText={setPerfQ}
|
||||
placeholder="search performers…"
|
||||
placeholderTextColor={theme.mutedDim}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
{perfQ.trim().length > 0 ? (
|
||||
performersQuery.isLoading ? (
|
||||
<ActivityIndicator color={theme.muted} style={{ marginVertical: 8 }} />
|
||||
) : (
|
||||
<View style={[styles.chipRow, { marginTop: 8 }]}>
|
||||
{(performersQuery.data?.items ?? [])
|
||||
.filter((p) => !draft.performer_ids.includes(p.id))
|
||||
.map((p) => (
|
||||
<TouchableOpacity
|
||||
key={p.id}
|
||||
style={styles.chip}
|
||||
onPress={() => togglePerformer(p.id, p.canonical_name)}
|
||||
>
|
||||
<Text style={styles.chipText}>
|
||||
{p.canonical_name}
|
||||
{p.scene_count > 0 ? ` · ${p.scene_count}` : ''}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
) : null}
|
||||
|
||||
<View style={styles.toggleRow}>
|
||||
<Text style={styles.toggleLabel}>Only with playback</Text>
|
||||
<Switch
|
||||
|
|
@ -301,7 +382,8 @@ export function isDefaultFilters(f: MovieFilters): boolean {
|
|||
f.year_to === null &&
|
||||
!f.has_playback &&
|
||||
f.tag_slugs.length === 0 &&
|
||||
f.studio_slugs.length === 0
|
||||
f.studio_slugs.length === 0 &&
|
||||
f.performer_ids.length === 0
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ import type { RootStackParamList } from '../navigation';
|
|||
import { theme } from '../theme';
|
||||
|
||||
const PER_PAGE = 30;
|
||||
const NUM_COLS = 2;
|
||||
// 3 kolumny — plakaty 2:3 mieszczą się dobrze i widać 50% więcej filmów na ekran
|
||||
// (user-report 0200956f: lepiej wykorzystać przestrzeń). Karta ma flex:1, skaluje się.
|
||||
const NUM_COLS = 3;
|
||||
|
||||
export function MoviesScreen() {
|
||||
const client = useClient();
|
||||
|
|
@ -86,6 +88,7 @@ export function MoviesScreen() {
|
|||
has_playback: filters.has_playback || undefined,
|
||||
tags: filters.tag_slugs.length ? filters.tag_slugs : undefined,
|
||||
studio_slugs: filters.studio_slugs.length ? filters.studio_slugs : undefined,
|
||||
performer_ids: filters.performer_ids.length ? filters.performer_ids : undefined,
|
||||
page: pageParam,
|
||||
per_page: PER_PAGE,
|
||||
}),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue