mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-23 01:32:11 +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);
|
setAlertVisible(true);
|
||||||
}, [currentTheme.colors.error]);
|
}, [currentTheme.colors.error]);
|
||||||
|
|
||||||
// Memoized render function for continue watching items
|
// Compute poster dimensions for poster-style cards
|
||||||
const renderContinueWatchingItem = useCallback(({ item }: { item: ContinueWatchingItem }) => (
|
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
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.wideContentItem,
|
styles.wideContentItem,
|
||||||
|
|
@ -1165,7 +1285,15 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</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
|
// Memoized key extractor
|
||||||
const keyExtractor = useCallback((item: ContinueWatchingItem) => `continue-${item.id}-${item.type}`, []);
|
const keyExtractor = useCallback((item: ContinueWatchingItem) => `continue-${item.id}-${item.type}`, []);
|
||||||
|
|
@ -1421,6 +1549,87 @@ const styles = StyleSheet.create({
|
||||||
progressBar: {
|
progressBar: {
|
||||||
height: '100%',
|
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) => {
|
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
|
useCachedStreams: boolean; // Enable/disable direct player navigation from Continue Watching cache
|
||||||
openMetadataScreenWhenCacheDisabled: boolean; // When cache disabled, open MetadataScreen instead of StreamsScreen
|
openMetadataScreenWhenCacheDisabled: boolean; // When cache disabled, open MetadataScreen instead of StreamsScreen
|
||||||
streamCacheTTL: number; // Stream cache duration in milliseconds (default: 1 hour)
|
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
|
enableStreamsBackdrop: boolean; // Enable blurred backdrop background on StreamsScreen mobile
|
||||||
useExternalPlayerForDownloads: boolean; // Enable/disable external player for downloaded content
|
useExternalPlayerForDownloads: boolean; // Enable/disable external player for downloaded content
|
||||||
// Android MPV player settings
|
// Android MPV player settings
|
||||||
|
|
@ -186,6 +187,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||||
useCachedStreams: false, // Enable by default
|
useCachedStreams: false, // Enable by default
|
||||||
openMetadataScreenWhenCacheDisabled: true, // Default to StreamsScreen when cache disabled
|
openMetadataScreenWhenCacheDisabled: true, // Default to StreamsScreen when cache disabled
|
||||||
streamCacheTTL: 60 * 60 * 1000, // Default: 1 hour in milliseconds
|
streamCacheTTL: 60 * 60 * 1000, // Default: 1 hour in milliseconds
|
||||||
|
continueWatchingCardStyle: 'wide', // Default to wide (horizontal) card style
|
||||||
enableStreamsBackdrop: true, // Enable by default (new behavior)
|
enableStreamsBackdrop: true, // Enable by default (new behavior)
|
||||||
// Android MPV player settings
|
// Android MPV player settings
|
||||||
videoPlayerEngine: 'auto', // Default to auto (ExoPlayer primary, MPV fallback)
|
videoPlayerEngine: 'auto', // Default to auto (ExoPlayer primary, MPV fallback)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ const ContinueWatchingSettingsScreen: React.FC = () => {
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === 'ios') {
|
||||||
StatusBar.setHidden(false);
|
StatusBar.setHidden(false);
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch { }
|
||||||
}, [colors.darkBackground]);
|
}, [colors.darkBackground]);
|
||||||
|
|
||||||
const handleBack = useCallback(() => {
|
const handleBack = useCallback(() => {
|
||||||
|
|
@ -184,22 +184,98 @@ const ContinueWatchingSettingsScreen: React.FC = () => {
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>PLAYBACK BEHAVIOR</Text>
|
<Text style={styles.sectionTitle}>PLAYBACK BEHAVIOR</Text>
|
||||||
<View style={styles.settingsCard}>
|
<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
|
<SettingItem
|
||||||
title="Open Metadata Screen"
|
title="Use Cached Streams"
|
||||||
description="When cached streams are disabled, open the Metadata screen instead of the Streams screen. This shows content details and allows manual stream selection."
|
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.openMetadataScreenWhenCacheDisabled}
|
value={settings.useCachedStreams}
|
||||||
onValueChange={(value) => handleUpdateSetting('openMetadataScreenWhenCacheDisabled', value)}
|
onValueChange={(value) => handleUpdateSetting('useCachedStreams', value)}
|
||||||
isLast={true}
|
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>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
@ -207,70 +283,70 @@ const ContinueWatchingSettingsScreen: React.FC = () => {
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>CACHE SETTINGS</Text>
|
<Text style={styles.sectionTitle}>CACHE SETTINGS</Text>
|
||||||
<View style={styles.settingsCard}>
|
<View style={styles.settingsCard}>
|
||||||
<View style={[styles.settingItem, { borderBottomWidth: 0, flexDirection: 'column', alignItems: 'flex-start' }]}>
|
<View style={[styles.settingItem, { borderBottomWidth: 0, flexDirection: 'column', alignItems: 'flex-start' }]}>
|
||||||
<Text style={[styles.settingTitle, { color: colors.highEmphasis, marginBottom: 8 }]}>
|
<Text style={[styles.settingTitle, { color: colors.highEmphasis, marginBottom: 8 }]}>
|
||||||
Stream Cache Duration
|
Stream Cache Duration
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.settingDescription, { color: colors.mediumEmphasis, marginBottom: 16 }]}>
|
<Text style={[styles.settingDescription, { color: colors.mediumEmphasis, marginBottom: 16 }]}>
|
||||||
How long to keep cached stream links before they expire
|
How long to keep cached stream links before they expire
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.ttlOptionsContainer}>
|
<View style={styles.ttlOptionsContainer}>
|
||||||
{TTL_OPTIONS.map((row, rowIndex) => (
|
{TTL_OPTIONS.map((row, rowIndex) => (
|
||||||
<View key={rowIndex} style={styles.ttlRow}>
|
<View key={rowIndex} style={styles.ttlRow}>
|
||||||
{row.map((option) => (
|
{row.map((option) => (
|
||||||
<TTLPickerItem key={option.value} option={option} />
|
<TTLPickerItem key={option.value} option={option} />
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{settings.useCachedStreams && (
|
{settings.useCachedStreams && (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<View style={[styles.warningCard, { borderColor: colors.warning }]}>
|
<View style={[styles.warningCard, { borderColor: colors.warning }]}>
|
||||||
<View style={styles.warningHeader}>
|
<View style={styles.warningHeader}>
|
||||||
<MaterialIcons name="warning" size={20} color={colors.warning} />
|
<MaterialIcons name="warning" size={20} color={colors.warning} />
|
||||||
<Text style={[styles.warningTitle, { color: colors.warning }]}>
|
<Text style={[styles.warningTitle, { color: colors.warning }]}>
|
||||||
Important Note
|
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>
|
</Text>
|
||||||
</View>
|
</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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<View style={styles.infoCard}>
|
<View style={styles.infoCard}>
|
||||||
<View style={styles.infoHeader}>
|
<View style={styles.infoHeader}>
|
||||||
<MaterialIcons name="info" size={20} color={colors.primary} />
|
<MaterialIcons name="info" size={20} color={colors.primary} />
|
||||||
<Text style={[styles.infoTitle, { color: colors.highEmphasis }]}>
|
<Text style={[styles.infoTitle, { color: colors.highEmphasis }]}>
|
||||||
How it works
|
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>
|
</Text>
|
||||||
</View>
|
</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>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|
@ -466,6 +542,84 @@ const createStyles = (colors: any) => StyleSheet.create({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: 20,
|
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;
|
export default ContinueWatchingSettingsScreen;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue