mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
Streamscreen UI Truncation fix
This commit is contained in:
parent
59c0b6ba1b
commit
502a683ba2
6 changed files with 128 additions and 67 deletions
BIN
assets/support_me_on_kofi_red.png
Normal file
BIN
assets/support_me_on_kofi_red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -95,7 +95,14 @@ const HeroCarousel: React.FC<HeroCarouselProps> = ({ items, loading = false }) =
|
||||||
contentContainerStyle={{ paddingHorizontal: (width - CARD_WIDTH) / 2 }}
|
contentContainerStyle={{ paddingHorizontal: (width - CARD_WIDTH) / 2 }}
|
||||||
renderItem={() => (
|
renderItem={() => (
|
||||||
<View style={{ width: CARD_WIDTH + 16 }}>
|
<View style={{ width: CARD_WIDTH + 16 }}>
|
||||||
<View style={[styles.card, { backgroundColor: currentTheme.colors.elevation1 }] as StyleProp<ViewStyle>}>
|
<View style={[
|
||||||
|
styles.card,
|
||||||
|
{
|
||||||
|
backgroundColor: currentTheme.colors.elevation1,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,255,255,0.18)',
|
||||||
|
}
|
||||||
|
] as StyleProp<ViewStyle>}>
|
||||||
<View style={styles.bannerContainer as ViewStyle}>
|
<View style={styles.bannerContainer as ViewStyle}>
|
||||||
<View style={styles.skeletonBannerFull as ViewStyle} />
|
<View style={styles.skeletonBannerFull as ViewStyle} />
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
|
|
@ -264,7 +271,14 @@ const CarouselCard: React.FC<CarouselCardProps> = memo(({ item, colors, logoFail
|
||||||
activeOpacity={0.9}
|
activeOpacity={0.9}
|
||||||
onPress={onPressInfo}
|
onPress={onPressInfo}
|
||||||
>
|
>
|
||||||
<View style={[styles.card, { backgroundColor: colors.elevation1 }] as StyleProp<ViewStyle>}>
|
<View style={[
|
||||||
|
styles.card,
|
||||||
|
{
|
||||||
|
backgroundColor: colors.elevation1,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(255,255,255,0.18)',
|
||||||
|
}
|
||||||
|
] as StyleProp<ViewStyle>}>
|
||||||
<View style={styles.bannerContainer as ViewStyle}>
|
<View style={styles.bannerContainer as ViewStyle}>
|
||||||
<ExpoImage
|
<ExpoImage
|
||||||
source={{ uri: item.banner || item.poster }}
|
source={{ uri: item.banner || item.poster }}
|
||||||
|
|
|
||||||
|
|
@ -831,8 +831,33 @@ const LibraryScreen = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filteredItems.length === 0) {
|
if (filteredItems.length === 0) {
|
||||||
// Intentionally render nothing to match the minimal empty state in the design
|
const emptyTitle = filter === 'movies' ? 'No movies yet' : filter === 'series' ? 'No TV shows yet' : 'No content yet';
|
||||||
return <View style={styles.listContainer} />;
|
const emptySubtitle = 'Add some content to your library to see it here';
|
||||||
|
return (
|
||||||
|
<View style={styles.emptyContainer}>
|
||||||
|
<MaterialIcons
|
||||||
|
name="video-library"
|
||||||
|
size={64}
|
||||||
|
color={currentTheme.colors.lightGray}
|
||||||
|
/>
|
||||||
|
<Text style={[styles.emptyText, { color: currentTheme.colors.white }]}>
|
||||||
|
{emptyTitle}
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.emptySubtext, { color: currentTheme.colors.mediumGray }]}>
|
||||||
|
{emptySubtitle}
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.exploreButton, {
|
||||||
|
backgroundColor: currentTheme.colors.primary,
|
||||||
|
shadowColor: currentTheme.colors.black
|
||||||
|
}]}
|
||||||
|
onPress={() => navigation.navigate('Search')}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Text style={[styles.exploreButtonText, { color: currentTheme.colors.white }]}>Find something to watch</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
InteractionManager,
|
InteractionManager,
|
||||||
BackHandler,
|
BackHandler,
|
||||||
|
Platform,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { useRoute, useNavigation, useFocusEffect } from '@react-navigation/native';
|
import { useRoute, useNavigation, useFocusEffect } from '@react-navigation/native';
|
||||||
|
|
@ -612,8 +613,10 @@ const MetadataScreen: React.FC = () => {
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
onScroll={animations.scrollHandler}
|
onScroll={animations.scrollHandler}
|
||||||
scrollEventThrottle={16}
|
scrollEventThrottle={16}
|
||||||
bounces={false}
|
bounces={Platform.OS === 'ios'}
|
||||||
overScrollMode="never"
|
overScrollMode={Platform.OS === 'android' ? 'always' : 'always'}
|
||||||
|
nestedScrollEnabled
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
>
|
>
|
||||||
{/* Hero Section - Optimized */}
|
{/* Hero Section - Optimized */}
|
||||||
|
|
|
||||||
|
|
@ -719,32 +719,46 @@ const SettingsScreen: React.FC = () => {
|
||||||
{renderCategoryContent(selectedCategory)}
|
{renderCategoryContent(selectedCategory)}
|
||||||
|
|
||||||
{selectedCategory === 'about' && (
|
{selectedCategory === 'about' && (
|
||||||
<View style={styles.footer}>
|
<>
|
||||||
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
<View style={styles.footer}>
|
||||||
Made with ❤️ by the Nuvio team
|
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||||
</Text>
|
Made with ❤️ by Tapframe and Friends
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Discord Join Button - Show on all categories for tablet */}
|
|
||||||
<View style={styles.discordContainer}>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={[styles.discordButton, { backgroundColor: currentTheme.colors.elevation1 }]}
|
|
||||||
onPress={() => Linking.openURL('https://discord.gg/6w8dr3TSDN')}
|
|
||||||
activeOpacity={0.7}
|
|
||||||
>
|
|
||||||
<View style={styles.discordButtonContent}>
|
|
||||||
<Image
|
|
||||||
source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
|
|
||||||
style={styles.discordLogo}
|
|
||||||
resizeMode="contain"
|
|
||||||
/>
|
|
||||||
<Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}>
|
|
||||||
Join Discord
|
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
<View style={styles.discordContainer}>
|
||||||
</View>
|
<View style={{ flexDirection: 'row', gap: 12 }}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.discordButton, { backgroundColor: currentTheme.colors.elevation1 }]}
|
||||||
|
onPress={() => Linking.openURL('https://discord.gg/6w8dr3TSDN')}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<View style={styles.discordButtonContent}>
|
||||||
|
<Image
|
||||||
|
source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
|
||||||
|
style={styles.discordLogo}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
<Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}>
|
||||||
|
Join Discord
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.discordButton, { backgroundColor: 'transparent', paddingVertical: 0, paddingHorizontal: 0 }]}
|
||||||
|
onPress={() => Linking.openURL('https://ko-fi.com/tapframe')}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
source={require('../../assets/support_me_on_kofi_red.png')}
|
||||||
|
style={styles.kofiImage}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -785,28 +799,42 @@ const SettingsScreen: React.FC = () => {
|
||||||
|
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||||
Made with ❤️ by the Nuvio team
|
Made with ❤️ by Tapframe and friends
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Discord Join Button */}
|
{/* Support & Community Buttons */}
|
||||||
<View style={styles.discordContainer}>
|
<View style={styles.discordContainer}>
|
||||||
<TouchableOpacity
|
<View style={{ flexDirection: 'row', gap: 12 }}>
|
||||||
style={[styles.discordButton, { backgroundColor: currentTheme.colors.elevation1 }]}
|
<TouchableOpacity
|
||||||
onPress={() => Linking.openURL('https://discord.gg/6w8dr3TSDN')}
|
style={[styles.discordButton, { backgroundColor: currentTheme.colors.elevation1 }]}
|
||||||
activeOpacity={0.7}
|
onPress={() => Linking.openURL('https://discord.gg/6w8dr3TSDN')}
|
||||||
>
|
activeOpacity={0.7}
|
||||||
<View style={styles.discordButtonContent}>
|
>
|
||||||
|
<View style={styles.discordButtonContent}>
|
||||||
|
<Image
|
||||||
|
source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
|
||||||
|
style={styles.discordLogo}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
<Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}>
|
||||||
|
Join Discord
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.discordButton, { backgroundColor: 'transparent', paddingVertical: 0, paddingHorizontal: 0 }]}
|
||||||
|
onPress={() => Linking.openURL('https://ko-fi.com/tapframe')}
|
||||||
|
activeOpacity={0.7}
|
||||||
|
>
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
|
source={require('../../assets/support_me_on_kofi_red.png')}
|
||||||
style={styles.discordLogo}
|
style={styles.kofiImage}
|
||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}>
|
</TouchableOpacity>
|
||||||
Join Discord
|
</View>
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -1045,7 +1073,7 @@ const styles = StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
marginBottom: 20,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
footerText: {
|
footerText: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
@ -1053,7 +1081,7 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
// New styles for Discord button
|
// New styles for Discord button
|
||||||
discordContainer: {
|
discordContainer: {
|
||||||
marginTop: 20,
|
marginTop: 8,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
|
|
@ -1079,6 +1107,10 @@ const styles = StyleSheet.create({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
},
|
},
|
||||||
|
kofiImage: {
|
||||||
|
height: 32,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
loadingSpinner: {
|
loadingSpinner: {
|
||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
|
|
|
||||||
|
|
@ -1197,26 +1197,15 @@ class StremioService {
|
||||||
const isDirectStreamingUrl = this.isDirectStreamingUrl(streamUrl);
|
const isDirectStreamingUrl = this.isDirectStreamingUrl(streamUrl);
|
||||||
const isMagnetStream = streamUrl?.startsWith('magnet:');
|
const isMagnetStream = streamUrl?.startsWith('magnet:');
|
||||||
|
|
||||||
// Memory optimization: Limit title length to prevent memory bloat
|
// Prefer full, untruncated text to preserve complete addon details
|
||||||
let displayTitle = stream.title || stream.name || 'Unnamed Stream';
|
let displayTitle = stream.title || stream.name || 'Unnamed Stream';
|
||||||
if (stream.description && stream.description.includes('\n') && stream.description.length > (stream.title?.length || 0)) {
|
if (stream.description && stream.description.includes('\n') && stream.description.length > (stream.title?.length || 0)) {
|
||||||
// If description exists, contains newlines (likely formatted metadata),
|
// If description exists and is likely the formatted metadata, prefer it as-is
|
||||||
// and is longer than the title, prefer it but truncate if too long
|
displayTitle = stream.description;
|
||||||
displayTitle = stream.description.length > 150
|
|
||||||
? stream.description.substring(0, 150) + '...'
|
|
||||||
: stream.description;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate display title if still too long
|
// Use full name for primary identifier if available
|
||||||
if (displayTitle.length > 100) {
|
|
||||||
displayTitle = displayTitle.substring(0, 100) + '...';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the original name field for the primary identifier if available
|
|
||||||
let name = stream.name || stream.title || 'Unnamed Stream';
|
let name = stream.name || stream.title || 'Unnamed Stream';
|
||||||
if (name.length > 80) {
|
|
||||||
name = name.substring(0, 80) + '...';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract size: Prefer behaviorHints.videoSize, fallback to top-level size
|
// Extract size: Prefer behaviorHints.videoSize, fallback to top-level size
|
||||||
const sizeInBytes = stream.behaviorHints?.videoSize || stream.size || undefined;
|
const sizeInBytes = stream.behaviorHints?.videoSize || stream.size || undefined;
|
||||||
|
|
@ -1241,10 +1230,8 @@ class StremioService {
|
||||||
title: displayTitle,
|
title: displayTitle,
|
||||||
addonName: addon.name,
|
addonName: addon.name,
|
||||||
addonId: addon.id,
|
addonId: addon.id,
|
||||||
// Memory optimization: Only include essential fields
|
// Include description as-is to preserve full details
|
||||||
description: stream.description && stream.description.length <= 100
|
description: stream.description,
|
||||||
? stream.description
|
|
||||||
: undefined, // Skip long descriptions
|
|
||||||
infoHash: stream.infoHash || undefined,
|
infoHash: stream.infoHash || undefined,
|
||||||
fileIdx: stream.fileIdx,
|
fileIdx: stream.fileIdx,
|
||||||
size: sizeInBytes,
|
size: sizeInBytes,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue