From 9b1368e7c6890b19a7261ead66501fa7c1c2266e Mon Sep 17 00:00:00 2001 From: tapframe Date: Mon, 1 Sep 2025 18:17:08 +0530 Subject: [PATCH] some UI changes --- assets/Ripple loading animation.lottie | Bin 0 -> 1431 bytes src/components/common/DogLoadingSpinner.tsx | 8 +- src/components/common/LoadingSpinner.tsx | 86 ++++++++++++++++++++ src/contexts/LoadingContext.tsx | 31 +++++++ src/navigation/AppNavigator.tsx | 31 ++++--- src/screens/HomeScreen.tsx | 15 +++- src/screens/SearchScreen.tsx | 5 +- src/screens/SettingsScreen.tsx | 13 ++- 8 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 assets/Ripple loading animation.lottie create mode 100644 src/components/common/LoadingSpinner.tsx create mode 100644 src/contexts/LoadingContext.tsx diff --git a/assets/Ripple loading animation.lottie b/assets/Ripple loading animation.lottie new file mode 100644 index 0000000000000000000000000000000000000000..09a3e025cf5b9763cb1a253337c6797fbd8222e3 GIT binary patch literal 1431 zcmWIWW@Zs#U|`^2_>iI){rk7nx&|O`D-iPnac*K>W?E`-iC$K5eqL{{Bi|tf0oV6N z*(+IBU0GMf=${aiFhi@&h|Nv-@|Sn#PL-DbSAYLh_rt*;v@4wVEoDY^CAH~KB?b0uf6`o9DwRnZPxVX%nS@R$_xx9K&Jv-nwwaXnV(mzZ=Pyw zWR_xKplf8Bma1!FWMHgoX_9EDYi?+fnq+KfZl0Q!3ii?4nZEh69R%9`cWq6V+E{b+ z+uG2@F;y$3teUQ|X@zf&6c5J>cafj_zspL9>Cdnz&C+s}Nk~k;Idg9L4Rs6uN6Tk$ zKX;lX9whO|PPq8IiOY;G%ULYFTM9mUpINSUi+B4+F@M+2{alZ~9_l~5HonEPhcj$- z=+7ndpF2+M{`q#UqSAlE6EA*w&O9u!*2Lwb#xp+u^35MFCw%H;^N;bFsw~D+d0?`? z(!$B{rBjSsuY8oA{;c(R>>=*7T~o`gS?)&U+)nHEoF*ZD$j7iwQ9O<7^&t_7fR3H1 zhZWCE>-^oY%;@FB`gg~VWu4^rjkj!a|HM4o{PsfqBU6Hs#B}z@7+q>r4Zd_D=@3)s zH=XT#A`6?-mKch)9oS{h;+DE>=jl0SX)ehh4mR(R^=(p3a`8J9@@ryT(p<;60*sRv zc)1C>s6_4eOJzv?*2DbMc#BTbnp2PC_;}y?h4*bf5Lqg7$5NKlx0ub{V!oJHm-^R& zDBE2pN(E$u*iU5b_R77qS~YX=G~aVCzwYQ-WN^0mWcvOaJ}+YLDye<^_qcua$?FM` zDmRx@HQw{BP-)9iIKNl^spHGZE?OSDx;pJ&{C(^|D5W3ScGQx5#omU+(-k-gh4 zv+2db4M*1oe^UMY_>bMmZB0QdQ#*x3lII2_MXu|)UZ9YBJ@S*X?Tj_0eedQSNatYM zo%DX0Nv_t4J=a$ItXjKdSDqG6a-i>3$!V|dXqAS)4Sf5{uKjx2>7?y>+gncOX8O*X zQ0%f&J1W;+d|qj$#}+-l^k)~d)nvZ}+|Nl%?0NEX*_Pj1ves(4tl~LxQSZntpQnuv z6uh>qne~3^hrq+LUh}-*dX!WYoO04RRF9qOu)q03q5LafcW2EBQ+=|o^`uPQ#hPYr zl_jm#QxfFg%~@EezBjRe{pjz)^u3vP0{^XeFEy1x0r1d9Vz?*m@-|FD+-w?TBj?4PSk?R5Ul(p}TMKaE-X zW4TDZhqwH{Hw!wnJo8f=R1%6*DtRZ6Oxazc%|ZBX#V!l`K9K(&x0R6KV|l3 zi(KqyD-OO+=9G`Js{c6l3)bl$5(xiw;7zb}#OzvM*8Vrv=j?vGOP+7~Lx1k!4gR0< zMcZ#Y_i_KWcV4Z|k|W__d{a{zmaSgVDPMT%^U61y=bHV77^!E#H{s~^sXvr|yM5cs z = ({ +const LoadingSpinner: React.FC = ({ text, size = 'large', style, @@ -47,7 +47,7 @@ const DogLoadingSpinner: React.FC = ({ return ( = ({ + 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 ( + + + {text && ( + + {text} + + )} + + ); +}; + +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; diff --git a/src/contexts/LoadingContext.tsx b/src/contexts/LoadingContext.tsx new file mode 100644 index 00000000..08913494 --- /dev/null +++ b/src/contexts/LoadingContext.tsx @@ -0,0 +1,31 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; + +interface LoadingContextValue { + isHomeLoading: boolean; + setHomeLoading: (loading: boolean) => void; +} + +const LoadingContext = createContext(undefined); + +export const LoadingProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [isHomeLoading, setIsHomeLoading] = useState(true); + + const value: LoadingContextValue = { + isHomeLoading, + setHomeLoading: setIsHomeLoading, + }; + + return ( + + {children} + + ); +}; + +export const useLoading = (): LoadingContextValue => { + const context = useContext(LoadingContext); + if (!context) { + throw new Error('useLoading must be used within a LoadingProvider'); + } + return context; +}; diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index cf9b1553..7a5f09fe 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -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}> = ({ 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 && ( - - )} + - + + + ); diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 52d4820e..2687fa0c 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -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 ( - + ); @@ -104,6 +105,7 @@ const HomeScreen = () => { const navigation = useNavigation>(); const isDarkMode = useColorScheme() === 'dark'; const { currentTheme } = useTheme(); + const { setHomeLoading } = useLoading(); const continueWatchingRef = useRef(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 /> - + ); @@ -639,7 +646,7 @@ const HomeScreen = () => { - + = 768; @@ -585,10 +585,9 @@ const SearchScreen = () => { {searching ? ( - ) : query.trim().length === 1 ? ( diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index 24c2de43..14dc1b6d 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -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 ( - {user ? ( + {!accountLoading && user ? ( <> { isTablet={isTablet} /> - ) : ( + ) : !accountLoading && !user ? ( { onPress={() => navigation.navigate('Account')} isTablet={isTablet} /> + ) : ( + )}