some customizations added

This commit is contained in:
tapframe 2025-09-04 19:49:40 +05:30
parent 3653879596
commit f642f3f9b5
3 changed files with 71 additions and 23 deletions

View file

@ -1,6 +1,6 @@
import React, { useMemo, useState, useEffect, useCallback, memo } from 'react'; import React, { useMemo, useState, useEffect, useCallback, memo } from 'react';
import { View, Text, StyleSheet, Dimensions, TouchableOpacity, ViewStyle, TextStyle, ImageStyle, FlatList, StyleProp } from 'react-native'; import { View, Text, StyleSheet, Dimensions, TouchableOpacity, ViewStyle, TextStyle, ImageStyle, FlatList, StyleProp } from 'react-native';
import Animated, { FadeIn, FadeOut, Easing, useSharedValue, withTiming, useAnimatedStyle } from 'react-native-reanimated'; import Animated, { FadeIn, FadeOut, Easing, useSharedValue, withTiming, useAnimatedStyle, useAnimatedScrollHandler, useAnimatedReaction, runOnJS } from 'react-native-reanimated';
import { LinearGradient } from 'expo-linear-gradient'; import { LinearGradient } from 'expo-linear-gradient';
import { Image as ExpoImage } from 'expo-image'; import { Image as ExpoImage } from 'expo-image';
import { MaterialIcons } from '@expo/vector-icons'; import { MaterialIcons } from '@expo/vector-icons';
@ -10,6 +10,7 @@ import { RootStackParamList } from '../../navigation/AppNavigator';
import { StreamingContent } from '../../services/catalogService'; import { StreamingContent } from '../../services/catalogService';
import { useTheme } from '../../contexts/ThemeContext'; import { useTheme } from '../../contexts/ThemeContext';
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useSettings } from '../../hooks/useSettings';
interface HeroCarouselProps { interface HeroCarouselProps {
items: StreamingContent[]; items: StreamingContent[];
@ -25,6 +26,7 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
const navigation = useNavigation<NavigationProp<RootStackParamList>>(); const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const { currentTheme } = useTheme(); const { currentTheme } = useTheme();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const { settings } = useSettings();
const data = useMemo(() => (items && items.length ? items.slice(0, 10) : []), [items]); const data = useMemo(() => (items && items.length ? items.slice(0, 10) : []), [items]);
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(0);
@ -34,18 +36,29 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
const hasData = data.length > 0; const hasData = data.length > 0;
const handleMomentumEnd = useCallback((event: any) => { // Optimized: update background as soon as scroll starts, without waiting for momentum end
const offsetX = event?.nativeEvent?.contentOffset?.x ?? 0; const scrollX = useSharedValue(0);
const interval = CARD_WIDTH + 16; const interval = CARD_WIDTH + 16;
const idx = Math.round(offsetX / interval); const scrollHandler = useAnimatedScrollHandler({
const clamped = Math.max(0, Math.min(idx, data.length - 1)); onScroll: (event) => {
if (clamped !== activeIndex) { scrollX.value = event.contentOffset.x;
// Small delay to ensure smooth transition },
setTimeout(() => { });
setActiveIndex(clamped);
}, 50); // Derive the index reactively and only set state when it changes
} useAnimatedReaction(
}, [activeIndex, data.length]); () => {
const idx = Math.round(scrollX.value / interval);
return idx;
},
(idx, prevIdx) => {
if (idx == null || idx === prevIdx) return;
// Clamp to bounds to avoid out-of-range access
const clamped = Math.max(0, Math.min(idx, data.length - 1));
runOnJS(setActiveIndex)(clamped);
},
[data.length]
);
const contentPadding = useMemo(() => ({ paddingHorizontal: (width - CARD_WIDTH) / 2 }), []); const contentPadding = useMemo(() => ({ paddingHorizontal: (width - CARD_WIDTH) / 2 }), []);
@ -164,7 +177,7 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
return ( return (
<Animated.View entering={FadeIn.duration(350).easing(Easing.out(Easing.cubic))}> <Animated.View entering={FadeIn.duration(350).easing(Easing.out(Easing.cubic))}>
<View style={styles.container as ViewStyle}> <View style={styles.container as ViewStyle}>
{data.length > 0 && ( {settings.enableHomeHeroBackground && data.length > 0 && (
<View style={{ height: 0, width: 0, overflow: 'hidden' }}> <View style={{ height: 0, width: 0, overflow: 'hidden' }}>
{data[activeIndex + 1] && ( {data[activeIndex + 1] && (
<ExpoImage <ExpoImage
@ -186,20 +199,22 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
)} )}
</View> </View>
)} )}
{data[activeIndex] && ( {settings.enableHomeHeroBackground && data[activeIndex] && (
<BackgroundImage <BackgroundImage
item={data[activeIndex]} item={data[activeIndex]}
insets={insets} insets={insets}
/> />
)} )}
{/* Bottom blend to HomeScreen background (not the card) */} {/* Bottom blend to HomeScreen background (not the card) */}
<LinearGradient {settings.enableHomeHeroBackground && (
colors={["transparent", currentTheme.colors.darkBackground]} <LinearGradient
locations={[0, 1]} colors={["transparent", currentTheme.colors.darkBackground]}
style={styles.bottomBlend as ViewStyle} locations={[0, 1]}
pointerEvents="none" style={styles.bottomBlend as ViewStyle}
/> pointerEvents="none"
<FlatList />
)}
<Animated.FlatList
data={data} data={data}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
horizontal horizontal
@ -207,7 +222,8 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
snapToInterval={CARD_WIDTH + 16} snapToInterval={CARD_WIDTH + 16}
decelerationRate="fast" decelerationRate="fast"
contentContainerStyle={contentPadding} contentContainerStyle={contentPadding}
onMomentumScrollEnd={handleMomentumEnd} onScroll={scrollHandler}
scrollEventThrottle={16}
initialNumToRender={3} initialNumToRender={3}
windowSize={3} windowSize={3}
maxToRenderPerBatch={3} maxToRenderPerBatch={3}

View file

@ -67,6 +67,8 @@ export interface AppSettings {
postersPerRow: number; // 3-6 range for number of posters per row postersPerRow: number; // 3-6 range for number of posters per row
// Home screen content item // Home screen content item
showPosterTitles: boolean; // Show text titles under posters showPosterTitles: boolean; // Show text titles under posters
// Home screen background behavior
enableHomeHeroBackground: boolean; // Enable dynamic hero background on Home
// Trailer settings // Trailer settings
showTrailers: boolean; // Enable/disable trailer playback in hero section showTrailers: boolean; // Enable/disable trailer playback in hero section
trailerMuted: boolean; // Default to muted for better user experience trailerMuted: boolean; // Default to muted for better user experience
@ -110,6 +112,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
posterBorderRadius: 12, posterBorderRadius: 12,
postersPerRow: 4, postersPerRow: 4,
showPosterTitles: true, showPosterTitles: true,
enableHomeHeroBackground: true,
// Trailer settings // Trailer settings
showTrailers: true, // Enable trailers by default showTrailers: true, // Enable trailers by default
trailerMuted: true, // Default to muted for better user experience trailerMuted: true, // Default to muted for better user experience

View file

@ -358,6 +358,25 @@ const HomeScreenSettings: React.FC = () => {
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>
{settings.heroStyle === 'carousel' && (
<SettingsCard isDarkMode={isDarkMode} colors={colors}>
<SettingItem
title="Dynamic Hero Background"
description="Blurred banner behind carousel"
icon="wallpaper"
isDarkMode={isDarkMode}
colors={colors}
renderControl={() => (
<CustomSwitch
value={settings.enableHomeHeroBackground}
onValueChange={(value) => handleUpdateSetting('enableHomeHeroBackground', value)}
/>
)}
/>
<Text style={[styles.settingInlineNote, { color: isDarkMode ? colors.mediumEmphasis : colors.textMutedDark }]}>May impact performance on low-end devices.</Text>
</SettingsCard>
)}
</> </>
)} )}
@ -489,6 +508,16 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
paddingLeft: 12, paddingLeft: 12,
}, },
settingInlineNote: {
fontSize: 12,
opacity: 0.7,
marginTop: 8,
marginBottom: 8,
textAlign: 'center',
alignSelf: 'center',
width: '100%',
paddingHorizontal: 16,
},
radioCardContainer: { radioCardContainer: {
marginHorizontal: 16, marginHorizontal: 16,
marginVertical: 8, marginVertical: 8,