mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
some UI changes
This commit is contained in:
parent
c1bae1d7f3
commit
9b1368e7c6
8 changed files with 163 additions and 26 deletions
BIN
assets/Ripple loading animation.lottie
Normal file
BIN
assets/Ripple loading animation.lottie
Normal file
Binary file not shown.
|
|
@ -3,7 +3,7 @@ import { View, Text, StyleSheet, Dimensions } from 'react-native';
|
|||
import LottieView from 'lottie-react-native';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
||||
interface DogLoadingSpinnerProps {
|
||||
interface LoadingSpinnerProps {
|
||||
text?: string;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
style?: any;
|
||||
|
|
@ -11,7 +11,7 @@ interface DogLoadingSpinnerProps {
|
|||
offsetY?: number; // optional vertical offset
|
||||
}
|
||||
|
||||
const DogLoadingSpinner: React.FC<DogLoadingSpinnerProps> = ({
|
||||
const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
|
||||
text,
|
||||
size = 'large',
|
||||
style,
|
||||
|
|
@ -47,7 +47,7 @@ const DogLoadingSpinner: React.FC<DogLoadingSpinnerProps> = ({
|
|||
return (
|
||||
<View style={[styles.container, { transform: [{ translateY: offsetY }] }, style]}>
|
||||
<LottieView
|
||||
source={source || require('../../../assets/dog-running.lottie')}
|
||||
source={source || require('../../../assets/Ripple loading animation.lottie')}
|
||||
autoPlay
|
||||
loop
|
||||
style={[styles.animation, getSizeStyles()]}
|
||||
|
|
@ -83,4 +83,4 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
export default DogLoadingSpinner;
|
||||
export default LoadingSpinner;
|
||||
|
|
|
|||
86
src/components/common/LoadingSpinner.tsx
Normal file
86
src/components/common/LoadingSpinner.tsx
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import React from 'react';
|
||||
import { View, Text, StyleSheet, Dimensions } from 'react-native';
|
||||
import LottieView from 'lottie-react-native';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
text?: string;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
style?: any;
|
||||
source?: any; // optional override for Lottie source
|
||||
offsetY?: number; // optional vertical offset
|
||||
}
|
||||
|
||||
const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
|
||||
text,
|
||||
size = 'large',
|
||||
style,
|
||||
source,
|
||||
offsetY = 0,
|
||||
}) => {
|
||||
const { currentTheme } = useTheme();
|
||||
|
||||
const getSizeStyles = () => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return { width: 60, height: 60 };
|
||||
case 'medium':
|
||||
return { width: 100, height: 100 };
|
||||
case 'large':
|
||||
default:
|
||||
return { width: 150, height: 150 };
|
||||
}
|
||||
};
|
||||
|
||||
const getTextSize = () => {
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return 12;
|
||||
case 'medium':
|
||||
return 14;
|
||||
case 'large':
|
||||
default:
|
||||
return 16;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { transform: [{ translateY: offsetY }] }, style]}>
|
||||
<LottieView
|
||||
source={source || require('../../../assets/Ripple loading animation.lottie')}
|
||||
autoPlay
|
||||
loop
|
||||
style={[styles.animation, getSizeStyles()]}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
{text && (
|
||||
<Text style={[
|
||||
styles.text,
|
||||
{
|
||||
color: currentTheme.colors.textMuted,
|
||||
fontSize: getTextSize()
|
||||
}
|
||||
]}>
|
||||
{text}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
animation: {
|
||||
// Size will be set by getSizeStyles()
|
||||
},
|
||||
text: {
|
||||
marginTop: 16,
|
||||
textAlign: 'center',
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
|
||||
export default LoadingSpinner;
|
||||
31
src/contexts/LoadingContext.tsx
Normal file
31
src/contexts/LoadingContext.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import React, { createContext, useContext, useState, ReactNode } from 'react';
|
||||
|
||||
interface LoadingContextValue {
|
||||
isHomeLoading: boolean;
|
||||
setHomeLoading: (loading: boolean) => void;
|
||||
}
|
||||
|
||||
const LoadingContext = createContext<LoadingContextValue | undefined>(undefined);
|
||||
|
||||
export const LoadingProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [isHomeLoading, setIsHomeLoading] = useState(true);
|
||||
|
||||
const value: LoadingContextValue = {
|
||||
isHomeLoading,
|
||||
setHomeLoading: setIsHomeLoading,
|
||||
};
|
||||
|
||||
return (
|
||||
<LoadingContext.Provider value={value}>
|
||||
{children}
|
||||
</LoadingContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useLoading = (): LoadingContextValue => {
|
||||
const context = useContext(LoadingContext);
|
||||
if (!context) {
|
||||
throw new Error('useLoading must be used within a LoadingProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
|
@ -44,6 +44,7 @@ import OnboardingScreen from '../screens/OnboardingScreen';
|
|||
import AuthScreen from '../screens/AuthScreen';
|
||||
import AccountManageScreen from '../screens/AccountManageScreen';
|
||||
import { AccountProvider, useAccount } from '../contexts/AccountContext';
|
||||
import { LoadingProvider, useLoading } from '../contexts/LoadingContext';
|
||||
import PluginsScreen from '../screens/PluginsScreen';
|
||||
import CastMoviesScreen from '../screens/CastMoviesScreen';
|
||||
|
||||
|
|
@ -419,6 +420,7 @@ const WrappedScreen: React.FC<{Screen: React.ComponentType<any>}> = ({ Screen })
|
|||
// Tab Navigator
|
||||
const MainTabs = () => {
|
||||
const { currentTheme } = useTheme();
|
||||
const { isHomeLoading } = useLoading();
|
||||
const isTablet = Dimensions.get('window').width >= 768;
|
||||
const insets = useSafeAreaInsets();
|
||||
const isIosTablet = Platform.OS === 'ios' && isTablet;
|
||||
|
|
@ -438,6 +440,11 @@ const MainTabs = () => {
|
|||
const fade = headerAnim.interpolate({ inputRange: [0, 1], outputRange: [1, 0] });
|
||||
|
||||
const renderTabBar = (props: BottomTabBarProps) => {
|
||||
// Hide tab bar when home is loading
|
||||
if (isHomeLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isTablet) {
|
||||
// Top floating, text-only pill nav for tablets
|
||||
return (
|
||||
|
|
@ -840,17 +847,15 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
|
|||
}),
|
||||
}}
|
||||
>
|
||||
{!loading && !user && (
|
||||
<Stack.Screen
|
||||
name="Account"
|
||||
component={AuthScreen as any}
|
||||
options={{
|
||||
headerShown: false,
|
||||
animation: 'fade',
|
||||
contentStyle: { backgroundColor: currentTheme.colors.darkBackground },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Stack.Screen
|
||||
name="Account"
|
||||
component={AuthScreen as any}
|
||||
options={{
|
||||
headerShown: false,
|
||||
animation: 'fade',
|
||||
contentStyle: { backgroundColor: currentTheme.colors.darkBackground },
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Onboarding"
|
||||
component={OnboardingScreen}
|
||||
|
|
@ -1203,7 +1208,9 @@ const AppNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootStack
|
|||
}}
|
||||
>
|
||||
<AccountProvider>
|
||||
<InnerNavigator initialRouteName={initialRouteName} />
|
||||
<LoadingProvider>
|
||||
<InnerNavigator initialRouteName={initialRouteName} />
|
||||
</LoadingProvider>
|
||||
</AccountProvider>
|
||||
</PostHogProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -48,10 +48,11 @@ import { useSettings, settingsEmitter } from '../hooks/useSettings';
|
|||
import FeaturedContent from '../components/home/FeaturedContent';
|
||||
import CatalogSection from '../components/home/CatalogSection';
|
||||
import { SkeletonFeatured } from '../components/home/SkeletonLoaders';
|
||||
import DogLoadingSpinner from '../components/common/DogLoadingSpinner';
|
||||
import LoadingSpinner from '../components/common/LoadingSpinner';
|
||||
import homeStyles, { sharedStyles } from '../styles/homeStyles';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import type { Theme } from '../contexts/ThemeContext';
|
||||
import { useLoading } from '../contexts/LoadingContext';
|
||||
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
|
@ -94,7 +95,7 @@ const SkeletonCatalog = React.memo(() => {
|
|||
return (
|
||||
<View style={styles.catalogContainer}>
|
||||
<View style={styles.loadingPlaceholder}>
|
||||
<DogLoadingSpinner size="small" text="" />
|
||||
<LoadingSpinner size="small" text="" />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -104,6 +105,7 @@ const HomeScreen = () => {
|
|||
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
|
||||
const isDarkMode = useColorScheme() === 'dark';
|
||||
const { currentTheme } = useTheme();
|
||||
const { setHomeLoading } = useLoading();
|
||||
const continueWatchingRef = useRef<ContinueWatchingRef>(null);
|
||||
const { settings } = useSettings();
|
||||
const { lastUpdate } = useCatalogContext(); // Add catalog context to listen for addon changes
|
||||
|
|
@ -293,6 +295,11 @@ const HomeScreen = () => {
|
|||
return heroLoading && (catalogsLoading && loadedCatalogCount === 0);
|
||||
}, [showHeroSection, featuredLoading, catalogsLoading, loadedCatalogCount]);
|
||||
|
||||
// Update global loading state
|
||||
useEffect(() => {
|
||||
setHomeLoading(isLoading);
|
||||
}, [isLoading, setHomeLoading]);
|
||||
|
||||
// React to settings changes
|
||||
useEffect(() => {
|
||||
setShowHeroSection(settings.showHeroSection);
|
||||
|
|
@ -539,7 +546,7 @@ const HomeScreen = () => {
|
|||
translucent
|
||||
/>
|
||||
<View style={styles.loadingMainContainer}>
|
||||
<DogLoadingSpinner size="large" offsetY={-20} />
|
||||
<LoadingSpinner size="large" offsetY={-20} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
@ -639,7 +646,7 @@ const HomeScreen = () => {
|
|||
<View style={styles.catalogPlaceholder}>
|
||||
<View style={styles.placeholderHeader}>
|
||||
<View style={[styles.placeholderTitle, { backgroundColor: currentTheme.colors.elevation1 }]} />
|
||||
<DogLoadingSpinner size="small" text="" />
|
||||
<LoadingSpinner size="small" text="" />
|
||||
</View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import { logger } from '../utils/logger';
|
|||
import { BlurView } from 'expo-blur';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import DogLoadingSpinner from '../components/common/DogLoadingSpinner';
|
||||
import LoadingSpinner from '../components/common/LoadingSpinner';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
const isTablet = width >= 768;
|
||||
|
|
@ -585,10 +585,9 @@ const SearchScreen = () => {
|
|||
<View style={[styles.contentContainer, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
{searching ? (
|
||||
<View style={styles.loadingOverlay} pointerEvents="none">
|
||||
<DogLoadingSpinner
|
||||
<LoadingSpinner
|
||||
size="large"
|
||||
offsetY={-60}
|
||||
source={require('../../assets/Progress of loading hand.lottie')}
|
||||
/>
|
||||
</View>
|
||||
) : query.trim().length === 1 ? (
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ const SettingsScreen: React.FC = () => {
|
|||
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
|
||||
const { currentTheme } = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { user, signOut } = useAccount();
|
||||
const { user, signOut, loading: accountLoading } = useAccount();
|
||||
|
||||
// Tablet-specific state
|
||||
const [selectedCategory, setSelectedCategory] = useState('account');
|
||||
|
|
@ -378,7 +378,7 @@ const SettingsScreen: React.FC = () => {
|
|||
case 'account':
|
||||
return (
|
||||
<SettingsCard title="ACCOUNT" isTablet={isTablet}>
|
||||
{user ? (
|
||||
{!accountLoading && user ? (
|
||||
<>
|
||||
<SettingItem
|
||||
title={user.displayName || user.email || user.id}
|
||||
|
|
@ -388,7 +388,7 @@ const SettingsScreen: React.FC = () => {
|
|||
isTablet={isTablet}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
) : !accountLoading && !user ? (
|
||||
<SettingItem
|
||||
title="Sign in / Create account"
|
||||
description="Sync across devices"
|
||||
|
|
@ -396,6 +396,13 @@ const SettingsScreen: React.FC = () => {
|
|||
onPress={() => navigation.navigate('Account')}
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
) : (
|
||||
<SettingItem
|
||||
title="Loading account..."
|
||||
description="Please wait"
|
||||
icon="hourglass-empty"
|
||||
isTablet={isTablet}
|
||||
/>
|
||||
)}
|
||||
<SettingItem
|
||||
title="Trakt"
|
||||
|
|
|
|||
Loading…
Reference in a new issue