import { MaterialCommunityIcons } from '@expo/vector-icons'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import type { RootStackParamList } from './navigation'; /** * Wspólny zestaw kontrolek nagłówka ekranów-list (Scenes, Movies, Sites, JAV, Anime). * * Po co: każdy ekran miał WŁASNĄ kopię stylów `search`/`toolbar`/przycisku filtra, * definiowaną niezależnie, więc rozjechały się na wszystkim naraz (zgłoszenie 2026-08-18, * cztery zrzuty obok siebie): róg pola 12 vs 10, ramka 1,5 vs 1, font 16 vs 14, odstępy * 12 vs 8, dwa promienie pigułek (8 i 12), a etykiety miały `fontWeight:'700'` BEZ * `fontFamily` — RN nie syntezuje pogrubienia dla fontu custom, więc renderowały się * chudym Regularem (stąd wrażenie „innych czcionek"). Jeden moduł = jeden wygląd. * * Zasady: jedna wysokość kontrolek w wierszu (pole = przycisk filtra), jeden promień, * jedna ramka, waga przez `fontFamily` (nigdy przez `fontWeight` na custom foncie), * stan aktywny robi kolor (accentText + delikatny tint), nie kształt. */ import React from 'react'; import { Pressable, StyleProp, StyleSheet, Text, TextInput, TextInputProps, View, ViewStyle, } from 'react-native'; import { fonts, theme } from './theme'; /** Poziomy padding treści list i wysokość kontrolek w wierszu narzędzi. Wspólne, żeby * pola i przyciski na każdej zakładce zaczynały się i kończyły w tej samej linii. */ export const SCREEN_PAD_H = 16; export const SCREEN_PAD_TOP = 14; export const CONTROL_H = 44; const RADIUS = 12; /** Pole wyszukiwania. Ramka focus zarządzana wewnątrz, żeby każdy ekran nie powtarzał * tego samego `searchFocused`. Wypełnia szerokość (flex:1) w wierszu z przyciskiem. */ export function SearchInput(props: TextInputProps) { const [focused, setFocused] = React.useState(false); return ( { setFocused(true); props.onFocus?.(e); }} onBlur={(e) => { setFocused(false); props.onBlur?.(e); }} style={[styles.search, focused && styles.searchFocused, props.style]} /> ); } /** Pigułka-przycisk (Filter, Performers, Tags, ★, Top/Recent). `compact` = niższy wariant * dla wiersza akcji pod polem. `active` przełącza kolor, nie kształt. */ export function PillButton({ label, active, onPress, onLongPress, compact, tone = 'accent', style, }: { label: string; active?: boolean; onPress?: () => void; onLongPress?: () => void; compact?: boolean; /** 'accent' = czerwień (filtr/aktywny), 'secondary' = amber (Performers/Tags). */ tone?: 'accent' | 'secondary'; style?: StyleProp; }) { const textColor = active ? theme.accentText : tone === 'secondary' ? theme.accentSecondary : theme.fg; return ( {label} ); } /** * Kwadratowy przycisk z ikona zamiast pigulki z napisem. `badge` to licznik w rogu * (np. liczba aktywnych filtrow), pokazywany tylko gdy > 0. */ export function IconButton({ icon, active, badge, onPress, accessibilityLabel, }: { icon: React.ComponentProps['name']; active?: boolean; badge?: number; onPress?: () => void; accessibilityLabel: string; }) { return ( {badge && badge > 0 ? ( {badge} ) : null} ); } /** * Wejscie do ustawien. Gorna belka znikla (2026-09-07), wiec kolo zebate siedzi w * wierszu narzedzi kazdej sekcji; darowizna i zmiana backendu sa w ustawieniach. */ export function SettingsButton() { const navigation = useNavigation>(); return ( navigation.navigate('AppLockSettings')} /> ); } /** * Ikona w prawym rogu natywnego naglowka (ulubione, ukryj). Jeden rozmiar i kolor * na wszystkich ekranach zamiast znakow ★ i 🚫 w roznych rozmiarach. */ export function HeaderIconButton({ icon, active, muted, onPress, disabled, accessibilityLabel, }: { icon: React.ComponentProps['name']; active?: boolean; /** Drugorzedna akcja (np. ukryj): szary zamiast koloru tekstu. */ muted?: boolean; onPress?: () => void; disabled?: boolean; accessibilityLabel: string; }) { const color = active ? theme.accentText : muted ? theme.mutedDim : theme.muted; return ( [styles.headerIcon, pressed && { opacity: 0.6 }]} > ); } /** Wiersz narzędzi: pole + przyciski, jednakowy odstęp i margines na każdym ekranie. */ export function Toolbar({ children }: { children: React.ReactNode }) { return {children}; } export interface SegmentTab { key: T; label: string; /** Liczba po etykiecie (np. 200+), pomijana gdy pusta albo 0. */ count?: number | string; /** Nowe pozycje: czerwony licznik w rogu, jak na IconButton. */ badge?: number; } /** * Zakladki ekranu w jednym wierszu o wysokosci pola wyszukiwania (2026-09-15). Wczesniej * ekrany podrzedne mialy pod naglowkiem osobny wiersz zakladek, pod nim wiersz z * licznikiem i ikonami, a ulubione jeszcze etykiete i podpowiedz, czyli trzy albo cztery * linie zanim zaczynala sie lista. Teraz zakladki siedza w Toolbarze obok ikon. */ export function SegmentTabs({ tabs, value, onChange, style, }: { tabs: SegmentTab[]; value: T; onChange: (key: T) => void; style?: StyleProp; }) { return ( {tabs.map((t) => { const on = t.key === value; const showCount = t.count !== undefined && t.count !== 0 && t.count !== ''; return ( onChange(t.key)} accessibilityRole="tab" accessibilityState={{ selected: on }} accessibilityLabel={showCount ? `${t.label}, ${t.count}` : t.label} style={[styles.seg, on && styles.segActive]} > {t.label} {showCount ? ( {t.count} ) : null} {t.badge && t.badge > 0 ? ( +{t.badge} ) : null} ); })} ); } const styles = StyleSheet.create({ toolbar: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 12 }, headerIcon: { paddingHorizontal: 6, paddingVertical: 4 }, iconBtn: { width: CONTROL_H, height: CONTROL_H, borderRadius: RADIUS, backgroundColor: theme.card, borderColor: theme.borderStrong, borderWidth: 1, alignItems: 'center', justifyContent: 'center', }, badge: { position: 'absolute', top: -6, right: -6, minWidth: 18, height: 18, borderRadius: 9, paddingHorizontal: 4, backgroundColor: theme.accent, alignItems: 'center', justifyContent: 'center', }, badgeText: { color: theme.fg, fontSize: 11, fontFamily: fonts.display }, search: { flex: 1, height: CONTROL_H, backgroundColor: theme.card, borderColor: theme.borderStrong, borderWidth: 1, borderRadius: RADIUS, color: theme.fg, paddingHorizontal: 14, fontFamily: fonts.body, fontSize: 15, }, searchFocused: { borderColor: theme.borderFocus }, pill: { backgroundColor: theme.card, borderColor: theme.border, borderWidth: 1, borderRadius: RADIUS, alignItems: 'center', justifyContent: 'center', }, pillTall: { height: CONTROL_H, paddingHorizontal: 16 }, pillCompact: { height: 36, paddingHorizontal: 14 }, pillActive: { borderColor: theme.accentText, backgroundColor: `${theme.accent}1A` }, pillText: { fontFamily: fonts.medium, fontSize: 14 }, segWrap: { flex: 1, height: CONTROL_H, flexDirection: 'row', padding: 3, gap: 3, backgroundColor: theme.card, borderColor: theme.borderStrong, borderWidth: 1, borderRadius: RADIUS, }, seg: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 5, paddingHorizontal: 4, borderRadius: RADIUS - 3, borderWidth: 1, borderColor: 'transparent', }, segActive: { borderColor: theme.accentText, backgroundColor: `${theme.accent}1A` }, segText: { fontFamily: fonts.medium, fontSize: 14, color: theme.muted, flexShrink: 1 }, segTextActive: { color: theme.accentText }, segCount: { fontFamily: fonts.body, fontSize: 12, color: theme.mutedDim }, });