feat(mobile): naglowki ekranow podrzednych jak sekcje glowne, ikony ulubione/ukryj, pasek dolny wyzszy z pierscieniem centrowanym przez layout
- screenOptions: tlo ekranu, bez cienia, tytul 17 display, tint accentText
- HeaderIconButton (star/star-outline, eye-off-outline) na 5 ekranach zamiast ★/🚫
- BottomTabsBar: 76 dp, pierscien statyczny w iconBox w spoczynku, kapsula tylko w
trakcie przejscia, srodki zaokraglone do piksela
- changelog 2026-09-08b/c, OTA 1cbb9181 i 18ba425a na 1.3
This commit is contained in:
parent
385aeb3ddb
commit
320d928927
10 changed files with 136 additions and 51 deletions
|
|
@ -29,6 +29,20 @@ export type ChangelogEntry = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CHANGELOG: ChangelogEntry[] = [
|
export const CHANGELOG: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
id: '2026-09-08c',
|
||||||
|
date: 'September 2026',
|
||||||
|
items: ['Bottom bar: a little more room under the labels.'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2026-09-08b',
|
||||||
|
date: 'September 2026',
|
||||||
|
items: [
|
||||||
|
'Scene, movie, performer, studio and site pages got the same look as the main sections: no lighter bar on top, same title style, and proper icons for favorite and hide instead of text symbols.',
|
||||||
|
'Bottom bar is a bit taller so the labels no longer sit on the edge, and the ring around the active icon is centred exactly.',
|
||||||
|
'Movie lists load about four times faster and the Sites tab opens instantly.',
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: '2026-09-08a',
|
id: '2026-09-08a',
|
||||||
date: 'September 2026',
|
date: 'September 2026',
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@
|
||||||
* paddingu. Przy otwartej klawiaturze (adjustResize wypychal pasek nad klawiature)
|
* paddingu. Przy otwartej klawiaturze (adjustResize wypychal pasek nad klawiature)
|
||||||
* pasek znika w calosci; stukniecie w aktywna zakladke przewija liste na gore.
|
* pasek znika w calosci; stukniecie w aktywna zakladke przewija liste na gore.
|
||||||
*
|
*
|
||||||
|
* Wyrownanie pierscienia (uwaga Jana 2026-09-08): w spoczynku pierscien NIE jest
|
||||||
|
* pozycjonowany absolutnie po zmierzonych x, tylko siedzi wewnatrz iconBox aktywnej
|
||||||
|
* zakladki i centruje go flexbox, wiec nie ma ulamkow piksela ani przesuniecia o
|
||||||
|
* obramowanie pigulki. Absolutna, animowana kapsula pokazuje sie wylacznie na czas
|
||||||
|
* przejscia miedzy zakladkami.
|
||||||
|
*
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
|
@ -61,7 +67,7 @@ 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 = 6;
|
const RING_TOP = 8;
|
||||||
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;
|
||||||
|
|
@ -89,6 +95,7 @@ export function BottomTabsBar<T extends string>({ tabs, icons, current, onSelect
|
||||||
// Skad i dokad jedzie pierscien. `from` to poprzednia aktywna, do pierwszego
|
// Skad i dokad jedzie pierscien. `from` to poprzednia aktywna, do pierwszego
|
||||||
// przelaczenia from == to i pierscien stoi.
|
// przelaczenia from == to i pierscien stoi.
|
||||||
const [trip, setTrip] = React.useState({ from: activeIndex, to: activeIndex });
|
const [trip, setTrip] = React.useState({ from: activeIndex, to: activeIndex });
|
||||||
|
const [animating, setAnimating] = React.useState(false);
|
||||||
const lead = React.useRef(new Animated.Value(1)).current;
|
const lead = React.useRef(new Animated.Value(1)).current;
|
||||||
const trail = React.useRef(new Animated.Value(1)).current;
|
const trail = React.useRef(new Animated.Value(1)).current;
|
||||||
|
|
||||||
|
|
@ -103,6 +110,7 @@ export function BottomTabsBar<T extends string>({ tabs, icons, current, onSelect
|
||||||
if (trip.from === trip.to) return;
|
if (trip.from === trip.to) return;
|
||||||
lead.setValue(0);
|
lead.setValue(0);
|
||||||
trail.setValue(0);
|
trail.setValue(0);
|
||||||
|
setAnimating(true);
|
||||||
Animated.parallel([
|
Animated.parallel([
|
||||||
Animated.timing(lead, {
|
Animated.timing(lead, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
|
|
@ -117,12 +125,12 @@ export function BottomTabsBar<T extends string>({ tabs, icons, current, onSelect
|
||||||
easing: Easing.out(Easing.cubic),
|
easing: Easing.out(Easing.cubic),
|
||||||
useNativeDriver: false,
|
useNativeDriver: false,
|
||||||
}),
|
}),
|
||||||
]).start();
|
]).start(() => setAnimating(false));
|
||||||
}, [trip, lead, trail]);
|
}, [trip, lead, trail]);
|
||||||
|
|
||||||
const onTabLayout = (i: number) => (e: LayoutChangeEvent) => {
|
const onTabLayout = (i: number) => (e: LayoutChangeEvent) => {
|
||||||
const { x, width } = e.nativeEvent.layout;
|
const { x, width } = e.nativeEvent.layout;
|
||||||
const cx = x + width / 2;
|
const cx = Math.round(x + width / 2);
|
||||||
setCenters((p) => (p[i] === cx ? p : { ...p, [i]: cx }));
|
setCenters((p) => (p[i] === cx ? p : { ...p, [i]: cx }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -157,7 +165,7 @@ export function BottomTabsBar<T extends string>({ tabs, icons, current, onSelect
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View style={styles.pill}>
|
<View style={styles.pill}>
|
||||||
{ring ? (
|
{ring && animating ? (
|
||||||
<>
|
<>
|
||||||
<Animated.View style={[styles.glow, { left: ring.glowLeft, width: ring.glowWidth }]} />
|
<Animated.View style={[styles.glow, { left: ring.glowLeft, width: ring.glowWidth }]} />
|
||||||
<Animated.View style={[styles.ring, { left: ring.left, width: ring.width }]} />
|
<Animated.View style={[styles.ring, { left: ring.left, width: ring.width }]} />
|
||||||
|
|
@ -185,6 +193,12 @@ export function BottomTabsBar<T extends string>({ tabs, icons, current, onSelect
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View style={styles.iconBox}>
|
<View style={styles.iconBox}>
|
||||||
|
{active && !animating ? (
|
||||||
|
<>
|
||||||
|
<View style={styles.glowStatic} />
|
||||||
|
<View style={styles.ringStatic} />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name={icons[t]}
|
name={icons[t]}
|
||||||
size={ICON_SIZE}
|
size={ICON_SIZE}
|
||||||
|
|
@ -236,13 +250,34 @@ const styles = StyleSheet.create({
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
marginTop: 2,
|
marginTop: 4,
|
||||||
color: theme.muted,
|
color: theme.muted,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontFamily: fonts.medium,
|
fontFamily: fonts.medium,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
},
|
},
|
||||||
labelActive: { color: theme.accentText, fontFamily: fonts.display },
|
labelActive: { color: theme.accentText, fontFamily: fonts.display },
|
||||||
|
ringStatic: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: RING_R * 2,
|
||||||
|
height: RING_R * 2,
|
||||||
|
borderRadius: RING_R,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
borderColor: theme.accentText,
|
||||||
|
},
|
||||||
|
glowStatic: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: -GLOW_PAD,
|
||||||
|
top: -GLOW_PAD,
|
||||||
|
width: RING_R * 2 + GLOW_PAD * 2,
|
||||||
|
height: RING_R * 2 + GLOW_PAD * 2,
|
||||||
|
borderRadius: RING_R + GLOW_PAD,
|
||||||
|
borderWidth: 4,
|
||||||
|
borderColor: theme.accentText,
|
||||||
|
opacity: 0.16,
|
||||||
|
},
|
||||||
ring: {
|
ring: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: RING_TOP,
|
top: RING_TOP,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ import { Animated, Easing, NativeScrollEvent, NativeSyntheticEvent } from 'react
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
/** Wysokosc samej pigulki, bez inset systemowego paska. */
|
/** Wysokosc samej pigulki, bez inset systemowego paska. */
|
||||||
export const BOTTOM_BAR_HEIGHT = 62;
|
// 8 (gora) + 40 (ikona) + 4 + ~14 (podpis) = 66, reszta to oddech pod podpisem. Przy 70
|
||||||
|
// podpis konczyl sie 3 dp nad krawedzia (pomiar na emulatorze 2026-09-08).
|
||||||
|
export const BOTTOM_BAR_HEIGHT = 76;
|
||||||
/** 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 = 0;
|
export const BOTTOM_BAR_GAP = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -213,9 +213,13 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
headerStyle: { backgroundColor: theme.card },
|
// Ekrany podrzedne maja wygladac jak sekcje glowne (2026-09-08): tlo ekranu
|
||||||
headerTitleStyle: { color: theme.fg, fontFamily: fonts.display },
|
// zamiast jasniejszej belki, bez cienia, tytul tym samym krojem i rozmiarem
|
||||||
headerTintColor: theme.accent,
|
// co naglowki sekcji, strzalka i ikony po prawej w accentText.
|
||||||
|
headerStyle: { backgroundColor: theme.bg },
|
||||||
|
headerShadowVisible: false,
|
||||||
|
headerTitleStyle: { color: theme.fg, fontFamily: fonts.display, fontSize: 17 },
|
||||||
|
headerTintColor: theme.accentText,
|
||||||
contentStyle: { backgroundColor: theme.bg },
|
contentStyle: { backgroundColor: theme.bg },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,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 { HeaderIconButton } from '../ui';
|
||||||
import type { PlaybackSource, StreamLink } from '../types';
|
import type { PlaybackSource, StreamLink } from '../types';
|
||||||
import { resolveVoePage } from '../lib/voeResolver';
|
import { resolveVoePage } from '../lib/voeResolver';
|
||||||
import { PlaybackQualityModal } from './PlaybackQualityModal';
|
import { PlaybackQualityModal } from './PlaybackQualityModal';
|
||||||
|
|
@ -47,15 +48,13 @@ export function MovieDetailScreen() {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
title: data?.title ?? 'Movie',
|
title: data?.title ?? 'Movie',
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<Pressable
|
<HeaderIconButton
|
||||||
|
icon={isFav ? 'star' : 'star-outline'}
|
||||||
|
active={isFav}
|
||||||
|
accessibilityLabel={isFav ? 'Remove from favorites' : 'Add to favorites'}
|
||||||
onPress={() => favMutation.mutate()}
|
onPress={() => favMutation.mutate()}
|
||||||
hitSlop={12}
|
|
||||||
disabled={favMutation.isPending}
|
disabled={favMutation.isPending}
|
||||||
>
|
/>
|
||||||
<Text style={{ color: isFav ? theme.accent : theme.muted, fontSize: 22 }}>
|
|
||||||
{isFav ? '★' : '☆'}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}, [navigation, data?.title, isFav, favMutation]);
|
}, [navigation, data?.title, isFav, favMutation]);
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { ErrorBoundary } from '../ErrorBoundary';
|
||||||
import type { RootStackParamList } from '../navigation';
|
import type { RootStackParamList } from '../navigation';
|
||||||
import { theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
import type { SceneOut } from '../types';
|
import type { SceneOut } from '../types';
|
||||||
import { IconButton, Toolbar } from '../ui';
|
import { HeaderIconButton, IconButton, Toolbar } from '../ui';
|
||||||
|
|
||||||
type Tab = 'scenes' | 'movies';
|
type Tab = 'scenes' | 'movies';
|
||||||
|
|
||||||
|
|
@ -134,19 +134,15 @@ export function PerformerScenesScreen() {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
title: name,
|
title: name,
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<View style={{ flexDirection: 'row', gap: 16, alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', gap: 4, alignItems: 'center' }}>
|
||||||
<Pressable onPress={onHide} hitSlop={10}>
|
<HeaderIconButton icon="eye-off-outline" muted accessibilityLabel="Hide" onPress={onHide} />
|
||||||
<Text style={{ color: theme.mutedDim, fontSize: 18 }}>🚫</Text>
|
<HeaderIconButton
|
||||||
</Pressable>
|
icon={isFavorite ? 'star' : 'star-outline'}
|
||||||
<Pressable
|
active={isFavorite}
|
||||||
|
accessibilityLabel={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
|
||||||
onPress={() => (isFavorite ? removeMutation.mutate() : addMutation.mutate())}
|
onPress={() => (isFavorite ? removeMutation.mutate() : addMutation.mutate())}
|
||||||
hitSlop={12}
|
|
||||||
disabled={addMutation.isPending || removeMutation.isPending}
|
disabled={addMutation.isPending || removeMutation.isPending}
|
||||||
>
|
/>
|
||||||
<Text style={{ color: isFavorite ? theme.accent : theme.muted, fontSize: 22 }}>
|
|
||||||
{isFavorite ? '★' : '☆'}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
</View>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { resolveEpornerPage } from '../lib/epornerResolver';
|
||||||
import { resolveFpoxxxPage } from '../lib/fpoxxxResolver';
|
import { resolveFpoxxxPage } from '../lib/fpoxxxResolver';
|
||||||
import type { RootStackParamList } from '../navigation';
|
import type { RootStackParamList } from '../navigation';
|
||||||
import { fonts, theme } from '../theme';
|
import { fonts, theme } from '../theme';
|
||||||
|
import { HeaderIconButton } from '../ui';
|
||||||
import type { PlaybackSource, SceneOut, StreamLink } from '../types';
|
import type { PlaybackSource, SceneOut, StreamLink } from '../types';
|
||||||
import { PlaybackQualityModal, pickByDefaultQuality } from './PlaybackQualityModal';
|
import { PlaybackQualityModal, pickByDefaultQuality } from './PlaybackQualityModal';
|
||||||
import { usePreferences } from '../PreferencesContext';
|
import { usePreferences } from '../PreferencesContext';
|
||||||
|
|
@ -230,11 +231,13 @@ export function SceneDetailScreen() {
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
nav.setOptions({
|
nav.setOptions({
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<Pressable onPress={() => favMutation.mutate()} hitSlop={12} disabled={favMutation.isPending}>
|
<HeaderIconButton
|
||||||
<Text style={{ color: isFav ? theme.accent : theme.muted, fontSize: 22 }}>
|
icon={isFav ? 'star' : 'star-outline'}
|
||||||
{isFav ? '★' : '☆'}
|
active={isFav}
|
||||||
</Text>
|
accessibilityLabel={isFav ? 'Remove from favorites' : 'Add to favorites'}
|
||||||
</Pressable>
|
onPress={() => favMutation.mutate()}
|
||||||
|
disabled={favMutation.isPending}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}, [nav, isFav, favMutation]);
|
}, [nav, isFav, favMutation]);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { useClient } from '../ClientContext';
|
||||||
import { MoviePosterCard } from '../components/MoviePosterCard';
|
import { MoviePosterCard } from '../components/MoviePosterCard';
|
||||||
import type { RootStackParamList } from '../navigation';
|
import type { RootStackParamList } from '../navigation';
|
||||||
import { theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
|
import { HeaderIconButton } from '../ui';
|
||||||
|
|
||||||
const PER_PAGE = 30;
|
const PER_PAGE = 30;
|
||||||
// 3 kolumny, parytet z MoviesScreen (plakaty 2:3, 50% więcej filmów na ekran).
|
// 3 kolumny, parytet z MoviesScreen (plakaty 2:3, 50% więcej filmów na ekran).
|
||||||
|
|
@ -44,15 +45,13 @@ export function StudioMoviesScreen() {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
title: name,
|
title: name,
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<Pressable
|
<HeaderIconButton
|
||||||
|
icon={isFavorite ? 'star' : 'star-outline'}
|
||||||
|
active={isFavorite}
|
||||||
|
accessibilityLabel={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
|
||||||
onPress={() => (isFavorite ? removeMutation.mutate() : addMutation.mutate())}
|
onPress={() => (isFavorite ? removeMutation.mutate() : addMutation.mutate())}
|
||||||
hitSlop={12}
|
|
||||||
disabled={addMutation.isPending || removeMutation.isPending}
|
disabled={addMutation.isPending || removeMutation.isPending}
|
||||||
>
|
/>
|
||||||
<Text style={{ color: isFavorite ? theme.accent : theme.muted, fontSize: 22 }}>
|
|
||||||
{isFavorite ? '★' : '☆'}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import { useClient } from '../ClientContext';
|
||||||
import { usePreferences } from '../PreferencesContext';
|
import { usePreferences } from '../PreferencesContext';
|
||||||
import type { RootStackParamList } from '../navigation';
|
import type { RootStackParamList } from '../navigation';
|
||||||
import { theme } from '../theme';
|
import { theme } from '../theme';
|
||||||
|
import { HeaderIconButton } from '../ui';
|
||||||
import type { SceneOut } from '../types';
|
import type { SceneOut } from '../types';
|
||||||
|
|
||||||
type Tab = 'scenes' | 'movies';
|
type Tab = 'scenes' | 'movies';
|
||||||
|
|
@ -100,19 +101,15 @@ export function StudioScenesScreen() {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
title: name,
|
title: name,
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<View style={{ flexDirection: 'row', gap: 16, alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', gap: 4, alignItems: 'center' }}>
|
||||||
<Pressable onPress={onHide} hitSlop={10}>
|
<HeaderIconButton icon="eye-off-outline" muted accessibilityLabel="Hide" onPress={onHide} />
|
||||||
<Text style={{ color: theme.mutedDim, fontSize: 18 }}>🚫</Text>
|
<HeaderIconButton
|
||||||
</Pressable>
|
icon={isFavorite ? 'star' : 'star-outline'}
|
||||||
<Pressable
|
active={isFavorite}
|
||||||
|
accessibilityLabel={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
|
||||||
onPress={() => (isFavorite ? removeMutation.mutate() : addMutation.mutate())}
|
onPress={() => (isFavorite ? removeMutation.mutate() : addMutation.mutate())}
|
||||||
hitSlop={12}
|
|
||||||
disabled={addMutation.isPending || removeMutation.isPending}
|
disabled={addMutation.isPending || removeMutation.isPending}
|
||||||
>
|
/>
|
||||||
<Text style={{ color: isFavorite ? theme.accent : theme.muted, fontSize: 22 }}>
|
|
||||||
{isFavorite ? '★' : '☆'}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
</View>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,41 @@ export function SettingsButton() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<typeof MaterialCommunityIcons>['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 (
|
||||||
|
<Pressable
|
||||||
|
onPress={onPress}
|
||||||
|
disabled={disabled}
|
||||||
|
hitSlop={12}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
style={({ pressed }) => [styles.headerIcon, pressed && { opacity: 0.6 }]}
|
||||||
|
>
|
||||||
|
<MaterialCommunityIcons name={icon} size={24} color={color} />
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** 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>;
|
||||||
|
|
@ -153,6 +188,7 @@ 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 },
|
||||||
|
headerIcon: { paddingHorizontal: 6, paddingVertical: 4 },
|
||||||
iconBtn: {
|
iconBtn: {
|
||||||
width: CONTROL_H,
|
width: CONTROL_H,
|
||||||
height: CONTROL_H,
|
height: CONTROL_H,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue