mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
feat: added new poster like layout for continue watching card
This commit is contained in:
parent
ab7f008bbb
commit
edeb6ebe3c
3 changed files with 448 additions and 83 deletions
|
|
@ -1002,8 +1002,128 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
setAlertVisible(true);
|
||||
}, [currentTheme.colors.error]);
|
||||
|
||||
// Memoized render function for continue watching items
|
||||
const renderContinueWatchingItem = useCallback(({ item }: { item: ContinueWatchingItem }) => (
|
||||
// Compute poster dimensions for poster-style cards
|
||||
const computedPosterWidth = useMemo(() => {
|
||||
switch (deviceType) {
|
||||
case 'tv':
|
||||
return 180;
|
||||
case 'largeTablet':
|
||||
return 160;
|
||||
case 'tablet':
|
||||
return 140;
|
||||
default:
|
||||
return 120;
|
||||
}
|
||||
}, [deviceType]);
|
||||
|
||||
const computedPosterHeight = useMemo(() => {
|
||||
return computedPosterWidth * 1.5; // 2:3 aspect ratio
|
||||
}, [computedPosterWidth]);
|
||||
|
||||
// Memoized render function for poster-style continue watching items
|
||||
const renderPosterStyleItem = useCallback(({ item }: { item: ContinueWatchingItem }) => (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.posterContentItem,
|
||||
{
|
||||
width: computedPosterWidth,
|
||||
}
|
||||
]}
|
||||
activeOpacity={0.8}
|
||||
onPress={() => handleContentPress(item)}
|
||||
onLongPress={() => handleLongPress(item)}
|
||||
delayLongPress={800}
|
||||
>
|
||||
{/* Poster Image */}
|
||||
<View style={[
|
||||
styles.posterImageContainer,
|
||||
{
|
||||
height: computedPosterHeight,
|
||||
borderRadius: settings.posterBorderRadius ?? 12,
|
||||
}
|
||||
]}>
|
||||
<FastImage
|
||||
source={{
|
||||
uri: item.poster || 'https://via.placeholder.com/300x450',
|
||||
priority: FastImage.priority.high,
|
||||
cache: FastImage.cacheControl.immutable
|
||||
}}
|
||||
style={[styles.posterImage, { borderRadius: settings.posterBorderRadius ?? 12 }]}
|
||||
resizeMode={FastImage.resizeMode.cover}
|
||||
/>
|
||||
|
||||
{/* Gradient overlay */}
|
||||
<LinearGradient
|
||||
colors={['transparent', 'rgba(0,0,0,0.8)']}
|
||||
style={[styles.posterGradient, { borderRadius: settings.posterBorderRadius ?? 12 }]}
|
||||
/>
|
||||
|
||||
{/* Episode Info Overlay */}
|
||||
{item.type === 'series' && item.season && item.episode && (
|
||||
<View style={styles.posterEpisodeOverlay}>
|
||||
<Text style={[styles.posterEpisodeText, { fontSize: isTV ? 14 : isLargeTablet ? 13 : 12 }]}>
|
||||
S{item.season} E{item.episode}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Up Next Badge */}
|
||||
{item.type === 'series' && item.progress === 0 && (
|
||||
<View style={[styles.posterUpNextBadge, { backgroundColor: currentTheme.colors.primary }]}>
|
||||
<Text style={[styles.posterUpNextText, { fontSize: isTV ? 12 : 10 }]}>UP NEXT</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Progress Bar */}
|
||||
{item.progress > 0 && (
|
||||
<View style={styles.posterProgressContainer}>
|
||||
<View style={[styles.posterProgressTrack, { backgroundColor: 'rgba(255,255,255,0.3)' }]}>
|
||||
<View
|
||||
style={[
|
||||
styles.posterProgressBar,
|
||||
{
|
||||
width: `${item.progress}%`,
|
||||
backgroundColor: currentTheme.colors.primary
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Delete Indicator Overlay */}
|
||||
{deletingItemId === item.id && (
|
||||
<View style={[styles.deletingOverlay, { borderRadius: settings.posterBorderRadius ?? 12 }]}>
|
||||
<ActivityIndicator size="large" color="#FFFFFF" />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Title below poster */}
|
||||
<View style={styles.posterTitleContainer}>
|
||||
<Text
|
||||
style={[
|
||||
styles.posterTitle,
|
||||
{
|
||||
color: currentTheme.colors.highEmphasis,
|
||||
fontSize: isTV ? 16 : isLargeTablet ? 15 : 14
|
||||
}
|
||||
]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
{item.progress > 0 && (
|
||||
<Text style={[styles.posterProgressLabel, { color: currentTheme.colors.textMuted, fontSize: isTV ? 13 : 11 }]}>
|
||||
{Math.round(item.progress)}%
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
), [currentTheme.colors, handleContentPress, handleLongPress, deletingItemId, computedPosterWidth, computedPosterHeight, isTV, isLargeTablet, settings.posterBorderRadius]);
|
||||
|
||||
// Memoized render function for wide-style continue watching items
|
||||
const renderWideStyleItem = useCallback(({ item }: { item: ContinueWatchingItem }) => (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.wideContentItem,
|
||||
|
|
@ -1165,7 +1285,15 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
|||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
), [currentTheme.colors, handleContentPress, handleLongPress, deletingItemId, computedItemWidth, computedItemHeight, isTV, isLargeTablet, isTablet]);
|
||||
), [currentTheme.colors, handleContentPress, handleLongPress, deletingItemId, computedItemWidth, computedItemHeight, isTV, isLargeTablet, isTablet, settings.posterBorderRadius]);
|
||||
|
||||
// Choose the appropriate render function based on settings
|
||||
const renderContinueWatchingItem = useCallback(({ item }: { item: ContinueWatchingItem }) => {
|
||||
if (settings.continueWatchingCardStyle === 'poster') {
|
||||
return renderPosterStyleItem({ item });
|
||||
}
|
||||
return renderWideStyleItem({ item });
|
||||
}, [settings.continueWatchingCardStyle, renderPosterStyleItem, renderWideStyleItem]);
|
||||
|
||||
// Memoized key extractor
|
||||
const keyExtractor = useCallback((item: ContinueWatchingItem) => `continue-${item.id}-${item.type}`, []);
|
||||
|
|
@ -1421,6 +1549,87 @@ const styles = StyleSheet.create({
|
|||
progressBar: {
|
||||
height: '100%',
|
||||
},
|
||||
// Poster-style card styles
|
||||
posterContentItem: {
|
||||
overflow: 'visible',
|
||||
},
|
||||
posterImageContainer: {
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
borderWidth: 1.5,
|
||||
borderColor: 'rgba(255,255,255,0.15)',
|
||||
elevation: 1,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 1,
|
||||
},
|
||||
posterImage: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
posterGradient: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '50%',
|
||||
},
|
||||
posterEpisodeOverlay: {
|
||||
position: 'absolute',
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 4,
|
||||
},
|
||||
posterEpisodeText: {
|
||||
color: '#FFFFFF',
|
||||
fontWeight: '600',
|
||||
},
|
||||
posterUpNextBadge: {
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
borderRadius: 4,
|
||||
},
|
||||
posterUpNextText: {
|
||||
color: '#FFFFFF',
|
||||
fontWeight: '700',
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
posterProgressContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
posterProgressTrack: {
|
||||
height: 4,
|
||||
},
|
||||
posterProgressBar: {
|
||||
height: '100%',
|
||||
},
|
||||
posterTitleContainer: {
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
posterTitle: {
|
||||
fontWeight: '600',
|
||||
flex: 1,
|
||||
lineHeight: 18,
|
||||
},
|
||||
posterProgressLabel: {
|
||||
fontWeight: '500',
|
||||
marginLeft: 6,
|
||||
},
|
||||
});
|
||||
|
||||
export default React.memo(ContinueWatchingSection, (prevProps, nextProps) => {
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ export interface AppSettings {
|
|||
useCachedStreams: boolean; // Enable/disable direct player navigation from Continue Watching cache
|
||||
openMetadataScreenWhenCacheDisabled: boolean; // When cache disabled, open MetadataScreen instead of StreamsScreen
|
||||
streamCacheTTL: number; // Stream cache duration in milliseconds (default: 1 hour)
|
||||
continueWatchingCardStyle: 'wide' | 'poster'; // Card style: 'wide' (horizontal) or 'poster' (vertical)
|
||||
enableStreamsBackdrop: boolean; // Enable blurred backdrop background on StreamsScreen mobile
|
||||
useExternalPlayerForDownloads: boolean; // Enable/disable external player for downloaded content
|
||||
// Android MPV player settings
|
||||
|
|
@ -186,6 +187,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
|||
useCachedStreams: false, // Enable by default
|
||||
openMetadataScreenWhenCacheDisabled: true, // Default to StreamsScreen when cache disabled
|
||||
streamCacheTTL: 60 * 60 * 1000, // Default: 1 hour in milliseconds
|
||||
continueWatchingCardStyle: 'wide', // Default to wide (horizontal) card style
|
||||
enableStreamsBackdrop: true, // Enable by default (new behavior)
|
||||
// Android MPV player settings
|
||||
videoPlayerEngine: 'auto', // Default to auto (ExoPlayer primary, MPV fallback)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const ContinueWatchingSettingsScreen: React.FC = () => {
|
|||
if (Platform.OS === 'ios') {
|
||||
StatusBar.setHidden(false);
|
||||
}
|
||||
} catch {}
|
||||
} catch { }
|
||||
}, [colors.darkBackground]);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
|
|
@ -97,22 +97,22 @@ const ContinueWatchingSettingsScreen: React.FC = () => {
|
|||
/>
|
||||
);
|
||||
|
||||
const SettingItem = ({
|
||||
title,
|
||||
description,
|
||||
value,
|
||||
onValueChange,
|
||||
isLast = false
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
value: boolean;
|
||||
const SettingItem = ({
|
||||
title,
|
||||
description,
|
||||
value,
|
||||
onValueChange,
|
||||
isLast = false
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
value: boolean;
|
||||
onValueChange: (value: boolean) => void;
|
||||
isLast?: boolean;
|
||||
}) => (
|
||||
<View style={[
|
||||
styles.settingItem,
|
||||
{
|
||||
{
|
||||
borderBottomColor: isLast ? 'transparent' : colors.border,
|
||||
}
|
||||
]}>
|
||||
|
|
@ -159,10 +159,10 @@ const ContinueWatchingSettingsScreen: React.FC = () => {
|
|||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: colors.darkBackground }]}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
|
||||
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity
|
||||
<TouchableOpacity
|
||||
style={styles.backButton}
|
||||
onPress={handleBack}
|
||||
>
|
||||
|
|
@ -170,13 +170,13 @@ const ContinueWatchingSettingsScreen: React.FC = () => {
|
|||
<Text style={styles.backText}>Settings</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
|
||||
<Text style={styles.headerTitle}>
|
||||
Continue Watching
|
||||
</Text>
|
||||
|
||||
{/* Content */}
|
||||
<ScrollView
|
||||
<ScrollView
|
||||
style={styles.content}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
|
|
@ -184,22 +184,98 @@ const ContinueWatchingSettingsScreen: React.FC = () => {
|
|||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>PLAYBACK BEHAVIOR</Text>
|
||||
<View style={styles.settingsCard}>
|
||||
<SettingItem
|
||||
title="Use Cached Streams"
|
||||
description="When enabled, clicking Continue Watching items will open the player directly using previously played streams. When disabled, opens a content screen instead."
|
||||
value={settings.useCachedStreams}
|
||||
onValueChange={(value) => handleUpdateSetting('useCachedStreams', value)}
|
||||
isLast={!settings.useCachedStreams}
|
||||
/>
|
||||
{!settings.useCachedStreams && (
|
||||
<SettingItem
|
||||
title="Open Metadata Screen"
|
||||
description="When cached streams are disabled, open the Metadata screen instead of the Streams screen. This shows content details and allows manual stream selection."
|
||||
value={settings.openMetadataScreenWhenCacheDisabled}
|
||||
onValueChange={(value) => handleUpdateSetting('openMetadataScreenWhenCacheDisabled', value)}
|
||||
isLast={true}
|
||||
title="Use Cached Streams"
|
||||
description="When enabled, clicking Continue Watching items will open the player directly using previously played streams. When disabled, opens a content screen instead."
|
||||
value={settings.useCachedStreams}
|
||||
onValueChange={(value) => handleUpdateSetting('useCachedStreams', value)}
|
||||
isLast={!settings.useCachedStreams}
|
||||
/>
|
||||
)}
|
||||
{!settings.useCachedStreams && (
|
||||
<SettingItem
|
||||
title="Open Metadata Screen"
|
||||
description="When cached streams are disabled, open the Metadata screen instead of the Streams screen. This shows content details and allows manual stream selection."
|
||||
value={settings.openMetadataScreenWhenCacheDisabled}
|
||||
onValueChange={(value) => handleUpdateSetting('openMetadataScreenWhenCacheDisabled', value)}
|
||||
isLast={true}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Card Appearance Section */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>CARD APPEARANCE</Text>
|
||||
<View style={styles.settingsCard}>
|
||||
<View style={[styles.settingItem, { borderBottomWidth: 0, flexDirection: 'column', alignItems: 'flex-start' }]}>
|
||||
<Text style={[styles.settingTitle, { color: colors.highEmphasis, marginBottom: 8 }]}>
|
||||
Card Style
|
||||
</Text>
|
||||
<Text style={[styles.settingDescription, { color: colors.mediumEmphasis, marginBottom: 16 }]}>
|
||||
Choose how Continue Watching items appear on the home screen
|
||||
</Text>
|
||||
<View style={styles.cardStyleOptionsContainer}>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.cardStyleOption,
|
||||
{
|
||||
backgroundColor: settings.continueWatchingCardStyle === 'wide' ? colors.primary : colors.elevation1,
|
||||
borderColor: settings.continueWatchingCardStyle === 'wide' ? colors.primary : colors.border,
|
||||
}
|
||||
]}
|
||||
onPress={() => handleUpdateSetting('continueWatchingCardStyle', 'wide')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.cardPreviewWide}>
|
||||
<View style={[styles.cardPreviewImage, { backgroundColor: colors.mediumGray }]} />
|
||||
<View style={styles.cardPreviewContent}>
|
||||
<View style={[styles.cardPreviewLine, { backgroundColor: colors.highEmphasis, width: '70%' }]} />
|
||||
<View style={[styles.cardPreviewLine, { backgroundColor: colors.mediumEmphasis, width: '50%', height: 6 }]} />
|
||||
<View style={[styles.cardPreviewProgress, { backgroundColor: colors.elevation2 }]}>
|
||||
<View style={[styles.cardPreviewProgressFill, { backgroundColor: colors.primary, width: '60%' }]} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={[
|
||||
styles.cardStyleLabel,
|
||||
{ color: settings.continueWatchingCardStyle === 'wide' ? colors.white : colors.highEmphasis }
|
||||
]}>
|
||||
Wide
|
||||
</Text>
|
||||
{settings.continueWatchingCardStyle === 'wide' && (
|
||||
<MaterialIcons name="check-circle" size={18} color={colors.white} style={styles.cardStyleCheck} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.cardStyleOption,
|
||||
{
|
||||
backgroundColor: settings.continueWatchingCardStyle === 'poster' ? colors.primary : colors.elevation1,
|
||||
borderColor: settings.continueWatchingCardStyle === 'poster' ? colors.primary : colors.border,
|
||||
}
|
||||
]}
|
||||
onPress={() => handleUpdateSetting('continueWatchingCardStyle', 'poster')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={styles.cardPreviewPoster}>
|
||||
<View style={[styles.cardPreviewPosterImage, { backgroundColor: colors.mediumGray }]} />
|
||||
<View style={[styles.cardPreviewPosterProgress, { backgroundColor: colors.elevation2 }]}>
|
||||
<View style={[styles.cardPreviewProgressFill, { backgroundColor: colors.primary, width: '45%' }]} />
|
||||
</View>
|
||||
</View>
|
||||
<Text style={[
|
||||
styles.cardStyleLabel,
|
||||
{ color: settings.continueWatchingCardStyle === 'poster' ? colors.white : colors.highEmphasis }
|
||||
]}>
|
||||
Poster
|
||||
</Text>
|
||||
{settings.continueWatchingCardStyle === 'poster' && (
|
||||
<MaterialIcons name="check-circle" size={18} color={colors.white} style={styles.cardStyleCheck} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
|
@ -207,80 +283,80 @@ const ContinueWatchingSettingsScreen: React.FC = () => {
|
|||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>CACHE SETTINGS</Text>
|
||||
<View style={styles.settingsCard}>
|
||||
<View style={[styles.settingItem, { borderBottomWidth: 0, flexDirection: 'column', alignItems: 'flex-start' }]}>
|
||||
<Text style={[styles.settingTitle, { color: colors.highEmphasis, marginBottom: 8 }]}>
|
||||
Stream Cache Duration
|
||||
</Text>
|
||||
<Text style={[styles.settingDescription, { color: colors.mediumEmphasis, marginBottom: 16 }]}>
|
||||
How long to keep cached stream links before they expire
|
||||
</Text>
|
||||
<View style={styles.ttlOptionsContainer}>
|
||||
{TTL_OPTIONS.map((row, rowIndex) => (
|
||||
<View key={rowIndex} style={styles.ttlRow}>
|
||||
{row.map((option) => (
|
||||
<TTLPickerItem key={option.value} option={option} />
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
<View style={[styles.settingItem, { borderBottomWidth: 0, flexDirection: 'column', alignItems: 'flex-start' }]}>
|
||||
<Text style={[styles.settingTitle, { color: colors.highEmphasis, marginBottom: 8 }]}>
|
||||
Stream Cache Duration
|
||||
</Text>
|
||||
<Text style={[styles.settingDescription, { color: colors.mediumEmphasis, marginBottom: 16 }]}>
|
||||
How long to keep cached stream links before they expire
|
||||
</Text>
|
||||
<View style={styles.ttlOptionsContainer}>
|
||||
{TTL_OPTIONS.map((row, rowIndex) => (
|
||||
<View key={rowIndex} style={styles.ttlRow}>
|
||||
{row.map((option) => (
|
||||
<TTLPickerItem key={option.value} option={option} />
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{settings.useCachedStreams && (
|
||||
<View style={styles.section}>
|
||||
<View style={[styles.warningCard, { borderColor: colors.warning }]}>
|
||||
<View style={styles.warningHeader}>
|
||||
<MaterialIcons name="warning" size={20} color={colors.warning} />
|
||||
<Text style={[styles.warningTitle, { color: colors.warning }]}>
|
||||
Important Note
|
||||
<View style={styles.warningHeader}>
|
||||
<MaterialIcons name="warning" size={20} color={colors.warning} />
|
||||
<Text style={[styles.warningTitle, { color: colors.warning }]}>
|
||||
Important Note
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={[styles.warningText, { color: colors.mediumEmphasis }]}>
|
||||
Not all stream links may remain active for the full cache duration. Longer cache times may result in expired links. If a cached link fails, the app will fall back to fetching fresh streams.
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={[styles.warningText, { color: colors.mediumEmphasis }]}>
|
||||
Not all stream links may remain active for the full cache duration. Longer cache times may result in expired links. If a cached link fails, the app will fall back to fetching fresh streams.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.section}>
|
||||
<View style={styles.infoCard}>
|
||||
<View style={styles.infoHeader}>
|
||||
<MaterialIcons name="info" size={20} color={colors.primary} />
|
||||
<Text style={[styles.infoTitle, { color: colors.highEmphasis }]}>
|
||||
How it works
|
||||
<View style={styles.infoHeader}>
|
||||
<MaterialIcons name="info" size={20} color={colors.primary} />
|
||||
<Text style={[styles.infoTitle, { color: colors.highEmphasis }]}>
|
||||
How it works
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={[styles.infoText, { color: colors.mediumEmphasis }]}>
|
||||
{settings.useCachedStreams ? (
|
||||
<>
|
||||
• Streams are cached for your selected duration after playing{'\n'}
|
||||
• Cached streams are validated before use{'\n'}
|
||||
• If cache is invalid or expired, falls back to content screen{'\n'}
|
||||
• "Use Cached Streams" controls direct player vs screen navigation{'\n'}
|
||||
• "Open Metadata Screen" appears only when cached streams are disabled
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
• When cached streams are disabled, clicking Continue Watching items opens content screens{'\n'}
|
||||
• "Open Metadata Screen" option controls which screen to open{'\n'}
|
||||
• Metadata screen shows content details and allows manual stream selection{'\n'}
|
||||
• Streams screen shows available streams for immediate playback
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={[styles.infoText, { color: colors.mediumEmphasis }]}>
|
||||
{settings.useCachedStreams ? (
|
||||
<>
|
||||
• Streams are cached for your selected duration after playing{'\n'}
|
||||
• Cached streams are validated before use{'\n'}
|
||||
• If cache is invalid or expired, falls back to content screen{'\n'}
|
||||
• "Use Cached Streams" controls direct player vs screen navigation{'\n'}
|
||||
• "Open Metadata Screen" appears only when cached streams are disabled
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
• When cached streams are disabled, clicking Continue Watching items opens content screens{'\n'}
|
||||
• "Open Metadata Screen" option controls which screen to open{'\n'}
|
||||
• Metadata screen shows content details and allows manual stream selection{'\n'}
|
||||
• Streams screen shows available streams for immediate playback
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* Saved indicator */}
|
||||
<Animated.View
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.savedIndicator,
|
||||
{
|
||||
{
|
||||
backgroundColor: colors.primary,
|
||||
opacity: fadeAnim
|
||||
opacity: fadeAnim
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
|
@ -466,6 +542,84 @@ const createStyles = (colors: any) => StyleSheet.create({
|
|||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
},
|
||||
// Card Style Selector Styles
|
||||
cardStyleOptionsContainer: {
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
gap: 12,
|
||||
},
|
||||
cardStyleOption: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
position: 'relative',
|
||||
},
|
||||
cardPreviewWide: {
|
||||
flexDirection: 'row',
|
||||
width: 100,
|
||||
height: 60,
|
||||
borderRadius: 6,
|
||||
overflow: 'hidden',
|
||||
marginBottom: 8,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
cardPreviewImage: {
|
||||
width: 40,
|
||||
height: '100%',
|
||||
borderTopLeftRadius: 6,
|
||||
borderBottomLeftRadius: 6,
|
||||
},
|
||||
cardPreviewContent: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
cardPreviewLine: {
|
||||
height: 8,
|
||||
borderRadius: 2,
|
||||
},
|
||||
cardPreviewProgress: {
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
width: '100%',
|
||||
},
|
||||
cardPreviewProgressFill: {
|
||||
height: '100%',
|
||||
borderRadius: 2,
|
||||
},
|
||||
cardPreviewPoster: {
|
||||
width: 44,
|
||||
height: 60,
|
||||
borderRadius: 6,
|
||||
overflow: 'hidden',
|
||||
marginBottom: 8,
|
||||
position: 'relative',
|
||||
},
|
||||
cardPreviewPosterImage: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: 6,
|
||||
},
|
||||
cardPreviewPosterProgress: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 4,
|
||||
},
|
||||
cardStyleLabel: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
marginTop: 4,
|
||||
},
|
||||
cardStyleCheck: {
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
},
|
||||
});
|
||||
|
||||
export default ContinueWatchingSettingsScreen;
|
||||
|
|
|
|||
Loading…
Reference in a new issue