feat(mobile): pasek chowa sie przy przewijaniu w dol (hystereza po drodze), FAB jedzie z nim, podpowiedz Sites do pierwszego przypiecia
- useAutoHideOnScroll: licznik drogi w jednym kierunku (48 px w dol chowa, 24 w gore pokazuje), strefy zawsze-widoczny przy gorze i dole listy, overscroll ignorowany - HomeScreen useFocusEffect pokazuje pasek po powrocie z ekranu szczegolow - changelog 2026-09-07e/f, OTA 40931479 i 8604dadb na 1.3
This commit is contained in:
parent
d9fbc08218
commit
8c75b12066
6 changed files with 60 additions and 14 deletions
|
|
@ -29,6 +29,21 @@ export type ChangelogEntry = {
|
|||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
id: '2026-09-07f',
|
||||
date: 'September 2026',
|
||||
items: [
|
||||
'The bottom bar is back on screen whenever you return from a scene, movie or performer, even if it was tucked away before you left.',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2026-09-07e',
|
||||
date: 'September 2026',
|
||||
items: [
|
||||
'The bottom bar slides away while you scroll down and comes back as soon as you scroll up, so the grid gets the whole screen.',
|
||||
'Sites: the pinning hint disappears once you have pinned something.',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2026-09-07d',
|
||||
date: 'September 2026',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import React, { useCallback, useState } from 'react';
|
|||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Animated,
|
||||
Image,
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
|
|
@ -35,7 +36,7 @@ import { captureScreen } from 'react-native-view-shot';
|
|||
|
||||
import { GoonClient } from '../api';
|
||||
import { theme } from '../theme';
|
||||
import { BOTTOM_BAR_GAP, BOTTOM_BAR_HEIGHT } from '../lib/bottomBar';
|
||||
import { BOTTOM_BAR_GAP, BOTTOM_BAR_HEIGHT, barHidden } from '../lib/bottomBar';
|
||||
import { formatLastPlayback } from '../lib/lastPlayback';
|
||||
import { getSxyprnDiag } from '../lib/sxyprnResolver';
|
||||
|
||||
|
|
@ -123,6 +124,9 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
const insets = useSafeAreaInsets();
|
||||
const liftAboveTabs = currentRoute === null || currentRoute === 'Scenes';
|
||||
const fabBottom = 24 + (liftAboveTabs ? insets.bottom + BOTTOM_BAR_GAP + BOTTOM_BAR_HEIGHT : 0);
|
||||
// Gdy pasek chowa sie na scroll, FAB zjezdza o tyle samo (ta sama wartosc Animated).
|
||||
const fabShift = liftAboveTabs ? insets.bottom + BOTTOM_BAR_GAP + BOTTOM_BAR_HEIGHT + 6 : 0;
|
||||
const fabTranslate = barHidden.interpolate({ inputRange: [0, 1], outputRange: [0, fabShift] });
|
||||
|
||||
const onPress = useCallback(async () => {
|
||||
let captured: string | null = null;
|
||||
|
|
@ -244,7 +248,10 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
return (
|
||||
<>
|
||||
{!hidden ? (
|
||||
<TouchableOpacity style={[styles.fab, { bottom: fabBottom }]} onPress={onPress} activeOpacity={0.7}>
|
||||
<Animated.View
|
||||
style={[styles.fabWrap, { bottom: fabBottom, transform: [{ translateY: fabTranslate }] }]}
|
||||
>
|
||||
<TouchableOpacity style={styles.fab} onPress={onPress} activeOpacity={0.7}>
|
||||
<Text style={styles.fabIcon}>?</Text>
|
||||
{unseen > 0 ? (
|
||||
<View style={styles.fabDot}>
|
||||
|
|
@ -252,6 +259,7 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
</View>
|
||||
) : null}
|
||||
</TouchableOpacity>
|
||||
</Animated.View>
|
||||
) : null}
|
||||
|
||||
<Modal
|
||||
|
|
@ -381,10 +389,12 @@ export function BugReportFAB({ client, appVersion, navRef }: Props) {
|
|||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fab: {
|
||||
fabWrap: {
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
bottom: 24,
|
||||
},
|
||||
fab: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import {
|
||||
DefaultTheme,
|
||||
NavigationContainer,
|
||||
useFocusEffect,
|
||||
useNavigationContainerRef,
|
||||
} from '@react-navigation/native';
|
||||
import { setBarHidden } from './lib/bottomBar';
|
||||
import { BugReportFAB } from './components/BugReportFAB';
|
||||
import { BottomTabsBar } from './components/BottomTabsBar';
|
||||
import { GoonClient } from './api';
|
||||
|
|
@ -140,6 +142,14 @@ function HomeScreen({ navigation }: { navigation: { setParams: (p: { tab?: strin
|
|||
navigation.setParams({ tab });
|
||||
}, [tab, navigation]);
|
||||
|
||||
// Powrot z ekranu sceny/filmu/performera: pasek ma byc widoczny, nawet jesli lista
|
||||
// zostala przewinieta i pasek schowal sie przed wyjsciem (test 2026-09-07).
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
setBarHidden(false);
|
||||
}, []),
|
||||
);
|
||||
|
||||
return (
|
||||
<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
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
MovieFilters,
|
||||
MovieFiltersSheet,
|
||||
} from '../components/MovieFiltersSheet';
|
||||
import { useBottomBarInset, useScrollTopListener } from '../lib/bottomBar';
|
||||
import { useAutoHideOnScroll, useBottomBarInset, useScrollTopListener } from '../lib/bottomBar';
|
||||
import { usePersistedState } from '../lib/persistedState';
|
||||
import { MoviePosterCard } from '../components/MoviePosterCard';
|
||||
import type { MovieOut } from '../types';
|
||||
|
|
@ -51,6 +51,7 @@ export function MoviesScreen() {
|
|||
React.useCallback(() => listRef.current?.scrollToOffset({ offset: 0, animated: true }), []),
|
||||
);
|
||||
const bottomInset = useBottomBarInset();
|
||||
const onListScroll = useAutoHideOnScroll();
|
||||
// seenSince: ISO timestamp ostatniego "kompletnie obejrzanej listy". Movies
|
||||
// z created_at > seenSince mają NEW badge. Ładujemy z SecureStore raz
|
||||
// przy mount; aktualizujemy gdy app idzie do tła (user widzi NEW przez
|
||||
|
|
@ -152,6 +153,8 @@ export function MoviesScreen() {
|
|||
ref={listRef}
|
||||
data={items}
|
||||
numColumns={NUM_COLS}
|
||||
onScroll={onListScroll}
|
||||
scrollEventThrottle={16}
|
||||
keyExtractor={(m) => m.id}
|
||||
// Android removeClippedSubviews=true blankuje plakaty po scrollu (expo-image
|
||||
// nie re-renderuje odpiętych), ten sam bug co "znikające miniaturki" scen.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
SettingsButton,
|
||||
Toolbar,
|
||||
} from '../ui';
|
||||
import { useBottomBarInset, useScrollTopListener } from '../lib/bottomBar';
|
||||
import { useAutoHideOnScroll, useBottomBarInset, useScrollTopListener } from '../lib/bottomBar';
|
||||
import { SceneTile, sceneGridProps } from '../components/SceneTile';
|
||||
import { SceneGridSkeleton } from '../components/SceneGridSkeleton';
|
||||
import { Thumb } from '../components/Thumb';
|
||||
|
|
@ -75,6 +75,7 @@ export function ScenesScreen({
|
|||
React.useCallback(() => listRef.current?.scrollToOffset({ offset: 0, animated: true }), []),
|
||||
);
|
||||
const bottomInset = useBottomBarInset();
|
||||
const onListScroll = useAutoHideOnScroll();
|
||||
|
||||
React.useEffect(() => {
|
||||
const t = setTimeout(() => setDebouncedQ(q), 350);
|
||||
|
|
@ -273,6 +274,8 @@ export function ScenesScreen({
|
|||
ref={listRef}
|
||||
{...sceneGridProps(gridColumns)}
|
||||
data={items}
|
||||
onScroll={onListScroll}
|
||||
scrollEventThrottle={16}
|
||||
keyExtractor={(s) => s.id}
|
||||
// removeClippedSubviews + okno renderowania (windowSize itd.) idą z sceneGridProps
|
||||
// patrz SceneTile. Perf bug-report 5b7ca1e1 (jank/obciążenie przy długim scrollu).
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import type { RootStackParamList } from '../navigation';
|
|||
import { theme } from '../theme';
|
||||
import { PillButton, SCREEN_PAD_H, SCREEN_PAD_TOP, SearchInput, SettingsButton, Toolbar } from '../ui';
|
||||
import type { SourceOut, SourceRating } from '../types';
|
||||
import { useBottomBarInset, useScrollTopListener } from '../lib/bottomBar';
|
||||
import { useAutoHideOnScroll, useBottomBarInset, useScrollTopListener } from '../lib/bottomBar';
|
||||
|
||||
type Order = 'popular' | 'recent';
|
||||
|
||||
|
|
@ -42,6 +42,7 @@ export function SitesScreen() {
|
|||
React.useCallback(() => listRef.current?.scrollToOffset({ offset: 0, animated: true }), []),
|
||||
);
|
||||
const bottomInset = useBottomBarInset();
|
||||
const onListScroll = useAutoHideOnScroll();
|
||||
|
||||
React.useEffect(() => {
|
||||
const t = setTimeout(() => setDebouncedQ(q), 250);
|
||||
|
|
@ -108,7 +109,9 @@ export function SitesScreen() {
|
|||
<PillButton label="Top" compact active={order === 'popular'} onPress={() => setOrder('popular')} />
|
||||
<PillButton label="Recent" compact active={order === 'recent'} onPress={() => setOrder('recent')} />
|
||||
</View>
|
||||
{favQuery.data && pinned.size === 0 ? (
|
||||
<Text style={styles.hint}>long-press a site to pin it to the top</Text>
|
||||
) : null}
|
||||
|
||||
{isLoading && <ActivityIndicator color={theme.fg} style={{ marginTop: 24 }} />}
|
||||
{error instanceof Error && <Text style={styles.error}>{error.message}</Text>}
|
||||
|
|
@ -117,6 +120,8 @@ export function SitesScreen() {
|
|||
ref={listRef}
|
||||
data={items}
|
||||
keyExtractor={(s) => s.origin}
|
||||
onScroll={onListScroll}
|
||||
scrollEventThrottle={16}
|
||||
numColumns={2}
|
||||
columnWrapperStyle={styles.gridRow}
|
||||
renderItem={({ item }) => (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue