feat(mobile): bez gornej belki, ikona filtra, darowizna i backend w ustawieniach
- pasek dolny nizszy (62) i przyciety na krawedzi ekranu, zaokraglone tylko gorne rogi - IconButton (filter-variant z licznikiem) zamiast pigulki Filter, SettingsButton w wierszu narzedzi Scenes/Movies/Sites - ustawienia: sekcje Support the project i Backend (host + wylogowanie), tytul Settings - changelog 2026-09-07c, OTA 5c8895d6 na 1.3
This commit is contained in:
parent
dce738b8ab
commit
90724af8ef
9 changed files with 162 additions and 66 deletions
|
|
@ -41,7 +41,7 @@ export class ApiError extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GoonClient {
|
export class GoonClient {
|
||||||
constructor(private baseUrl: string, private apiKey: string) {
|
constructor(readonly baseUrl: string, private apiKey: string) {
|
||||||
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,14 @@ export type ChangelogEntry = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: ChangelogEntry[] = [
|
export const CHANGELOG: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
id: '2026-09-07c',
|
||||||
|
date: 'September 2026',
|
||||||
|
items: [
|
||||||
|
'The top bar is gone: the bottom bar names the section, the gear next to the search box opens Settings, and Donate plus switching backend now live inside Settings.',
|
||||||
|
'Filter is an icon with a counter of active filters. The bottom bar is lower and grows out of the screen edge.',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: '2026-09-07b',
|
id: '2026-09-07b',
|
||||||
date: 'September 2026',
|
date: 'September 2026',
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@
|
||||||
* (theme.ts). Pierscien to `accentText` z miekka poswiata (druga, szersza obwodka o
|
* (theme.ts). Pierscien to `accentText` z miekka poswiata (druga, szersza obwodka o
|
||||||
* niskiej przezroczystosci), jeden kolor na wszystkich zakladkach.
|
* niskiej przezroczystosci), jeden kolor na wszystkich zakladkach.
|
||||||
*
|
*
|
||||||
|
* Ksztalt (uwaga Jana 2026-09-07): pigulka nie jest zamknieta polokregiem od dolu,
|
||||||
|
* tylko przycieta na krawedzi ekranu, jakby z niej wyrastala. Zaokraglone sa tylko
|
||||||
|
* gorne rogi, dolne siedza na insecie systemowego paska.
|
||||||
|
*
|
||||||
* Pozycje ikon mierzymy `onLayout` (flex:1 daje rowne komorki, ale szerokosc paska
|
* Pozycje ikon mierzymy `onLayout` (flex:1 daje rowne komorki, ale szerokosc paska
|
||||||
* zalezy od ekranu). Do pierwszego pomiaru pierscienia nie rysujemy.
|
* zalezy od ekranu). Do pierwszego pomiaru pierscienia nie rysujemy.
|
||||||
*/
|
*/
|
||||||
|
|
@ -35,14 +39,14 @@ interface Props<T extends string> {
|
||||||
|
|
||||||
/** Promien pierscienia: kolo 40 px wokol ikony 22 px. */
|
/** Promien pierscienia: kolo 40 px wokol ikony 22 px. */
|
||||||
const RING_R = 20;
|
const RING_R = 20;
|
||||||
const RING_TOP = 8;
|
const RING_TOP = 6;
|
||||||
const ICON_SIZE = 22;
|
const ICON_SIZE = 22;
|
||||||
/** O ile poswiata wystaje poza pierscien, z kazdej strony. */
|
/** O ile poswiata wystaje poza pierscien, z kazdej strony. */
|
||||||
const GLOW_PAD = 3;
|
const GLOW_PAD = 3;
|
||||||
/** Wysokosc samej pigulki, bez marginesu i bez inset systemowego paska. */
|
/** Wysokosc samej pigulki, bez marginesu i bez inset systemowego paska. */
|
||||||
export const BOTTOM_BAR_HEIGHT = 74;
|
export const BOTTOM_BAR_HEIGHT = 62;
|
||||||
/** Odstep pigulki od dolu (nad inset). Eksport, zeby FAB zglaszania mogl wisiec wyzej. */
|
/** Odstep pigulki od dolu (nad inset). Eksport, zeby FAB zglaszania mogl wisiec wyzej. */
|
||||||
export const BOTTOM_BAR_GAP = 10;
|
export const BOTTOM_BAR_GAP = 0;
|
||||||
|
|
||||||
export function BottomTabsBar<T extends string>({ tabs, icons, current, onSelect }: Props<T>) {
|
export function BottomTabsBar<T extends string>({ tabs, icons, current, onSelect }: Props<T>) {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
@ -164,7 +168,9 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
pill: {
|
pill: {
|
||||||
height: BOTTOM_BAR_HEIGHT,
|
height: BOTTOM_BAR_HEIGHT,
|
||||||
borderRadius: BOTTOM_BAR_HEIGHT / 2,
|
borderTopLeftRadius: 26,
|
||||||
|
borderTopRightRadius: 26,
|
||||||
|
borderBottomWidth: 0,
|
||||||
backgroundColor: theme.card,
|
backgroundColor: theme.card,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: theme.border,
|
borderColor: theme.border,
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,10 @@ import {
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { BugReportFAB } from './components/BugReportFAB';
|
import { BugReportFAB } from './components/BugReportFAB';
|
||||||
import { BottomTabsBar } from './components/BottomTabsBar';
|
import { BottomTabsBar } from './components/BottomTabsBar';
|
||||||
import { GoonMark } from './components/GoonWordmark';
|
|
||||||
import { GoonClient } from './api';
|
import { GoonClient } from './api';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Pressable, Text, View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { AppLockSettingsScreen } from './screens/AppLockSettingsScreen';
|
import { AppLockSettingsScreen } from './screens/AppLockSettingsScreen';
|
||||||
import { BlacklistScreen } from './screens/BlacklistScreen';
|
import { BlacklistScreen } from './screens/BlacklistScreen';
|
||||||
|
|
@ -131,13 +130,7 @@ type TopTab = 'Scenes' | 'Movies' | 'Sites' | 'JAV' | 'Anime';
|
||||||
* `replace` (reset wyszukiwania/scrolla). Bez key przełączenie Scenes→JAV reużyłoby
|
* `replace` (reset wyszukiwania/scrolla). Bez key przełączenie Scenes→JAV reużyłoby
|
||||||
* instancję ScenesScreen i zostawiło stary tekst wyszukiwania w nowej sekcji.
|
* instancję ScenesScreen i zostawiło stary tekst wyszukiwania w nowej sekcji.
|
||||||
*/
|
*/
|
||||||
function HomeScreen({
|
function HomeScreen({ navigation }: { navigation: { setParams: (p: { tab?: string }) => void } }) {
|
||||||
navigation,
|
|
||||||
onLogout,
|
|
||||||
}: {
|
|
||||||
navigation: { setParams: (p: { tab?: string }) => void; navigate: (r: 'Donate' | 'AppLockSettings') => void };
|
|
||||||
onLogout: () => void;
|
|
||||||
}) {
|
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const tabs: TopTab[] = ['Scenes', 'Movies', 'Sites', 'JAV', 'Anime'];
|
const tabs: TopTab[] = ['Scenes', 'Movies', 'Sites', 'JAV', 'Anime'];
|
||||||
const [tab, setTab] = React.useState<TopTab>('Scenes');
|
const [tab, setTab] = React.useState<TopTab>('Scenes');
|
||||||
|
|
@ -148,47 +141,10 @@ function HomeScreen({
|
||||||
}, [tab, navigation]);
|
}, [tab, navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1, paddingTop: insets.top, backgroundColor: theme.bg }}>
|
||||||
{/* Własny nagłówek zamiast natywnego (headerShown:false): natywny należy do
|
{/* Gornej belki nie ma (2026-09-07): nazwe sekcji mowi pasek na dole, kolo zebate
|
||||||
ekranu, a ten pasek musi przeżywać przełączanie zakładek. Ikony po prawej są
|
siedzi w wierszu narzedzi, darowizna i zmiana backendu w ustawieniach. Zostaje
|
||||||
teraz jednakowe na każdej zakładce (wcześniej ♥ i ⚙ miał tylko Scenes). */}
|
tylko odstep na pasek statusu. */}
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: theme.card,
|
|
||||||
paddingTop: insets.top,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
|
||||||
paddingLeft: 10,
|
|
||||||
paddingRight: 14,
|
|
||||||
paddingBottom: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GoonMark size={28} />
|
|
||||||
<View style={{ width: 1, height: 16, backgroundColor: theme.border }} />
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
color: theme.fg,
|
|
||||||
fontFamily: fonts.display,
|
|
||||||
fontSize: 17,
|
|
||||||
letterSpacing: 0.2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tab}
|
|
||||||
</Text>
|
|
||||||
<View style={{ flexDirection: 'row', gap: 14, alignItems: 'center', marginLeft: 6 }}>
|
|
||||||
<Pressable onPress={() => navigation.navigate('Donate')} hitSlop={12}>
|
|
||||||
<Text style={{ color: theme.accentText, fontSize: 18 }}>♥</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable onPress={() => navigation.navigate('AppLockSettings')} hitSlop={12}>
|
|
||||||
<Text style={{ color: theme.muted, fontSize: 18 }}>⚙</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable onPress={onLogout} hitSlop={12}>
|
|
||||||
<Text style={{ fontSize: 17 }}>🚪</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={{ flex: 1 }} key={tab}>
|
<View style={{ flex: 1 }} key={tab}>
|
||||||
{tab === 'Movies' ? (
|
{tab === 'Movies' ? (
|
||||||
<MoviesScreen />
|
<MoviesScreen />
|
||||||
|
|
@ -254,7 +210,7 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack.Screen name="Scenes" options={{ headerShown: false }}>
|
<Stack.Screen name="Scenes" options={{ headerShown: false }}>
|
||||||
{({ navigation }) => <HomeScreen navigation={navigation} onLogout={onLogout} />}
|
{({ navigation }) => <HomeScreen navigation={navigation} />}
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="SiteScenes"
|
name="SiteScenes"
|
||||||
|
|
@ -288,9 +244,9 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="AppLockSettings"
|
name="AppLockSettings"
|
||||||
options={{ title: 'App lock' }}
|
options={{ title: 'Settings' }}
|
||||||
>
|
>
|
||||||
{() => <AppLockSettingsScreen />}
|
{() => <AppLockSettingsScreen onLogout={onLogout} />}
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="Blacklist"
|
name="Blacklist"
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ const QUALITY_OPTIONS: { label: string; value: DefaultQuality }[] = [
|
||||||
|
|
||||||
type Stage = 'menu' | 'enter-current' | 'set-new' | 'confirm-new' | 'disable-confirm';
|
type Stage = 'menu' | 'enter-current' | 'set-new' | 'confirm-new' | 'disable-confirm';
|
||||||
|
|
||||||
export function AppLockSettingsScreen() {
|
export function AppLockSettingsScreen({ onLogout }: { onLogout?: () => void }) {
|
||||||
const [settings, setSettings] = useState<AppLockSettings | null>(null);
|
const [settings, setSettings] = useState<AppLockSettings | null>(null);
|
||||||
const [bioAvailable, setBioAvailable] = useState(false);
|
const [bioAvailable, setBioAvailable] = useState(false);
|
||||||
const [stage, setStage] = useState<Stage>('menu');
|
const [stage, setStage] = useState<Stage>('menu');
|
||||||
|
|
@ -65,6 +65,9 @@ export function AppLockSettingsScreen() {
|
||||||
const { gridColumns, setGridColumns, defaultQuality, setDefaultQuality, autoRotate, setAutoRotate } = usePreferences();
|
const { gridColumns, setGridColumns, defaultQuality, setDefaultQuality, autoRotate, setAutoRotate } = usePreferences();
|
||||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
|
// Sam host, bez schematu: user ma zobaczyc, czy siedzi na instancji publicznej,
|
||||||
|
// czy na wlasnym serwerze.
|
||||||
|
const backendHost = client ? client.baseUrl.replace(/^https?:\/\//, '') : null;
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [deviceId, setDeviceId] = useState('');
|
const [deviceId, setDeviceId] = useState('');
|
||||||
const [importCode, setImportCode] = useState('');
|
const [importCode, setImportCode] = useState('');
|
||||||
|
|
@ -223,6 +226,37 @@ export function AppLockSettingsScreen() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView style={styles.root} contentContainerStyle={{ paddingBottom: 40 }}>
|
<ScrollView style={styles.root} contentContainerStyle={{ paddingBottom: 40 }}>
|
||||||
|
{/* Darowizna i backend byly w gornej belce; belka znikla 2026-09-07. */}
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Support the project</Text>
|
||||||
|
<Text style={styles.hint}>
|
||||||
|
Goon is free, open-source and ad-free. Donations cover the server and the API keys.
|
||||||
|
</Text>
|
||||||
|
<Pressable style={styles.actionBtn} onPress={() => navigation.navigate('Donate')}>
|
||||||
|
<Text style={styles.actionText}>♥ Donate</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.sectionTitle}>Backend</Text>
|
||||||
|
<Text style={styles.hint}>
|
||||||
|
{backendHost ? `Connected to ${backendHost}` : 'Not connected'}
|
||||||
|
</Text>
|
||||||
|
{onLogout ? (
|
||||||
|
<Pressable
|
||||||
|
style={styles.actionBtn}
|
||||||
|
onPress={() =>
|
||||||
|
Alert.alert('Switch backend', 'Sign out and pick the public instance or your own server?', [
|
||||||
|
{ text: 'Cancel', style: 'cancel' },
|
||||||
|
{ text: 'Sign out', style: 'destructive', onPress: onLogout },
|
||||||
|
])
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Text style={styles.actionText}>Switch backend / sign out</Text>
|
||||||
|
</Pressable>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>App lock</Text>
|
<Text style={styles.sectionTitle}>App lock</Text>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import { MoviePosterCard } from '../components/MoviePosterCard';
|
||||||
import { getLastSeen, markSeen } from '../lib/seenStore';
|
import { getLastSeen, markSeen } from '../lib/seenStore';
|
||||||
import type { RootStackParamList } from '../navigation';
|
import type { RootStackParamList } from '../navigation';
|
||||||
import { theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
import { PillButton, SCREEN_PAD_H, SCREEN_PAD_TOP, SearchInput, Toolbar } from '../ui';
|
import { IconButton, SCREEN_PAD_H, SCREEN_PAD_TOP, SearchInput, SettingsButton, Toolbar } from '../ui';
|
||||||
|
|
||||||
const PER_PAGE = 30;
|
const PER_PAGE = 30;
|
||||||
// 3 kolumny, plakaty 2:3 mieszczą się dobrze i widać 50% więcej filmów na ekran
|
// 3 kolumny, plakaty 2:3 mieszczą się dobrze i widać 50% więcej filmów na ekran
|
||||||
|
|
@ -122,11 +122,14 @@ export function MoviesScreen() {
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
returnKeyType="search"
|
returnKeyType="search"
|
||||||
/>
|
/>
|
||||||
<PillButton
|
<IconButton
|
||||||
label={`Filter${!isDefaultFilters(filters) ? ' 1' : ''}`}
|
icon="filter-variant"
|
||||||
|
accessibilityLabel="Filters"
|
||||||
active={!isDefaultFilters(filters)}
|
active={!isDefaultFilters(filters)}
|
||||||
|
badge={isDefaultFilters(filters) ? 0 : 1}
|
||||||
onPress={() => setFiltersOpen(true)}
|
onPress={() => setFiltersOpen(true)}
|
||||||
/>
|
/>
|
||||||
|
<SettingsButton />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<MovieFiltersSheet
|
<MovieFiltersSheet
|
||||||
visible={filtersOpen}
|
visible={filtersOpen}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,15 @@ import {
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
import { MigrationBanner } from '../components/MigrationBanner';
|
import { MigrationBanner } from '../components/MigrationBanner';
|
||||||
import { PillButton, SCREEN_PAD_H, SCREEN_PAD_TOP, SearchInput, Toolbar } from '../ui';
|
import {
|
||||||
|
IconButton,
|
||||||
|
PillButton,
|
||||||
|
SCREEN_PAD_H,
|
||||||
|
SCREEN_PAD_TOP,
|
||||||
|
SearchInput,
|
||||||
|
SettingsButton,
|
||||||
|
Toolbar,
|
||||||
|
} from '../ui';
|
||||||
import { SceneTile, sceneGridProps } from '../components/SceneTile';
|
import { SceneTile, sceneGridProps } from '../components/SceneTile';
|
||||||
import { SceneGridSkeleton } from '../components/SceneGridSkeleton';
|
import { SceneGridSkeleton } from '../components/SceneGridSkeleton';
|
||||||
import { Thumb } from '../components/Thumb';
|
import { Thumb } from '../components/Thumb';
|
||||||
|
|
@ -172,11 +180,14 @@ export function ScenesScreen({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<PillButton
|
<IconButton
|
||||||
label={`Filter${activeCount > 0 ? ` ${activeCount}` : ''}`}
|
icon="filter-variant"
|
||||||
|
accessibilityLabel="Filters"
|
||||||
active={activeCount > 0}
|
active={activeCount > 0}
|
||||||
|
badge={activeCount}
|
||||||
onPress={() => setFilterOpen(true)}
|
onPress={() => setFilterOpen(true)}
|
||||||
/>
|
/>
|
||||||
|
<SettingsButton />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
{saved.length > 0 ? (
|
{saved.length > 0 ? (
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import {
|
||||||
import { useClient } from '../ClientContext';
|
import { useClient } from '../ClientContext';
|
||||||
import type { RootStackParamList } from '../navigation';
|
import type { RootStackParamList } from '../navigation';
|
||||||
import { theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
import { PillButton, SCREEN_PAD_H, SCREEN_PAD_TOP, SearchInput, Toolbar } from '../ui';
|
import { PillButton, SCREEN_PAD_H, SCREEN_PAD_TOP, SearchInput, SettingsButton, Toolbar } from '../ui';
|
||||||
import type { SourceOut, SourceRating } from '../types';
|
import type { SourceOut, SourceRating } from '../types';
|
||||||
|
|
||||||
type Order = 'popular' | 'recent';
|
type Order = 'popular' | 'recent';
|
||||||
|
|
@ -95,6 +95,7 @@ export function SitesScreen() {
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<SearchInput value={q} onChangeText={setQ} placeholder="search site…" />
|
<SearchInput value={q} onChangeText={setQ} placeholder="search site…" />
|
||||||
|
<SettingsButton />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
<View style={styles.segment}>
|
<View style={styles.segment}>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
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).
|
* Wspólny zestaw kontrolek nagłówka ekranów-list (Scenes, Movies, Sites, JAV, Anime).
|
||||||
*
|
*
|
||||||
|
|
@ -92,6 +96,56 @@ export function PillButton({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<typeof MaterialCommunityIcons>['name'];
|
||||||
|
active?: boolean;
|
||||||
|
badge?: number;
|
||||||
|
onPress?: () => void;
|
||||||
|
accessibilityLabel: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
onPress={onPress}
|
||||||
|
hitSlop={6}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
style={[styles.iconBtn, active && styles.pillActive]}
|
||||||
|
>
|
||||||
|
<MaterialCommunityIcons name={icon} size={22} color={active ? theme.accentText : theme.fg} />
|
||||||
|
{badge && badge > 0 ? (
|
||||||
|
<View style={styles.badge}>
|
||||||
|
<Text style={styles.badgeText}>{badge}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<NativeStackNavigationProp<RootStackParamList>>();
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
icon="cog-outline"
|
||||||
|
accessibilityLabel="Settings"
|
||||||
|
onPress={() => navigation.navigate('AppLockSettings')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Wiersz narzędzi: pole + przyciski, jednakowy odstęp i margines na każdym ekranie. */
|
/** Wiersz narzędzi: pole + przyciski, jednakowy odstęp i margines na każdym ekranie. */
|
||||||
export function Toolbar({ children }: { children: React.ReactNode }) {
|
export function Toolbar({ children }: { children: React.ReactNode }) {
|
||||||
return <View style={styles.toolbar}>{children}</View>;
|
return <View style={styles.toolbar}>{children}</View>;
|
||||||
|
|
@ -99,6 +153,29 @@ export function Toolbar({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
toolbar: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 12 },
|
toolbar: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 12 },
|
||||||
|
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: {
|
search: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
height: CONTROL_H,
|
height: CONTROL_H,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue