mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +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 }}
|
||||
renderItem={() => (
|
||||
<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.skeletonBannerFull as ViewStyle} />
|
||||
<LinearGradient
|
||||
|
|
@ -264,7 +271,14 @@ const CarouselCard: React.FC<CarouselCardProps> = memo(({ item, colors, logoFail
|
|||
activeOpacity={0.9}
|
||||
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}>
|
||||
<ExpoImage
|
||||
source={{ uri: item.banner || item.poster }}
|
||||
|
|
|
|||
|
|
@ -831,8 +831,33 @@ const LibraryScreen = () => {
|
|||
}
|
||||
|
||||
if (filteredItems.length === 0) {
|
||||
// Intentionally render nothing to match the minimal empty state in the design
|
||||
return <View style={styles.listContainer} />;
|
||||
const emptyTitle = filter === 'movies' ? 'No movies yet' : filter === 'series' ? 'No TV shows yet' : 'No content yet';
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
TouchableOpacity,
|
||||
InteractionManager,
|
||||
BackHandler,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useRoute, useNavigation, useFocusEffect } from '@react-navigation/native';
|
||||
|
|
@ -612,8 +613,10 @@ const MetadataScreen: React.FC = () => {
|
|||
showsVerticalScrollIndicator={false}
|
||||
onScroll={animations.scrollHandler}
|
||||
scrollEventThrottle={16}
|
||||
bounces={false}
|
||||
overScrollMode="never"
|
||||
bounces={Platform.OS === 'ios'}
|
||||
overScrollMode={Platform.OS === 'android' ? 'always' : 'always'}
|
||||
nestedScrollEnabled
|
||||
keyboardShouldPersistTaps="handled"
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
>
|
||||
{/* Hero Section - Optimized */}
|
||||
|
|
|
|||
|
|
@ -719,32 +719,46 @@ const SettingsScreen: React.FC = () => {
|
|||
{renderCategoryContent(selectedCategory)}
|
||||
|
||||
{selectedCategory === 'about' && (
|
||||
<View style={styles.footer}>
|
||||
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
Made with ❤️ by the Nuvio team
|
||||
</Text>
|
||||
</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
|
||||
<>
|
||||
<View style={styles.footer}>
|
||||
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
Made with ❤️ by Tapframe and Friends
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.discordContainer}>
|
||||
<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>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -785,28 +799,42 @@ const SettingsScreen: React.FC = () => {
|
|||
|
||||
<View style={styles.footer}>
|
||||
<Text style={[styles.footerText, { color: currentTheme.colors.mediumEmphasis }]}>
|
||||
Made with ❤️ by the Nuvio team
|
||||
Made with ❤️ by Tapframe and friends
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Discord Join Button */}
|
||||
{/* Support & Community Buttons */}
|
||||
<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}>
|
||||
<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={{ uri: 'https://pngimg.com/uploads/discord/discord_PNG3.png' }}
|
||||
style={styles.discordLogo}
|
||||
source={require('../../assets/support_me_on_kofi_red.png')}
|
||||
style={styles.kofiImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<Text style={[styles.discordButtonText, { color: currentTheme.colors.highEmphasis }]}>
|
||||
Join Discord
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
|
@ -1045,7 +1073,7 @@ const styles = StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 10,
|
||||
marginBottom: 20,
|
||||
marginBottom: 8,
|
||||
},
|
||||
footerText: {
|
||||
fontSize: 14,
|
||||
|
|
@ -1053,7 +1081,7 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
// New styles for Discord button
|
||||
discordContainer: {
|
||||
marginTop: 20,
|
||||
marginTop: 8,
|
||||
marginBottom: 20,
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
|
@ -1079,6 +1107,10 @@ const styles = StyleSheet.create({
|
|||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
},
|
||||
kofiImage: {
|
||||
height: 32,
|
||||
width: 150,
|
||||
},
|
||||
loadingSpinner: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
|
|
|
|||
|
|
@ -1197,26 +1197,15 @@ class StremioService {
|
|||
const isDirectStreamingUrl = this.isDirectStreamingUrl(streamUrl);
|
||||
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';
|
||||
if (stream.description && stream.description.includes('\n') && stream.description.length > (stream.title?.length || 0)) {
|
||||
// If description exists, contains newlines (likely formatted metadata),
|
||||
// and is longer than the title, prefer it but truncate if too long
|
||||
displayTitle = stream.description.length > 150
|
||||
? stream.description.substring(0, 150) + '...'
|
||||
: stream.description;
|
||||
// If description exists and is likely the formatted metadata, prefer it as-is
|
||||
displayTitle = stream.description;
|
||||
}
|
||||
|
||||
// Truncate display title if still too long
|
||||
if (displayTitle.length > 100) {
|
||||
displayTitle = displayTitle.substring(0, 100) + '...';
|
||||
}
|
||||
|
||||
// Use the original name field for the primary identifier if available
|
||||
|
||||
// Use full name for primary identifier if available
|
||||
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
|
||||
const sizeInBytes = stream.behaviorHints?.videoSize || stream.size || undefined;
|
||||
|
|
@ -1241,10 +1230,8 @@ class StremioService {
|
|||
title: displayTitle,
|
||||
addonName: addon.name,
|
||||
addonId: addon.id,
|
||||
// Memory optimization: Only include essential fields
|
||||
description: stream.description && stream.description.length <= 100
|
||||
? stream.description
|
||||
: undefined, // Skip long descriptions
|
||||
// Include description as-is to preserve full details
|
||||
description: stream.description,
|
||||
infoHash: stream.infoHash || undefined,
|
||||
fileIdx: stream.fileIdx,
|
||||
size: sizeInBytes,
|
||||
|
|
|
|||
Loading…
Reference in a new issue