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 {
|
||||
constructor(private baseUrl: string, private apiKey: string) {
|
||||
constructor(readonly baseUrl: string, private apiKey: string) {
|
||||
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@ export type 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',
|
||||
date: 'September 2026',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
* (theme.ts). Pierscien to `accentText` z miekka poswiata (druga, szersza obwodka o
|
||||
* 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
|
||||
* 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. */
|
||||
const RING_R = 20;
|
||||
const RING_TOP = 8;
|
||||
const RING_TOP = 6;
|
||||
const ICON_SIZE = 22;
|
||||
/** O ile poswiata wystaje poza pierscien, z kazdej strony. */
|
||||
const GLOW_PAD = 3;
|
||||
/** 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. */
|
||||
export const BOTTOM_BAR_GAP = 10;
|
||||
export const BOTTOM_BAR_GAP = 0;
|
||||
|
||||
export function BottomTabsBar<T extends string>({ tabs, icons, current, onSelect }: Props<T>) {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
|
@ -164,7 +168,9 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
pill: {
|
||||
height: BOTTOM_BAR_HEIGHT,
|
||||
borderRadius: BOTTOM_BAR_HEIGHT / 2,
|
||||
borderTopLeftRadius: 26,
|
||||
borderTopRightRadius: 26,
|
||||
borderBottomWidth: 0,
|
||||
backgroundColor: theme.card,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.border,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ import {
|
|||
} from '@react-navigation/native';
|
||||
import { BugReportFAB } from './components/BugReportFAB';
|
||||
import { BottomTabsBar } from './components/BottomTabsBar';
|
||||
import { GoonMark } from './components/GoonWordmark';
|
||||
import { GoonClient } from './api';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
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 { AppLockSettingsScreen } from './screens/AppLockSettingsScreen';
|
||||
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
|
||||
* instancję ScenesScreen i zostawiło stary tekst wyszukiwania w nowej sekcji.
|
||||
*/
|
||||
function HomeScreen({
|
||||
navigation,
|
||||
onLogout,
|
||||
}: {
|
||||
navigation: { setParams: (p: { tab?: string }) => void; navigate: (r: 'Donate' | 'AppLockSettings') => void };
|
||||
onLogout: () => void;
|
||||
}) {
|
||||
function HomeScreen({ navigation }: { navigation: { setParams: (p: { tab?: string }) => void } }) {
|
||||
const insets = useSafeAreaInsets();
|
||||
const tabs: TopTab[] = ['Scenes', 'Movies', 'Sites', 'JAV', 'Anime'];
|
||||
const [tab, setTab] = React.useState<TopTab>('Scenes');
|
||||
|
|
@ -148,47 +141,10 @@ function HomeScreen({
|
|||
}, [tab, navigation]);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
{/* Własny nagłówek zamiast natywnego (headerShown:false): natywny należy do
|
||||
ekranu, a ten pasek musi przeżywać przełączanie zakładek. Ikony po prawej są
|
||||
teraz jednakowe na każdej zakładce (wcześniej ♥ i ⚙ miał tylko Scenes). */}
|
||||
<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, paddingTop: insets.top, backgroundColor: theme.bg }}>
|
||||
{/* Gornej belki nie ma (2026-09-07): nazwe sekcji mowi pasek na dole, kolo zebate
|
||||
siedzi w wierszu narzedzi, darowizna i zmiana backendu w ustawieniach. Zostaje
|
||||
tylko odstep na pasek statusu. */}
|
||||
<View style={{ flex: 1 }} key={tab}>
|
||||
{tab === 'Movies' ? (
|
||||
<MoviesScreen />
|
||||
|
|
@ -254,7 +210,7 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
|
|||
}}
|
||||
>
|
||||
<Stack.Screen name="Scenes" options={{ headerShown: false }}>
|
||||
{({ navigation }) => <HomeScreen navigation={navigation} onLogout={onLogout} />}
|
||||
{({ navigation }) => <HomeScreen navigation={navigation} />}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name="SiteScenes"
|
||||
|
|
@ -288,9 +244,9 @@ export function AppNavigator({ onLogout, client, appVersion }: AppNavigatorProps
|
|||
/>
|
||||
<Stack.Screen
|
||||
name="AppLockSettings"
|
||||
options={{ title: 'App lock' }}
|
||||
options={{ title: 'Settings' }}
|
||||
>
|
||||
{() => <AppLockSettingsScreen />}
|
||||
{() => <AppLockSettingsScreen onLogout={onLogout} />}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name="Blacklist"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ const QUALITY_OPTIONS: { label: string; value: DefaultQuality }[] = [
|
|||
|
||||
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 [bioAvailable, setBioAvailable] = useState(false);
|
||||
const [stage, setStage] = useState<Stage>('menu');
|
||||
|
|
@ -65,6 +65,9 @@ export function AppLockSettingsScreen() {
|
|||
const { gridColumns, setGridColumns, defaultQuality, setDefaultQuality, autoRotate, setAutoRotate } = usePreferences();
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
||||
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 [deviceId, setDeviceId] = useState('');
|
||||
const [importCode, setImportCode] = useState('');
|
||||
|
|
@ -223,6 +226,37 @@ export function AppLockSettingsScreen() {
|
|||
|
||||
return (
|
||||
<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}>
|
||||
<Text style={styles.sectionTitle}>App lock</Text>
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { MoviePosterCard } from '../components/MoviePosterCard';
|
|||
import { getLastSeen, markSeen } from '../lib/seenStore';
|
||||
import type { RootStackParamList } from '../navigation';
|
||||
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;
|
||||
// 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}
|
||||
returnKeyType="search"
|
||||
/>
|
||||
<PillButton
|
||||
label={`Filter${!isDefaultFilters(filters) ? ' 1' : ''}`}
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
accessibilityLabel="Filters"
|
||||
active={!isDefaultFilters(filters)}
|
||||
badge={isDefaultFilters(filters) ? 0 : 1}
|
||||
onPress={() => setFiltersOpen(true)}
|
||||
/>
|
||||
<SettingsButton />
|
||||
</Toolbar>
|
||||
<MovieFiltersSheet
|
||||
visible={filtersOpen}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,15 @@ import {
|
|||
import { Image } from 'expo-image';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
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 { SceneGridSkeleton } from '../components/SceneGridSkeleton';
|
||||
import { Thumb } from '../components/Thumb';
|
||||
|
|
@ -172,11 +180,14 @@ export function ScenesScreen({
|
|||
}}
|
||||
/>
|
||||
) : null}
|
||||
<PillButton
|
||||
label={`Filter${activeCount > 0 ? ` ${activeCount}` : ''}`}
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
accessibilityLabel="Filters"
|
||||
active={activeCount > 0}
|
||||
badge={activeCount}
|
||||
onPress={() => setFilterOpen(true)}
|
||||
/>
|
||||
<SettingsButton />
|
||||
</Toolbar>
|
||||
|
||||
{saved.length > 0 ? (
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
import { useClient } from '../ClientContext';
|
||||
import type { RootStackParamList } from '../navigation';
|
||||
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';
|
||||
|
||||
type Order = 'popular' | 'recent';
|
||||
|
|
@ -95,6 +95,7 @@ export function SitesScreen() {
|
|||
<View style={styles.container}>
|
||||
<Toolbar>
|
||||
<SearchInput value={q} onChangeText={setQ} placeholder="search site…" />
|
||||
<SettingsButton />
|
||||
</Toolbar>
|
||||
|
||||
<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).
|
||||
*
|
||||
|
|
@ -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. */
|
||||
export function Toolbar({ children }: { children: React.ReactNode }) {
|
||||
return <View style={styles.toolbar}>{children}</View>;
|
||||
|
|
@ -99,6 +153,29 @@ export function Toolbar({ children }: { children: React.ReactNode }) {
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
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: {
|
||||
flex: 1,
|
||||
height: CONTROL_H,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue