From 759215da8c1ecd9600ebab084aac9bfdfa334597 Mon Sep 17 00:00:00 2001 From: tapframe Date: Mon, 22 Dec 2025 01:54:36 +0530 Subject: [PATCH] onboarding screen UI update --- src/components/metadata/.HeroSection.tsx.swp | Bin 0 -> 16384 bytes src/screens/OnboardingScreen.tsx | 682 +++++++------------ 2 files changed, 244 insertions(+), 438 deletions(-) create mode 100644 src/components/metadata/.HeroSection.tsx.swp diff --git a/src/components/metadata/.HeroSection.tsx.swp b/src/components/metadata/.HeroSection.tsx.swp new file mode 100644 index 0000000000000000000000000000000000000000..6a8c4627e1749cc50a75056595b826f85ba0b4a3 GIT binary patch literal 16384 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 void; - backgroundColor: string; - text: string; - icon: string; -}) => { - const scale = useSharedValue(1); - - const animatedStyle = useAnimatedStyle(() => ({ - transform: [{ scale: scale.value }], - })); - - const handlePressIn = () => { - scale.value = withSpring(0.95, SPRING_CONFIG); - }; - - const handlePressOut = () => { - scale.value = withSpring(1, SPRING_CONFIG); - }; - - return ( - - - {text} - - - - ); -}; - -// Slide Content Component with animations -const SlideContent = ({ item, isActive }: { item: OnboardingSlide; isActive: boolean }) => { - // Premium icon animations: scale, floating, rotation, and glow - const iconScale = useSharedValue(isActive ? 1 : 0.8); - const iconOpacity = useSharedValue(isActive ? 1 : 0); - const iconTranslateY = useSharedValue(isActive ? 0 : 20); - const iconRotation = useSharedValue(0); - const glowIntensity = useSharedValue(isActive ? 1 : 0); - - React.useEffect(() => { - if (isActive) { - iconScale.value = withSpring(1.1, SPRING_CONFIG); - iconOpacity.value = withTiming(1, SLIDE_TIMING); - iconTranslateY.value = withSpring(0, SPRING_CONFIG); - iconRotation.value = withSpring(0, SPRING_CONFIG); - glowIntensity.value = withSpring(1, SPRING_CONFIG); - } else { - iconScale.value = 0.8; - iconOpacity.value = 0; - iconTranslateY.value = 20; - iconRotation.value = -15; - glowIntensity.value = 0; - } - }, [isActive]); - - const animatedIconStyle = useAnimatedStyle(() => { - return { - transform: [ - { scale: iconScale.value }, - { translateY: iconTranslateY.value }, - { rotate: `${iconRotation.value}deg` }, - ], - opacity: iconOpacity.value, - }; - }); - - // Premium floating animation for active icon - const floatAnim = useSharedValue(0); - React.useEffect(() => { - if (isActive) { - floatAnim.value = withRepeat( - withSequence( - withTiming(10, { duration: 2500 }), - withTiming(-10, { duration: 2500 }) - ), - -1, - true - ); - } else { - floatAnim.value = 0; - } - }, [isActive]); - - const floatingIconStyle = useAnimatedStyle(() => ({ - transform: [{ translateY: floatAnim.value }], - })); - - // Glow animation with pulse effect - const pulseAnim = useSharedValue(1); - React.useEffect(() => { - if (isActive) { - pulseAnim.value = withRepeat( - withSequence( - withTiming(1.3, { duration: 2000 }), - withTiming(1.1, { duration: 2000 }) - ), - -1, - true - ); - } - }, [isActive]); - - const animatedGlowStyle = useAnimatedStyle(() => ({ - opacity: glowIntensity.value * 0.5, - transform: [{ scale: pulseAnim.value * 1.2 + iconScale.value * 0.3 }], - })); - - return ( - - {/* Premium glow effect */} - - - - - - - - - - - - - {item.title} - - - {item.subtitle} - - - {item.description} - - - - ); + damping: 20, + stiffness: 90, + mass: 0.8, }; interface OnboardingSlide { @@ -240,90 +39,180 @@ interface OnboardingSlide { title: string; subtitle: string; description: string; - icon: keyof typeof MaterialIcons.glyphMap; - gradient: [string, string]; } const onboardingData: OnboardingSlide[] = [ { id: '1', - title: 'Welcome to Nuvio', + title: 'Welcome to\nNuvio', subtitle: 'Your Ultimate Content Hub', description: 'Discover, organize, and manage your favorite movies and TV shows from multiple sources in one beautiful app.', - icon: 'play-circle-filled', - gradient: ['#667eea', '#764ba2'], }, { id: '2', - title: 'Powerful Addons', + title: 'Powerful\nAddons', subtitle: 'Extend Your Experience', description: 'Install addons to access content from various platforms and services. Choose what works best for you.', - icon: 'extension', - gradient: ['#f093fb', '#f5576c'], }, { id: '3', - title: 'Smart Discovery', + title: 'Smart\nDiscovery', subtitle: 'Find What You Love', description: 'Browse trending content, search across all your sources, and get personalized recommendations.', - icon: 'explore', - gradient: ['#4facfe', '#00f2fe'], }, { id: '4', - title: 'Your Library', + title: 'Your\nLibrary', subtitle: 'Track & Organize', description: 'Save favorites, track your progress, and sync with Trakt to keep everything organized across devices.', - icon: 'library-books', - gradient: ['#43e97b', '#38f9d7'], }, ]; +// Animated Slide Component with parallax +const AnimatedSlide = ({ + item, + index, + scrollX +}: { + item: OnboardingSlide; + index: number; + scrollX: SharedValue; +}) => { + const inputRange = [(index - 1) * width, index * width, (index + 1) * width]; + + const titleStyle = useAnimatedStyle(() => { + const translateX = interpolate( + scrollX.value, + inputRange, + [width * 0.3, 0, -width * 0.3], + Extrapolation.CLAMP + ); + const opacity = interpolate( + scrollX.value, + inputRange, + [0, 1, 0], + Extrapolation.CLAMP + ); + const scale = interpolate( + scrollX.value, + inputRange, + [0.8, 1, 0.8], + Extrapolation.CLAMP + ); + return { + transform: [{ translateX }, { scale }], + opacity, + }; + }); + + const subtitleStyle = useAnimatedStyle(() => { + const translateX = interpolate( + scrollX.value, + inputRange, + [width * 0.5, 0, -width * 0.5], + Extrapolation.CLAMP + ); + const opacity = interpolate( + scrollX.value, + inputRange, + [0, 1, 0], + Extrapolation.CLAMP + ); + return { + transform: [{ translateX }], + opacity, + }; + }); + + const descriptionStyle = useAnimatedStyle(() => { + const translateX = interpolate( + scrollX.value, + inputRange, + [width * 0.7, 0, -width * 0.7], + Extrapolation.CLAMP + ); + const opacity = interpolate( + scrollX.value, + inputRange, + [0, 1, 0], + Extrapolation.CLAMP + ); + return { + transform: [{ translateX }], + opacity, + }; + }); + + return ( + + + + {item.title} + + + + {item.subtitle} + + + + {item.description} + + + + ); +}; + const OnboardingScreen = () => { const { currentTheme } = useTheme(); const navigation = useNavigation>(); const [currentIndex, setCurrentIndex] = useState(0); - const flatListRef = useRef(null); - const progressValue = useSharedValue(0); + const flatListRef = useRef>(null); const scrollX = useSharedValue(0); - const currentSlide = onboardingData[currentIndex]; - // Update progress when index changes - React.useEffect(() => { - progressValue.value = withSpring( - (currentIndex + 1) / onboardingData.length, - SPRING_CONFIG - ); - }, [currentIndex]); + const updateIndex = (index: number) => { + setCurrentIndex(index); + }; const onScroll = useAnimatedScrollHandler({ onScroll: (event) => { scrollX.value = event.contentOffset.x; }, + onMomentumEnd: (event) => { + const slideIndex = Math.round(event.contentOffset.x / width); + runOnJS(updateIndex)(slideIndex); + }, }); - const animatedProgressStyle = useAnimatedStyle(() => ({ - width: `${progressValue.value * 100}%`, - })); + const progressStyle = useAnimatedStyle(() => { + const progress = interpolate( + scrollX.value, + [0, (onboardingData.length - 1) * width], + [0, 100], + Extrapolation.CLAMP + ); + return { + width: `${progress}%`, + }; + }); const handleNext = () => { if (currentIndex < onboardingData.length - 1) { const nextIndex = currentIndex + 1; - setCurrentIndex(nextIndex); - flatListRef.current?.scrollToIndex({ index: nextIndex, animated: true }); - progressValue.value = (nextIndex + 1) / onboardingData.length; + flatListRef.current?.scrollToOffset({ + offset: nextIndex * width, + animated: true + }); } else { handleGetStarted(); } }; const handleSkip = () => { - // Skip login: proceed to app and show a one-time hint toast (async () => { try { await mmkvStorage.setItem('hasCompletedOnboarding', 'true'); await mmkvStorage.setItem('showLoginHintToastOnce', 'true'); - } catch {} + } catch { } navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] }); })(); }; @@ -331,7 +220,6 @@ const OnboardingScreen = () => { const handleGetStarted = async () => { try { await mmkvStorage.setItem('hasCompletedOnboarding', 'true'); - // After onboarding, go directly to main app navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] }); } catch (error) { if (__DEV__) console.error('Error saving onboarding status:', error); @@ -339,120 +227,71 @@ const OnboardingScreen = () => { } }; - const renderSlide = ({ item, index }: { item: OnboardingSlide; index: number }) => { - const isActive = index === currentIndex; - - return ( - - ); - }; - - const renderPaginationDot = (index: number) => { - const scale = useSharedValue(index === currentIndex ? 1 : 0.8); - const opacity = useSharedValue(index === currentIndex ? 1 : 0.4); - - React.useEffect(() => { - scale.value = withSpring( - index === currentIndex ? 1.3 : 0.8, - SPRING_CONFIG - ); - opacity.value = withTiming( - index === currentIndex ? 1 : 0.4, - SLIDE_TIMING - ); - }, [currentIndex, index]); - - const animatedStyle = useAnimatedStyle(() => ({ - transform: [{ scale: scale.value }], - opacity: opacity.value, - })); - - return ( - - ); - }; - - const renderPagination = () => ( - - {onboardingData.map((_, index) => renderPaginationDot(index))} - + const renderSlide = ({ item, index }: { item: OnboardingSlide; index: number }) => ( + ); - // Background slide styles - const getBackgroundSlideStyle = (index: number) => { - 'worklet'; - return useAnimatedStyle(() => { + // Animated pagination dots + const PaginationDot = ({ index }: { index: number }) => { + const dotStyle = useAnimatedStyle(() => { const inputRange = [(index - 1) * width, index * width, (index + 1) * width]; - const slideOpacity = interpolate( + const dotWidth = interpolate( scrollX.value, inputRange, - [0, 1, 0], + [8, 32, 8], Extrapolation.CLAMP ); - - return { opacity: slideOpacity }; + const opacity = interpolate( + scrollX.value, + inputRange, + [0.3, 1, 0.3], + Extrapolation.CLAMP + ); + return { + width: dotWidth, + opacity, + }; }); + + return ; + }; + + // Animated button + const buttonScale = useSharedValue(1); + + const buttonStyle = useAnimatedStyle(() => ({ + transform: [{ scale: buttonScale.value }], + })); + + const handlePressIn = () => { + buttonScale.value = withSpring(0.95, { damping: 15, stiffness: 400 }); + }; + + const handlePressOut = () => { + buttonScale.value = withSpring(1, { damping: 15, stiffness: 400 }); }; return ( - {/* Animated gradient background that transitions between slides */} - - {onboardingData.map((slide, index) => ( - - - - - ))} - - - + - {/* Content container with status bar padding */} {/* Header */} - + - - Skip - + Skip - {/* Progress Bar */} - - + {/* Smooth Progress Bar */} + + - + - {/* Content */} + {/* Slides */} { keyExtractor={(item) => item.id} onScroll={onScroll} scrollEventThrottle={16} - onMomentumScrollEnd={(event) => { - const slideIndex = Math.round(event.nativeEvent.contentOffset.x / width); - setCurrentIndex(slideIndex); - }} + decelerationRate="fast" + snapToInterval={width} + snapToAlignment="start" + bounces={false} style={{ flex: 1 }} /> {/* Footer */} - - {renderPagination()} - - - + + {/* Smooth Pagination */} + + {onboardingData.map((_, index) => ( + + ))} - + + {/* Animated Button */} + + + + {currentIndex === onboardingData.length - 1 ? 'Get Started' : 'Continue'} + + + + ); @@ -491,146 +343,100 @@ const OnboardingScreen = () => { const styles = StyleSheet.create({ container: { flex: 1, + backgroundColor: '#0A0A0A', + }, + fullScreenContainer: { + flex: 1, + paddingTop: Platform.OS === 'ios' ? 60 : (StatusBar.currentHeight || 24) + 16, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - paddingHorizontal: 20, - paddingTop: 10, + paddingHorizontal: 24, paddingBottom: 20, }, skipButton: { - padding: 10, + paddingVertical: 8, + paddingHorizontal: 4, }, skipText: { - fontSize: 16, + fontSize: 15, fontWeight: '500', + color: 'rgba(255, 255, 255, 0.4)', }, progressContainer: { flex: 1, - height: 4, - borderRadius: 2, - marginHorizontal: 20, + height: 3, + borderRadius: 1.5, + marginLeft: 24, + backgroundColor: 'rgba(255, 255, 255, 0.08)', overflow: 'hidden', }, progressBar: { height: '100%', - borderRadius: 2, + borderRadius: 1.5, + backgroundColor: '#FFFFFF', }, slide: { width, - height: '100%', - alignItems: 'center', - justifyContent: 'center', - paddingHorizontal: 40, - }, - fullScreenContainer: { flex: 1, - paddingTop: Platform.OS === 'ios' ? 44 : StatusBar.currentHeight || 24, - }, - glowContainer: { - position: 'absolute', - width: 200, - height: 200, - borderRadius: 100, - alignItems: 'center', justifyContent: 'center', - top: '35%', - }, - glowCircle: { - width: '100%', - height: '100%', - borderRadius: 100, - opacity: 0.4, - }, - iconContainer: { - width: 180, - height: 180, - borderRadius: 90, - alignItems: 'center', - justifyContent: 'center', - marginBottom: 60, - shadowColor: '#000', - shadowOffset: { - width: 0, - height: 15, - }, - shadowOpacity: 0.5, - shadowRadius: 25, - elevation: 20, - }, - iconWrapper: { - alignItems: 'center', - justifyContent: 'center', - zIndex: 10, + paddingHorizontal: 32, }, textContainer: { - alignItems: 'center', - paddingHorizontal: 20, + alignItems: 'flex-start', }, title: { - fontSize: 32, - fontWeight: 'bold', - textAlign: 'center', - marginBottom: 12, + fontSize: 52, + fontWeight: '800', + letterSpacing: -2, + lineHeight: 56, + marginBottom: 16, + color: '#FFFFFF', }, subtitle: { - fontSize: 20, + fontSize: 17, fontWeight: '600', - textAlign: 'center', + color: 'rgba(255, 255, 255, 0.6)', marginBottom: 20, + letterSpacing: 0.3, }, description: { - fontSize: 16, - textAlign: 'center', + fontSize: 15, lineHeight: 24, + color: 'rgba(255, 255, 255, 0.4)', maxWidth: 300, }, footer: { - paddingHorizontal: 20, - paddingBottom: Platform.OS === 'ios' ? 40 : 20, + paddingHorizontal: 24, + paddingBottom: Platform.OS === 'ios' ? 50 : 32, }, pagination: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', - marginBottom: 40, + marginBottom: 32, + gap: 6, }, paginationDot: { - width: 10, - height: 10, - borderRadius: 5, - marginHorizontal: 6, - }, - buttonContainer: { - alignItems: 'center', + height: 8, + borderRadius: 4, + backgroundColor: '#FFFFFF', }, button: { - borderRadius: 30, - paddingVertical: 16, - paddingHorizontal: 32, - minWidth: 160, + backgroundColor: '#FFFFFF', + borderRadius: 16, + paddingVertical: 18, alignItems: 'center', justifyContent: 'center', - flexDirection: 'row', - shadowColor: '#000', - shadowOffset: { - width: 0, - height: 4, - }, - shadowOpacity: 0.3, - shadowRadius: 8, - elevation: 8, }, - nextButton: {}, buttonText: { fontSize: 16, - fontWeight: '600', - }, - buttonIcon: { - marginLeft: 8, + fontWeight: '700', + color: '#0A0A0A', + letterSpacing: 0.3, }, }); -export default OnboardingScreen; \ No newline at end of file +export default OnboardingScreen; \ No newline at end of file