/** * SceneActions — long-press akcje na kafelkach scen (bug-report 5a6844db). * Zastępuje martwy animated-preview gesture. Long-press → menu: * - Ukryj scenę → POST /scenes/{id}/hide (playback dead, znika z list) * - Oznacz duplikat → wejście w tryb wyboru; następny tap na inną scenę scala * (merge długo-naciśniętej W wybraną). Banner na dole prowadzi przez wybór. * * Context bo SceneTile jest współdzielony przez 5 ekranów — jeden provider daje * tryb selekcji działający wszędzie bez per-screen wiringu. */ import { useQueryClient } from '@tanstack/react-query'; import React from 'react'; import { Alert, Pressable, StyleSheet, Text, View } from 'react-native'; import { useClient } from './ClientContext'; import { theme } from './theme'; import type { SceneOut } from './types'; interface SceneActionsCtx { pendingDuplicate: SceneOut | null; isSelecting: boolean; /** Otwórz menu akcji dla sceny (long-press). */ openActions: (scene: SceneOut) => void; /** W trybie selekcji: wybierz tę scenę jako oryginał i scal w nią duplikat. */ pickDuplicateTarget: (target: SceneOut) => void; cancelDuplicate: () => void; } const Ctx = React.createContext(null); export function useSceneActions(): SceneActionsCtx { const c = React.useContext(Ctx); if (!c) throw new Error('useSceneActions used outside SceneActionsProvider'); return c; } const SCENE_LIST_KEYS = ['scenes', 'performer-scenes', 'studio-scenes', 'tag-scenes', 'site-scenes']; export function SceneActionsProvider({ children }: { children: React.ReactNode }) { const client = useClient(); const queryClient = useQueryClient(); const [pendingDuplicate, setPending] = React.useState(null); const invalidate = React.useCallback(() => { for (const k of SCENE_LIST_KEYS) { queryClient.invalidateQueries({ queryKey: [k] }); } }, [queryClient]); const hide = React.useCallback( (scene: SceneOut) => { Alert.alert('Hide scene?', scene.title, [ { text: 'Cancel', style: 'cancel' }, { text: 'Hide', style: 'destructive', onPress: async () => { try { await client.hideScene(scene.id); invalidate(); } catch (e: any) { Alert.alert('Failed to hide', e?.message || 'unknown error'); } }, }, ]); }, [client, invalidate], ); const openActions = React.useCallback( (scene: SceneOut) => { if (pendingDuplicate) return; // w trakcie wyboru duplikatu — ignoruj Alert.alert(scene.title, 'Scene actions', [ { text: 'Hide scene', style: 'destructive', onPress: () => hide(scene) }, { text: 'Mark as duplicate', onPress: () => setPending(scene) }, { text: 'Cancel', style: 'cancel' }, ]); }, [pendingDuplicate, hide], ); const pickDuplicateTarget = React.useCallback( (target: SceneOut) => { const dup = pendingDuplicate; if (!dup || target.id === dup.id) { setPending(null); return; } Alert.alert( 'Merge duplicate?', `"${dup.title}"\n\nmerge into\n\n"${target.title}"`, [ { text: 'Cancel', style: 'cancel', onPress: () => setPending(null) }, { text: 'Merge', onPress: async () => { try { // keep = wybrany oryginał (target), drop = długo-naciśnięty duplikat (dup) await client.mergeDuplicateScene(target.id, dup.id); invalidate(); } catch (e: any) { Alert.alert('Merge failed', e?.message || 'unknown error'); } finally { setPending(null); } }, }, ], ); }, [pendingDuplicate, client, invalidate], ); const cancelDuplicate = React.useCallback(() => setPending(null), []); const value = React.useMemo( () => ({ pendingDuplicate, isSelecting: !!pendingDuplicate, openActions, pickDuplicateTarget, cancelDuplicate, }), [pendingDuplicate, openActions, pickDuplicateTarget, cancelDuplicate], ); return ( {children} {pendingDuplicate ? ( Pick the original — tap the scene to merge "{pendingDuplicate.title}" into Cancel ) : null} ); } const styles = StyleSheet.create({ banner: { position: 'absolute', left: 0, right: 0, bottom: 0, padding: 10, }, bannerInner: { backgroundColor: theme.accent, borderRadius: 12, padding: 12, flexDirection: 'row', alignItems: 'center', gap: 10, }, bannerText: { color: theme.fg, fontSize: 13, fontWeight: '600', flex: 1 }, bannerCancel: { backgroundColor: 'rgba(0,0,0,0.3)', borderRadius: 8, paddingVertical: 6, paddingHorizontal: 12, }, bannerCancelText: { color: theme.fg, fontWeight: '700', fontSize: 13 }, });