Streamscreen UI Truncation fix

This commit is contained in:
tapframe 2025-09-17 15:20:19 +05:30
parent 59c0b6ba1b
commit 502a683ba2
6 changed files with 128 additions and 67 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -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 }}

View file

@ -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 (

View file

@ -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 */}

View file

@ -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,

View file

@ -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,