Tłumaczenie wszystkich user-facing stringów PL→EN (bug-report 2026-05-31 "dalej wszystko po polsku"). Alerty, przyciski, placeholdery, labelki w 12 ekranach/komponentach: BugReportFAB, AppLock(Screen/Settings/PinEntry), applock biometric prompts, doodstream error msgs, MovieDetail, PlaybackQuality, Player, SceneDetail, ScenesFilter, SiteScenes. Komentarze w kodzie zostają PL. Zmiany były WIP drugiego okna (uncommitted); wjechały do bundla 0.2.1 przy buildzie (były w working tree) — apka zainstalowana już ma EN. Ten commit utrwala je w gicie żeby nie zginęły. Czysto stringi, zero zmian logiki. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
import * as Haptics from 'expo-haptics';
|
|
import { theme } from '../theme';
|
|
|
|
interface Props {
|
|
title: string;
|
|
error?: string | null;
|
|
onSubmit: (pin: string) => Promise<void> | void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
const KEYS: (string | null)[] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', null, '0', '⌫'];
|
|
|
|
export function PinEntry({ title, error, onSubmit, onCancel }: Props) {
|
|
const [pin, setPin] = useState('');
|
|
|
|
function press(k: string) {
|
|
if (k === '⌫') {
|
|
setPin((p) => p.slice(0, -1));
|
|
Haptics.selectionAsync().catch(() => {});
|
|
return;
|
|
}
|
|
if (pin.length >= 8) return;
|
|
setPin((p) => p + k);
|
|
Haptics.selectionAsync().catch(() => {});
|
|
}
|
|
|
|
return (
|
|
<View style={styles.root}>
|
|
<Text style={styles.title}>{title}</Text>
|
|
{error ? <Text style={styles.error}>{error}</Text> : <View style={{ height: 18 }} />}
|
|
<View style={styles.dots}>
|
|
{Array.from({ length: Math.max(4, pin.length) }).map((_, i) => (
|
|
<View
|
|
key={i}
|
|
style={[styles.dot, i < pin.length && styles.dotFilled]}
|
|
/>
|
|
))}
|
|
</View>
|
|
|
|
<View style={styles.pad}>
|
|
{KEYS.map((k, i) =>
|
|
k === null ? (
|
|
<View key={i} style={[styles.key, styles.keyEmpty]} />
|
|
) : (
|
|
<Pressable
|
|
key={i}
|
|
style={({ pressed }) => [styles.key, pressed && styles.keyPressed]}
|
|
onPress={() => press(k)}
|
|
>
|
|
<Text style={k === '⌫' ? styles.keyBack : styles.keyText}>{k}</Text>
|
|
</Pressable>
|
|
),
|
|
)}
|
|
</View>
|
|
|
|
<View style={styles.actions}>
|
|
<Pressable style={styles.ghostBtn} onPress={onCancel}>
|
|
<Text style={styles.ghostText}>Cancel</Text>
|
|
</Pressable>
|
|
<Pressable
|
|
style={[styles.primaryBtn, pin.length < 4 && styles.primaryBtnDisabled]}
|
|
disabled={pin.length < 4}
|
|
onPress={() => onSubmit(pin)}
|
|
>
|
|
<Text style={styles.primaryText}>Next</Text>
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const KEY_SIZE = 72;
|
|
|
|
const styles = StyleSheet.create({
|
|
root: { flex: 1, backgroundColor: theme.bg, alignItems: 'center', paddingTop: 60 },
|
|
title: { color: theme.fg, fontSize: 18, fontWeight: '700' },
|
|
error: { color: theme.bad, fontSize: 13, marginTop: 8 },
|
|
dots: { flexDirection: 'row', gap: 14, marginTop: 14, minHeight: 16 },
|
|
dot: {
|
|
width: 12,
|
|
height: 12,
|
|
borderRadius: 6,
|
|
borderWidth: 1.5,
|
|
borderColor: theme.border,
|
|
},
|
|
dotFilled: { backgroundColor: theme.accent, borderColor: theme.accent },
|
|
pad: {
|
|
marginTop: 24,
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
width: KEY_SIZE * 3 + 36,
|
|
justifyContent: 'space-between',
|
|
rowGap: 12,
|
|
},
|
|
key: {
|
|
width: KEY_SIZE,
|
|
height: KEY_SIZE,
|
|
borderRadius: KEY_SIZE / 2,
|
|
backgroundColor: theme.card,
|
|
borderWidth: 1,
|
|
borderColor: theme.border,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
keyEmpty: { backgroundColor: 'transparent', borderColor: 'transparent' },
|
|
keyPressed: { backgroundColor: theme.bgElevated, borderColor: theme.borderFocus },
|
|
keyText: { color: theme.fg, fontSize: 26, fontWeight: '500' },
|
|
keyBack: { color: theme.muted, fontSize: 22 },
|
|
actions: {
|
|
flexDirection: 'row',
|
|
gap: 12,
|
|
marginTop: 28,
|
|
paddingHorizontal: 24,
|
|
},
|
|
ghostBtn: {
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 22,
|
|
borderRadius: 10,
|
|
borderWidth: 1,
|
|
borderColor: theme.border,
|
|
},
|
|
ghostText: { color: theme.muted, fontSize: 14, fontWeight: '600' },
|
|
primaryBtn: {
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 30,
|
|
borderRadius: 10,
|
|
backgroundColor: theme.accent,
|
|
},
|
|
primaryBtnDisabled: { backgroundColor: theme.border },
|
|
primaryText: { color: '#fff', fontSize: 14, fontWeight: '700' },
|
|
});
|