feat(mobile): sortowanie losowe, najdluzsze i najkrotsze w filtrze scen; seed losowy per lista, nowy przy pull-to-refresh
- changelog 2026-09-14b, OTA 6770483f na 1.3 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
bd8af5f44c
commit
656604e868
6 changed files with 41 additions and 5 deletions
|
|
@ -157,6 +157,7 @@ export class GoonClient {
|
|||
if (params.vertical) qs.set('vertical', params.vertical);
|
||||
if (params.origin) qs.set('origin', params.origin);
|
||||
if (params.sort) qs.set('sort', params.sort);
|
||||
if (params.seed) qs.set('seed', params.seed);
|
||||
qs.set('page', String(params.page ?? 1));
|
||||
qs.set('per_page', String(params.per_page ?? 50));
|
||||
return this.request(`/scenes?${qs.toString()}`);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ export type ChangelogEntry = {
|
|||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
id: '2026-09-14b',
|
||||
date: 'September 2026',
|
||||
items: [
|
||||
'Three new sort orders in the scene filter: random, longest and shortest. Random keeps its order while you scroll and reshuffles when you pull to refresh. Works on performer pages too.',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2026-09-14a',
|
||||
date: 'September 2026',
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ export function PerformerScenesScreen() {
|
|||
const [selectedTag, setSelectedTag] = React.useState<{ slug: string; name: string } | null>(null);
|
||||
|
||||
const [filter, setFilter] = React.useState<FilterState>(PERFORMER_DEFAULT_FILTER);
|
||||
const [seed, setSeed] = React.useState(() => Math.random().toString(36).slice(2) + Date.now().toString(36));
|
||||
const [filterOpen, setFilterOpen] = React.useState(false);
|
||||
// Licznik liczony WZGLĘDEM domyślnych tego ekranu, nie jak w głównym feedzie, gdzie
|
||||
// domyślne has_playback=true podbija licznik i "Filter 1" świeci na czysto.
|
||||
|
|
@ -170,7 +171,7 @@ export function PerformerScenesScreen() {
|
|||
(!filter.hasPlayback ? 1 : 0);
|
||||
|
||||
const scenesQuery = useQuery({
|
||||
queryKey: ['performer-scenes', id, selectedTag?.slug ?? null, vertical ?? null, filter],
|
||||
queryKey: ['performer-scenes', id, selectedTag?.slug ?? null, vertical ?? null, filter, filter.sort === 'random' ? seed : null],
|
||||
queryFn: () =>
|
||||
client.listScenes({
|
||||
// Wybrani w modalu to KONIUNKCJA z bieżącym performerem ("sceny także z..."),
|
||||
|
|
@ -192,6 +193,7 @@ export function PerformerScenesScreen() {
|
|||
min_quality_p: filter.minQualityP > 0 ? filter.minQualityP : undefined,
|
||||
include_stubs: filter.includeStubs || undefined,
|
||||
sort: filter.sort,
|
||||
seed: filter.sort === 'random' ? seed : undefined,
|
||||
per_page: 200,
|
||||
// Domyślnie ukrywamy stuby TPDB/StashDB bez tube-linków; wyłączenie w modalu
|
||||
// pokazuje pełną filmografię, z pozycjami, których (jeszcze) nie da się odtworzyć.
|
||||
|
|
@ -280,7 +282,10 @@ export function PerformerScenesScreen() {
|
|||
<SceneTile scene={item} seenSince={seenSince} secondLine="studio" />
|
||||
)}
|
||||
refreshing={scenesQuery.isRefetching}
|
||||
onRefresh={scenesQuery.refetch}
|
||||
onRefresh={() => {
|
||||
if (filter.sort === 'random') setSeed(Math.random().toString(36).slice(2) + Date.now().toString(36));
|
||||
else scenesQuery.refetch();
|
||||
}}
|
||||
ListHeaderComponent={
|
||||
<View>
|
||||
{/* Jeden wiersz (2026-09-07): licznik scen po lewej, po prawej ikony
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ const SORT_OPTIONS: { value: ScenesSort; label: string }[] = [
|
|||
{ value: 'release_date', label: 'release date' },
|
||||
{ value: 'title', label: 'title' },
|
||||
{ value: 'studio', label: 'studio' },
|
||||
{ value: 'random', label: 'random' },
|
||||
{ value: 'duration_desc', label: 'longest' },
|
||||
{ value: 'duration_asc', label: 'shortest' },
|
||||
];
|
||||
|
||||
// Filtr długości, user-report mobilism: "short clips nowadays are mostly ads in
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ export function ScenesScreen({
|
|||
);
|
||||
const [filterOpen, setFilterOpen] = useState(false);
|
||||
const [searchFocused, setSearchFocused] = useState(false);
|
||||
// Seed losowej kolejnosci: staly w obrebie listy (paginacja spójna), nowy przy
|
||||
// pull-to-refresh, zeby odswiezenie dalo nowe rozdanie.
|
||||
const [seed, setSeed] = useState(() => newSeed());
|
||||
const listRef = React.useRef<FlatList<SceneOut>>(null);
|
||||
useScrollTopListener(
|
||||
React.useCallback(() => listRef.current?.scrollToOffset({ offset: 0, animated: true }), []),
|
||||
|
|
@ -94,7 +97,7 @@ export function ScenesScreen({
|
|||
isFetchingNextPage,
|
||||
} = useInfiniteQuery({
|
||||
enabled: filterHydrated,
|
||||
queryKey: ['scenes', debouncedQ, filter, jav, vertical],
|
||||
queryKey: ['scenes', debouncedQ, filter, jav, vertical, filter.sort === 'random' ? seed : null],
|
||||
queryFn: ({ pageParam = 1 }) =>
|
||||
client.listScenes({
|
||||
q: debouncedQ || undefined,
|
||||
|
|
@ -103,6 +106,7 @@ export function ScenesScreen({
|
|||
performer_ids: filter.performerIds.length ? filter.performerIds : undefined,
|
||||
has_playback: filter.hasPlayback || undefined,
|
||||
sort: filter.sort,
|
||||
seed: filter.sort === 'random' ? seed : undefined,
|
||||
include_stubs: filter.includeStubs || undefined,
|
||||
origin: filter.origin.trim() || undefined,
|
||||
jav: jav || undefined,
|
||||
|
|
@ -286,7 +290,10 @@ export function ScenesScreen({
|
|||
renderItem={({ item }) => <SceneTile scene={item} />}
|
||||
ListHeaderComponent={!debouncedQ && activeCount === 0 ? <ContinueWatchingRail /> : null}
|
||||
refreshing={isRefetching}
|
||||
onRefresh={refetch}
|
||||
onRefresh={() => {
|
||||
if (filter.sort === 'random') setSeed(newSeed());
|
||||
else refetch();
|
||||
}}
|
||||
onEndReached={() => {
|
||||
if (hasNextPage && !isFetchingNextPage) fetchNextPage();
|
||||
}}
|
||||
|
|
@ -317,6 +324,10 @@ export function ScenesScreen({
|
|||
);
|
||||
}
|
||||
|
||||
function newSeed(): string {
|
||||
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
||||
}
|
||||
|
||||
function ContinueWatchingRail() {
|
||||
const client = useClient();
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList, 'Scenes'>>();
|
||||
|
|
|
|||
|
|
@ -139,7 +139,14 @@ export interface SourceListOut {
|
|||
total: number;
|
||||
}
|
||||
|
||||
export type ScenesSort = 'created_at' | 'release_date' | 'title' | 'studio';
|
||||
export type ScenesSort =
|
||||
| 'created_at'
|
||||
| 'release_date'
|
||||
| 'title'
|
||||
| 'studio'
|
||||
| 'random'
|
||||
| 'duration_desc'
|
||||
| 'duration_asc';
|
||||
|
||||
export interface ScenesListParams {
|
||||
q?: string;
|
||||
|
|
@ -157,6 +164,8 @@ export interface ScenesListParams {
|
|||
/** Wertykał do pokazania: 'animation' | 'jav'. Brak → główny feed (wertykały odcięte). */
|
||||
vertical?: string;
|
||||
sort?: ScenesSort;
|
||||
/** Dla sort=random: stały w obrębie sesji listy, nowy przy pull-to-refresh. */
|
||||
seed?: string;
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue