mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
some customizations added
This commit is contained in:
parent
3653879596
commit
f642f3f9b5
3 changed files with 71 additions and 23 deletions
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue