mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-29 20:33:42 +00:00
addes scrolltotop by clicking tab navigation buttons
This commit is contained in:
parent
d39a485d24
commit
95e7d44035
7 changed files with 206 additions and 10 deletions
57
src/contexts/ScrollToTopContext.tsx
Normal file
57
src/contexts/ScrollToTopContext.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React, { createContext, useContext, useCallback, useRef, useEffect } from 'react';
|
||||||
|
|
||||||
|
type ScrollToTopListener = () => void;
|
||||||
|
|
||||||
|
interface ScrollToTopContextType {
|
||||||
|
emitScrollToTop: (routeName: string) => void;
|
||||||
|
subscribe: (routeName: string, listener: ScrollToTopListener) => () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScrollToTopContext = createContext<ScrollToTopContextType | null>(null);
|
||||||
|
|
||||||
|
export const ScrollToTopProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const listenersRef = useRef<Map<string, Set<ScrollToTopListener>>>(new Map());
|
||||||
|
|
||||||
|
const subscribe = useCallback((routeName: string, listener: ScrollToTopListener) => {
|
||||||
|
if (!listenersRef.current.has(routeName)) {
|
||||||
|
listenersRef.current.set(routeName, new Set());
|
||||||
|
}
|
||||||
|
listenersRef.current.get(routeName)!.add(listener);
|
||||||
|
|
||||||
|
// Return unsubscribe function
|
||||||
|
return () => {
|
||||||
|
listenersRef.current.get(routeName)?.delete(listener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const emitScrollToTop = useCallback((routeName: string) => {
|
||||||
|
const listeners = listenersRef.current.get(routeName);
|
||||||
|
if (listeners) {
|
||||||
|
listeners.forEach(listener => listener());
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollToTopContext.Provider value={{ emitScrollToTop, subscribe }}>
|
||||||
|
{children}
|
||||||
|
</ScrollToTopContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useScrollToTop = (routeName: string, scrollToTop: () => void) => {
|
||||||
|
const context = useContext(ScrollToTopContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!context) return;
|
||||||
|
|
||||||
|
const unsubscribe = context.subscribe(routeName, scrollToTop);
|
||||||
|
return unsubscribe;
|
||||||
|
}, [context, routeName, scrollToTop]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useScrollToTopEmitter = () => {
|
||||||
|
const context = useContext(ScrollToTopContext);
|
||||||
|
return context?.emitScrollToTop || (() => { });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScrollToTopContext;
|
||||||
|
|
@ -16,6 +16,7 @@ import { Stream } from '../types/streams';
|
||||||
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { useTheme } from '../contexts/ThemeContext';
|
import { useTheme } from '../contexts/ThemeContext';
|
||||||
import { PostHogProvider } from 'posthog-react-native';
|
import { PostHogProvider } from 'posthog-react-native';
|
||||||
|
import { ScrollToTopProvider, useScrollToTopEmitter } from '../contexts/ScrollToTopContext';
|
||||||
|
|
||||||
// Optional iOS Glass effect (expo-glass-effect) with safe fallback
|
// Optional iOS Glass effect (expo-glass-effect) with safe fallback
|
||||||
let GlassViewComp: any = null;
|
let GlassViewComp: any = null;
|
||||||
|
|
@ -581,6 +582,7 @@ const MainTabs = () => {
|
||||||
const isIosTablet = Platform.OS === 'ios' && isTablet;
|
const isIosTablet = Platform.OS === 'ios' && isTablet;
|
||||||
const [hidden, setHidden] = React.useState(HeaderVisibility.isHidden());
|
const [hidden, setHidden] = React.useState(HeaderVisibility.isHidden());
|
||||||
React.useEffect(() => HeaderVisibility.subscribe(setHidden), []);
|
React.useEffect(() => HeaderVisibility.subscribe(setHidden), []);
|
||||||
|
const emitScrollToTop = useScrollToTopEmitter();
|
||||||
// Smooth animate header hide/show
|
// Smooth animate header hide/show
|
||||||
const headerAnim = React.useRef(new Animated.Value(0)).current; // 0: shown, 1: hidden
|
const headerAnim = React.useRef(new Animated.Value(0)).current; // 0: shown, 1: hidden
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
@ -674,7 +676,10 @@ const MainTabs = () => {
|
||||||
target: route.key,
|
target: route.key,
|
||||||
canPreventDefault: true,
|
canPreventDefault: true,
|
||||||
});
|
});
|
||||||
if (!isFocused && !event.defaultPrevented) {
|
if (isFocused) {
|
||||||
|
// Same tab pressed - emit scroll to top
|
||||||
|
emitScrollToTop(route.name);
|
||||||
|
} else if (!event.defaultPrevented) {
|
||||||
props.navigation.navigate(route.name);
|
props.navigation.navigate(route.name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -789,7 +794,10 @@ const MainTabs = () => {
|
||||||
canPreventDefault: true,
|
canPreventDefault: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isFocused && !event.defaultPrevented) {
|
if (isFocused) {
|
||||||
|
// Same tab pressed - emit scroll to top
|
||||||
|
emitScrollToTop(route.name);
|
||||||
|
} else if (!event.defaultPrevented) {
|
||||||
props.navigation.navigate(route.name);
|
props.navigation.navigate(route.name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -893,6 +901,13 @@ const MainTabs = () => {
|
||||||
tabBarIcon: () => ({ sfSymbol: 'house' }),
|
tabBarIcon: () => ({ sfSymbol: 'house' }),
|
||||||
freezeOnBlur: true,
|
freezeOnBlur: true,
|
||||||
}}
|
}}
|
||||||
|
listeners={({ navigation }) => ({
|
||||||
|
tabPress: (e) => {
|
||||||
|
if (navigation.isFocused()) {
|
||||||
|
emitScrollToTop('Home');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<IOSTab.Screen
|
<IOSTab.Screen
|
||||||
name="Library"
|
name="Library"
|
||||||
|
|
@ -901,6 +916,13 @@ const MainTabs = () => {
|
||||||
title: 'Library',
|
title: 'Library',
|
||||||
tabBarIcon: () => ({ sfSymbol: 'heart' }),
|
tabBarIcon: () => ({ sfSymbol: 'heart' }),
|
||||||
}}
|
}}
|
||||||
|
listeners={({ navigation }) => ({
|
||||||
|
tabPress: (e) => {
|
||||||
|
if (navigation.isFocused()) {
|
||||||
|
emitScrollToTop('Library');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
<IOSTab.Screen
|
<IOSTab.Screen
|
||||||
name="Search"
|
name="Search"
|
||||||
|
|
@ -909,6 +931,13 @@ const MainTabs = () => {
|
||||||
title: 'Search',
|
title: 'Search',
|
||||||
tabBarIcon: () => ({ sfSymbol: 'magnifyingglass' }),
|
tabBarIcon: () => ({ sfSymbol: 'magnifyingglass' }),
|
||||||
}}
|
}}
|
||||||
|
listeners={({ navigation }) => ({
|
||||||
|
tabPress: (e) => {
|
||||||
|
if (navigation.isFocused()) {
|
||||||
|
emitScrollToTop('Search');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
{downloadsEnabled && (
|
{downloadsEnabled && (
|
||||||
<IOSTab.Screen
|
<IOSTab.Screen
|
||||||
|
|
@ -918,6 +947,13 @@ const MainTabs = () => {
|
||||||
title: 'Downloads',
|
title: 'Downloads',
|
||||||
tabBarIcon: () => ({ sfSymbol: 'arrow.down.circle' }),
|
tabBarIcon: () => ({ sfSymbol: 'arrow.down.circle' }),
|
||||||
}}
|
}}
|
||||||
|
listeners={({ navigation }) => ({
|
||||||
|
tabPress: (e) => {
|
||||||
|
if (navigation.isFocused()) {
|
||||||
|
emitScrollToTop('Downloads');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<IOSTab.Screen
|
<IOSTab.Screen
|
||||||
|
|
@ -927,6 +963,13 @@ const MainTabs = () => {
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
tabBarIcon: () => ({ sfSymbol: 'gear' }),
|
tabBarIcon: () => ({ sfSymbol: 'gear' }),
|
||||||
}}
|
}}
|
||||||
|
listeners={({ navigation }) => ({
|
||||||
|
tabPress: (e) => {
|
||||||
|
if (navigation.isFocused()) {
|
||||||
|
emitScrollToTop('Settings');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</IOSTab.Navigator>
|
</IOSTab.Navigator>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -1612,9 +1655,11 @@ const AppNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootStack
|
||||||
host: "https://us.i.posthog.com",
|
host: "https://us.i.posthog.com",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LoadingProvider>
|
<ScrollToTopProvider>
|
||||||
<InnerNavigator initialRouteName={initialRouteName} />
|
<LoadingProvider>
|
||||||
</LoadingProvider>
|
<InnerNavigator initialRouteName={initialRouteName} />
|
||||||
|
</LoadingProvider>
|
||||||
|
</ScrollToTopProvider>
|
||||||
</PostHogProvider>
|
</PostHogProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
import React, { useCallback, useState, useEffect, useMemo, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
|
|
@ -35,6 +35,7 @@ import type { DownloadItem } from '../contexts/DownloadsContext';
|
||||||
import { useToast } from '../contexts/ToastContext';
|
import { useToast } from '../contexts/ToastContext';
|
||||||
import CustomAlert from '../components/CustomAlert';
|
import CustomAlert from '../components/CustomAlert';
|
||||||
import ScreenHeader from '../components/common/ScreenHeader';
|
import ScreenHeader from '../components/common/ScreenHeader';
|
||||||
|
import { useScrollToTop } from '../contexts/ScrollToTopContext';
|
||||||
|
|
||||||
const { height, width } = Dimensions.get('window');
|
const { height, width } = Dimensions.get('window');
|
||||||
const isTablet = width >= 768;
|
const isTablet = width >= 768;
|
||||||
|
|
@ -355,6 +356,14 @@ const DownloadsScreen: React.FC = () => {
|
||||||
const [showHelpAlert, setShowHelpAlert] = useState(false);
|
const [showHelpAlert, setShowHelpAlert] = useState(false);
|
||||||
const [showRemoveAlert, setShowRemoveAlert] = useState(false);
|
const [showRemoveAlert, setShowRemoveAlert] = useState(false);
|
||||||
const [pendingRemoveItem, setPendingRemoveItem] = useState<DownloadItem | null>(null);
|
const [pendingRemoveItem, setPendingRemoveItem] = useState<DownloadItem | null>(null);
|
||||||
|
const flatListRef = useRef<FlatList>(null);
|
||||||
|
|
||||||
|
// Scroll to top handler
|
||||||
|
const scrollToTop = useCallback(() => {
|
||||||
|
flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useScrollToTop('Downloads', scrollToTop);
|
||||||
|
|
||||||
// Filter downloads based on selected filter
|
// Filter downloads based on selected filter
|
||||||
const filteredDownloads = useMemo(() => {
|
const filteredDownloads = useMemo(() => {
|
||||||
|
|
@ -656,6 +665,7 @@ const DownloadsScreen: React.FC = () => {
|
||||||
<EmptyDownloadsState navigation={navigation} />
|
<EmptyDownloadsState navigation={navigation} />
|
||||||
) : (
|
) : (
|
||||||
<FlatList
|
<FlatList
|
||||||
|
ref={flatListRef}
|
||||||
data={filteredDownloads}
|
data={filteredDownloads}
|
||||||
keyExtractor={(item) => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ import { useToast } from '../contexts/ToastContext';
|
||||||
import FirstTimeWelcome from '../components/FirstTimeWelcome';
|
import FirstTimeWelcome from '../components/FirstTimeWelcome';
|
||||||
import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
import { HeaderVisibility } from '../contexts/HeaderVisibility';
|
||||||
import { useTrailer } from '../contexts/TrailerContext';
|
import { useTrailer } from '../contexts/TrailerContext';
|
||||||
|
import { useScrollToTop } from '../contexts/ScrollToTopContext';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const CATALOG_SETTINGS_KEY = 'catalog_settings';
|
const CATALOG_SETTINGS_KEY = 'catalog_settings';
|
||||||
|
|
@ -137,6 +138,25 @@ const HomeScreen = () => {
|
||||||
const totalCatalogsRef = useRef(0);
|
const totalCatalogsRef = useRef(0);
|
||||||
const [visibleCatalogCount, setVisibleCatalogCount] = useState(5); // Reduced for memory
|
const [visibleCatalogCount, setVisibleCatalogCount] = useState(5); // Reduced for memory
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
const flashListRef = useRef<any>(null);
|
||||||
|
|
||||||
|
// Scroll to top handler - use scrollToIndex and retry to handle re-renders
|
||||||
|
const scrollToTop = useCallback(() => {
|
||||||
|
// First attempt
|
||||||
|
flashListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
||||||
|
|
||||||
|
// Retry after a short delay in case re-render interrupted the scroll
|
||||||
|
setTimeout(() => {
|
||||||
|
flashListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
||||||
|
}, 150);
|
||||||
|
|
||||||
|
// Final retry to ensure we're at the top
|
||||||
|
setTimeout(() => {
|
||||||
|
flashListRef.current?.scrollToOffset({ offset: 0, animated: false });
|
||||||
|
}, 400);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useScrollToTop('Home', scrollToTop);
|
||||||
|
|
||||||
// Stabilize insets to prevent iOS layout shifts
|
// Stabilize insets to prevent iOS layout shifts
|
||||||
const [stableInsetsTop, setStableInsetsTop] = useState(insets.top);
|
const [stableInsetsTop, setStableInsetsTop] = useState(insets.top);
|
||||||
|
|
@ -890,6 +910,7 @@ const HomeScreen = () => {
|
||||||
translucent
|
translucent
|
||||||
/>
|
/>
|
||||||
<FlashList
|
<FlashList
|
||||||
|
ref={flashListRef}
|
||||||
data={listData}
|
data={listData}
|
||||||
renderItem={renderListItem}
|
renderItem={renderListItem}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||||
import { DeviceEventEmitter } from 'react-native';
|
import { DeviceEventEmitter } from 'react-native';
|
||||||
import { Share } from 'react-native';
|
import { Share } from 'react-native';
|
||||||
import { mmkvStorage } from '../services/mmkvStorage';
|
import { mmkvStorage } from '../services/mmkvStorage';
|
||||||
|
|
@ -38,6 +38,7 @@ import TraktIcon from '../../assets/rating-icons/trakt.svg';
|
||||||
import { traktService, TraktService, TraktImages } from '../services/traktService';
|
import { traktService, TraktService, TraktImages } from '../services/traktService';
|
||||||
import { TraktLoadingSpinner } from '../components/common/TraktLoadingSpinner';
|
import { TraktLoadingSpinner } from '../components/common/TraktLoadingSpinner';
|
||||||
import { useSettings } from '../hooks/useSettings';
|
import { useSettings } from '../hooks/useSettings';
|
||||||
|
import { useScrollToTop } from '../contexts/ScrollToTopContext';
|
||||||
|
|
||||||
interface LibraryItem extends StreamingContent {
|
interface LibraryItem extends StreamingContent {
|
||||||
progress?: number;
|
progress?: number;
|
||||||
|
|
@ -225,6 +226,14 @@ const LibraryScreen = () => {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { currentTheme } = useTheme();
|
const { currentTheme } = useTheme();
|
||||||
const { settings } = useSettings();
|
const { settings } = useSettings();
|
||||||
|
const flashListRef = useRef<any>(null);
|
||||||
|
|
||||||
|
// Scroll to top handler
|
||||||
|
const scrollToTop = useCallback(() => {
|
||||||
|
flashListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useScrollToTop('Library', scrollToTop);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isAuthenticated: traktAuthenticated,
|
isAuthenticated: traktAuthenticated,
|
||||||
|
|
@ -733,6 +742,7 @@ const LibraryScreen = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlashList
|
<FlashList
|
||||||
|
ref={flashListRef}
|
||||||
data={traktFolders}
|
data={traktFolders}
|
||||||
renderItem={({ item }) => renderTraktCollectionFolder({ folder: item })}
|
renderItem={({ item }) => renderTraktCollectionFolder({ folder: item })}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
|
|
@ -774,6 +784,7 @@ const LibraryScreen = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlashList
|
<FlashList
|
||||||
|
ref={flashListRef}
|
||||||
data={folderItems}
|
data={folderItems}
|
||||||
renderItem={({ item }) => renderTraktItem({ item })}
|
renderItem={({ item }) => renderTraktItem({ item })}
|
||||||
keyExtractor={(item) => `${item.type}-${item.id}`}
|
keyExtractor={(item) => `${item.type}-${item.id}`}
|
||||||
|
|
@ -874,6 +885,7 @@ const LibraryScreen = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlashList
|
<FlashList
|
||||||
|
ref={flashListRef}
|
||||||
data={filteredItems}
|
data={filteredItems}
|
||||||
renderItem={({ item }) => renderItem({ item: item as LibraryItem })}
|
renderItem={({ item }) => renderItem({ item: item as LibraryItem })}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { useTheme } from '../contexts/ThemeContext';
|
import { useTheme } from '../contexts/ThemeContext';
|
||||||
import LoadingSpinner from '../components/common/LoadingSpinner';
|
import LoadingSpinner from '../components/common/LoadingSpinner';
|
||||||
import ScreenHeader from '../components/common/ScreenHeader';
|
import ScreenHeader from '../components/common/ScreenHeader';
|
||||||
|
import { useScrollToTop } from '../contexts/ScrollToTopContext';
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
|
|
||||||
|
|
@ -237,6 +238,14 @@ const SearchScreen = () => {
|
||||||
const isInitialMount = useRef(true);
|
const isInitialMount = useRef(true);
|
||||||
// Track mount status for async operations
|
// Track mount status for async operations
|
||||||
const isMounted = useRef(true);
|
const isMounted = useRef(true);
|
||||||
|
const scrollViewRef = useRef<ScrollView>(null);
|
||||||
|
|
||||||
|
// Scroll to top handler
|
||||||
|
const scrollToTop = useCallback(() => {
|
||||||
|
scrollViewRef.current?.scrollTo({ y: 0, animated: true });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useScrollToTop('Search', scrollToTop);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
isMounted.current = true;
|
isMounted.current = true;
|
||||||
|
|
@ -994,6 +1003,7 @@ const SearchScreen = () => {
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
ref={scrollViewRef}
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
contentContainerStyle={styles.scrollViewContent}
|
contentContainerStyle={styles.scrollViewContent}
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useState, useEffect } from 'react';
|
import React, { useCallback, useState, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
|
|
@ -40,6 +40,7 @@ import TraktIcon from '../components/icons/TraktIcon';
|
||||||
import TMDBIcon from '../components/icons/TMDBIcon';
|
import TMDBIcon from '../components/icons/TMDBIcon';
|
||||||
import MDBListIcon from '../components/icons/MDBListIcon';
|
import MDBListIcon from '../components/icons/MDBListIcon';
|
||||||
import { campaignService } from '../services/campaignService';
|
import { campaignService } from '../services/campaignService';
|
||||||
|
import { useScrollToTop } from '../contexts/ScrollToTopContext';
|
||||||
|
|
||||||
const { width, height } = Dimensions.get('window');
|
const { width, height } = Dimensions.get('window');
|
||||||
const isTablet = width >= 768;
|
const isTablet = width >= 768;
|
||||||
|
|
@ -320,6 +321,17 @@ const SettingsScreen: React.FC = () => {
|
||||||
const [displayDownloads, setDisplayDownloads] = useState<number | null>(null);
|
const [displayDownloads, setDisplayDownloads] = useState<number | null>(null);
|
||||||
const [isCountingUp, setIsCountingUp] = useState<boolean>(false);
|
const [isCountingUp, setIsCountingUp] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// Scroll to top ref and handler
|
||||||
|
const mobileScrollViewRef = useRef<ScrollView>(null);
|
||||||
|
const tabletScrollViewRef = useRef<ScrollView>(null);
|
||||||
|
|
||||||
|
const scrollToTop = useCallback(() => {
|
||||||
|
mobileScrollViewRef.current?.scrollTo({ y: 0, animated: true });
|
||||||
|
tabletScrollViewRef.current?.scrollTo({ y: 0, animated: true });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useScrollToTop('Settings', scrollToTop);
|
||||||
|
|
||||||
// Add a useEffect to check Trakt authentication status on focus
|
// Add a useEffect to check Trakt authentication status on focus
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This will reload the Trakt auth status whenever the settings screen is focused
|
// This will reload the Trakt auth status whenever the settings screen is focused
|
||||||
|
|
@ -923,6 +935,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
}
|
}
|
||||||
]}>
|
]}>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
ref={tabletScrollViewRef}
|
||||||
style={styles.tabletScrollView}
|
style={styles.tabletScrollView}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={styles.tabletScrollContent}
|
contentContainerStyle={styles.tabletScrollContent}
|
||||||
|
|
@ -1005,6 +1018,14 @@ const SettingsScreen: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.brandLogoContainer}>
|
||||||
|
<FastImage
|
||||||
|
source={require('../../assets/nuviotext.png')}
|
||||||
|
style={styles.brandLogo}
|
||||||
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||||
Made with ❤️ by Tapframe and Friends
|
Made with ❤️ by Tapframe and Friends
|
||||||
|
|
@ -1040,6 +1061,7 @@ const SettingsScreen: React.FC = () => {
|
||||||
|
|
||||||
<View style={styles.contentContainer}>
|
<View style={styles.contentContainer}>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
ref={mobileScrollViewRef}
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
|
|
@ -1131,6 +1153,14 @@ const SettingsScreen: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.brandLogoContainer}>
|
||||||
|
<FastImage
|
||||||
|
source={require('../../assets/nuviotext.png')}
|
||||||
|
style={styles.brandLogo}
|
||||||
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||||
Made with ❤️ by Tapframe and friends
|
Made with ❤️ by Tapframe and friends
|
||||||
|
|
@ -1368,7 +1398,7 @@ const styles = StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
marginBottom: 12,
|
marginBottom: 48,
|
||||||
},
|
},
|
||||||
footerText: {
|
footerText: {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
|
@ -1437,12 +1467,23 @@ const styles = StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
marginBottom: 32,
|
marginBottom: 16,
|
||||||
},
|
},
|
||||||
monkeyAnimation: {
|
monkeyAnimation: {
|
||||||
width: 180,
|
width: 180,
|
||||||
height: 180,
|
height: 180,
|
||||||
},
|
},
|
||||||
|
brandLogoContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginTop: 0,
|
||||||
|
marginBottom: 16,
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
brandLogo: {
|
||||||
|
width: 120,
|
||||||
|
height: 40,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SettingsScreen;
|
export default SettingsScreen;
|
||||||
Loading…
Reference in a new issue