Ios #35

Merged
tapframe merged 30 commits from ios into main 2025-07-28 13:17:13 +00:00
3 changed files with 258 additions and 138 deletions
Showing only changes of commit bc9f397ada - Show all commits

View file

@ -80,13 +80,8 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
>
<View style={styles.catalogHeader}>
<View style={styles.titleContainer}>
<Text style={[styles.catalogTitle, { color: currentTheme.colors.highEmphasis }]}>{catalog.name}</Text>
<LinearGradient
colors={[currentTheme.colors.primary, currentTheme.colors.secondary]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.titleUnderline}
/>
<Text style={[styles.catalogTitle, { color: currentTheme.colors.text }]}>{catalog.name}</Text>
<View style={[styles.titleUnderline, { backgroundColor: currentTheme.colors.primary }]} />
</View>
<TouchableOpacity
onPress={() =>
@ -96,10 +91,10 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
addonId: catalog.addon
})
}
style={styles.seeAllButton}
style={styles.viewAllButton}
>
<Text style={[styles.seeAllText, { color: currentTheme.colors.primary }]}>See More</Text>
<MaterialIcons name="arrow-forward" color={currentTheme.colors.primary} size={16} />
<Text style={[styles.viewAllText, { color: currentTheme.colors.textMuted }]}>View All</Text>
<MaterialIcons name="chevron-right" size={20} color={currentTheme.colors.textMuted} />
</TouchableOpacity>
</View>
@ -130,41 +125,45 @@ const CatalogSection = ({ catalog }: CatalogSectionProps) => {
const styles = StyleSheet.create({
catalogContainer: {
marginBottom: 24,
marginBottom: 28,
},
catalogHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
marginBottom: 12,
marginBottom: 16,
},
titleContainer: {
position: 'relative',
},
catalogTitle: {
fontSize: 19,
fontWeight: '700',
letterSpacing: 0.2,
fontSize: 24,
fontWeight: '800',
letterSpacing: 0.5,
marginBottom: 4,
},
titleUnderline: {
position: 'absolute',
bottom: -2,
left: 0,
width: 35,
height: 2,
borderRadius: 1,
width: 40,
height: 3,
borderRadius: 2,
opacity: 0.8,
},
seeAllButton: {
viewAllButton: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
paddingVertical: 8,
paddingHorizontal: 10,
borderRadius: 20,
backgroundColor: 'rgba(255,255,255,0.1)',
},
seeAllText: {
viewAllText: {
fontSize: 14,
fontWeight: '600',
marginRight: 4,
},
catalogList: {
paddingHorizontal: 16,

View file

@ -277,13 +277,8 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
<Animated.View entering={FadeIn.duration(400).delay(250)} style={styles.container}>
<View style={styles.header}>
<View style={styles.titleContainer}>
<Text style={[styles.title, { color: currentTheme.colors.highEmphasis }]}>Continue Watching</Text>
<LinearGradient
colors={[currentTheme.colors.primary, currentTheme.colors.secondary]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.titleUnderline}
/>
<Text style={[styles.title, { color: currentTheme.colors.text }]}>Continue Watching</Text>
<View style={[styles.titleUnderline, { backgroundColor: currentTheme.colors.primary }]} />
</View>
</View>
@ -386,7 +381,7 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
const styles = StyleSheet.create({
container: {
marginBottom: 24,
marginBottom: 28,
paddingTop: 0,
marginTop: 12,
},
@ -395,15 +390,15 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
marginBottom: 12,
marginBottom: 16,
},
titleContainer: {
position: 'relative',
},
title: {
fontSize: 20,
fontWeight: '700',
letterSpacing: 0.3,
fontSize: 24,
fontWeight: '800',
letterSpacing: 0.5,
marginBottom: 4,
},
titleUnderline: {
@ -411,8 +406,8 @@ const styles = StyleSheet.create({
bottom: -2,
left: 0,
width: 40,
height: 2,
borderRadius: 1,
height: 3,
borderRadius: 2,
opacity: 0.8,
},
wideList: {

View file

@ -22,8 +22,8 @@ import { parseISO, isThisWeek, format, isAfter, isBefore } from 'date-fns';
import Animated, { FadeIn, FadeInRight } from 'react-native-reanimated';
const { width } = Dimensions.get('window');
const ITEM_WIDTH = width * 0.85;
const ITEM_HEIGHT = 180;
const ITEM_WIDTH = width * 0.75; // Reduced width for better spacing
const ITEM_HEIGHT = 220; // Increased height for better proportions
interface ThisWeekEpisode {
id: string;
@ -163,7 +163,10 @@ export const ThisWeekSection = () => {
if (loading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="small" color={currentTheme.colors.primary} />
<ActivityIndicator size="large" color={currentTheme.colors.primary} />
<Text style={[styles.loadingText, { color: currentTheme.colors.textMuted }]}>
Loading this week's episodes...
</Text>
</View>
);
}
@ -186,84 +189,134 @@ export const ThisWeekSection = () => {
return (
<Animated.View
entering={FadeInRight.delay(index * 100).duration(400)}
entering={FadeInRight.delay(index * 150).duration(600)}
style={styles.episodeItemContainer}
>
<TouchableOpacity
style={styles.episodeItem}
style={[
styles.episodeItem,
{
shadowColor: currentTheme.colors.black,
backgroundColor: currentTheme.colors.background,
}
]}
onPress={() => handleEpisodePress(item)}
activeOpacity={0.7}
activeOpacity={0.8}
>
<Image
source={{ uri: imageUrl }}
style={styles.poster}
contentFit="cover"
transition={300}
/>
<LinearGradient
colors={['transparent', 'rgba(0,0,0,0.8)', 'rgba(0,0,0,0.9)']}
style={styles.gradient}
>
<View style={styles.badgeContainer}>
<View style={[
styles.badge,
isReleased ? styles.releasedBadge : styles.upcomingBadge,
{ backgroundColor: isReleased ? currentTheme.colors.success + 'CC' : currentTheme.colors.primary + 'CC' }
]}>
<MaterialIcons
name={isReleased ? "check-circle" : "event"}
size={12}
color={currentTheme.colors.white}
/>
<Text style={[styles.badgeText, { color: currentTheme.colors.white }]}>
{isReleased ? 'Released' : 'Coming Soon'}
</Text>
</View>
{item.vote_average > 0 && (
<View style={[styles.ratingBadge, { backgroundColor: 'rgba(0,0,0,0.8)' }]}>
<View style={styles.imageContainer}>
<Image
source={{ uri: imageUrl }}
style={styles.poster}
contentFit="cover"
transition={400}
/>
{/* Enhanced gradient overlay */}
<LinearGradient
colors={[
'transparent',
'rgba(0,0,0,0.3)',
'rgba(0,0,0,0.7)',
'rgba(0,0,0,0.9)'
]}
style={styles.gradient}
locations={[0, 0.3, 0.7, 1]}
>
{/* Top badges */}
<View style={styles.topBadgeContainer}>
<View style={[
styles.statusBadge,
isReleased ? styles.releasedBadge : styles.upcomingBadge,
{
backgroundColor: isReleased
? currentTheme.colors.success + 'E6'
: currentTheme.colors.primary + 'E6',
borderColor: isReleased
? currentTheme.colors.success
: currentTheme.colors.primary,
}
]}>
<MaterialIcons
name="star"
size={12}
color={currentTheme.colors.primary}
name={isReleased ? "check-circle" : "schedule"}
size={14}
color={currentTheme.colors.white}
/>
<Text style={[styles.ratingText, { color: currentTheme.colors.primary }]}>
{item.vote_average.toFixed(1)}
<Text style={styles.statusBadgeText}>
{isReleased ? 'Available' : 'Upcoming'}
</Text>
</View>
)}
</View>
<View style={styles.content}>
<Text style={[styles.seriesName, { color: currentTheme.colors.text }]} numberOfLines={1}>
{item.seriesName}
</Text>
<Text style={[styles.episodeTitle, { color: currentTheme.colors.lightGray }]} numberOfLines={2}>
S{item.season}:E{item.episode} - {item.title}
</Text>
{item.overview ? (
<Text style={[styles.overview, { color: currentTheme.colors.lightGray, opacity: 0.8 }]} numberOfLines={2}>
{item.overview}
{item.vote_average > 0 && (
<View style={[
styles.ratingBadge,
{
backgroundColor: 'rgba(255,193,7,0.9)',
borderColor: '#FFD700',
}
]}>
<MaterialIcons
name="star"
size={14}
color="#FFF"
/>
<Text style={styles.ratingText}>
{item.vote_average.toFixed(1)}
</Text>
</View>
)}
</View>
{/* Content area */}
<View style={styles.contentArea}>
<View style={styles.seriesHeader}>
<Text style={[styles.seriesName, { color: currentTheme.colors.white }]} numberOfLines={1}>
{item.seriesName}
</Text>
<View style={[styles.episodeNumber, { backgroundColor: currentTheme.colors.primary + '40' }]}>
<Text style={[styles.episodeNumberText, { color: currentTheme.colors.primary }]}>
S{item.season}:E{item.episode}
</Text>
</View>
</View>
<Text style={[styles.episodeTitle, { color: currentTheme.colors.lightGray }]} numberOfLines={2}>
{item.title}
</Text>
) : null}
<Text style={[styles.releaseDate, { color: currentTheme.colors.primary }]}>
{formattedDate}
</Text>
</View>
</LinearGradient>
{item.overview && (
<Text style={[styles.overview, { color: currentTheme.colors.lightGray }]} numberOfLines={2}>
{item.overview}
</Text>
)}
<View style={styles.dateContainer}>
<MaterialIcons
name="event"
size={14}
color={currentTheme.colors.primary}
/>
<Text style={[styles.releaseDate, { color: currentTheme.colors.primary }]}>
{formattedDate}
</Text>
</View>
</View>
</LinearGradient>
</View>
</TouchableOpacity>
</Animated.View>
);
};
return (
<Animated.View entering={FadeIn.duration(300)} style={styles.container}>
<Animated.View entering={FadeIn.duration(400)} style={styles.container}>
<View style={styles.header}>
<Text style={[styles.title, { color: currentTheme.colors.text }]}>This Week</Text>
<View style={styles.titleContainer}>
<Text style={[styles.title, { color: currentTheme.colors.text }]}>This Week</Text>
<View style={[styles.titleUnderline, { backgroundColor: currentTheme.colors.primary }]} />
</View>
<TouchableOpacity onPress={handleViewAll} style={styles.viewAllButton}>
<Text style={[styles.viewAllText, { color: currentTheme.colors.lightGray }]}>View All</Text>
<MaterialIcons name="chevron-right" size={18} color={currentTheme.colors.lightGray} />
<Text style={[styles.viewAllText, { color: currentTheme.colors.textMuted }]}>View All</Text>
<MaterialIcons name="chevron-right" size={20} color={currentTheme.colors.textMuted} />
</TouchableOpacity>
</View>
@ -274,8 +327,10 @@ export const ThisWeekSection = () => {
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.listContent}
snapToInterval={ITEM_WIDTH + 12}
snapToInterval={ITEM_WIDTH + 16}
decelerationRate="fast"
snapToAlignment="start"
ItemSeparatorComponent={() => <View style={{ width: 16 }} />}
/>
</Animated.View>
);
@ -283,109 +338,180 @@ export const ThisWeekSection = () => {
const styles = StyleSheet.create({
container: {
marginVertical: 16,
marginVertical: 20,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
marginBottom: 12,
marginBottom: 16,
},
titleContainer: {
position: 'relative',
},
title: {
fontSize: 19,
fontWeight: '700',
letterSpacing: 0.2,
fontSize: 24,
fontWeight: '800',
letterSpacing: 0.5,
marginBottom: 4,
},
titleUnderline: {
position: 'absolute',
bottom: -2,
left: 0,
width: 40,
height: 3,
borderRadius: 2,
opacity: 0.8,
},
viewAllButton: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
paddingHorizontal: 10,
borderRadius: 20,
backgroundColor: 'rgba(255,255,255,0.1)',
},
viewAllText: {
fontSize: 14,
fontWeight: '600',
marginRight: 4,
},
listContent: {
paddingHorizontal: 8,
paddingHorizontal: 20,
paddingBottom: 8,
},
loadingContainer: {
padding: 20,
padding: 32,
alignItems: 'center',
},
loadingText: {
marginTop: 12,
fontSize: 16,
fontWeight: '500',
},
episodeItemContainer: {
width: ITEM_WIDTH,
height: ITEM_HEIGHT,
marginHorizontal: 6,
},
episodeItem: {
width: '100%',
height: '100%',
borderRadius: 8,
borderRadius: 16,
overflow: 'hidden',
shadowOffset: { width: 0, height: 8 },
shadowOpacity: 0.3,
shadowRadius: 12,
elevation: 12,
},
imageContainer: {
width: '100%',
height: '100%',
position: 'relative',
},
poster: {
width: '100%',
height: '100%',
borderRadius: 16,
},
gradient: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
height: '80%',
justifyContent: 'flex-end',
padding: 16,
},
badgeContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 12,
padding: 16,
borderRadius: 16,
},
badge: {
topBadgeContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
},
statusBadge: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 4,
paddingHorizontal: 10,
paddingVertical: 6,
borderRadius: 20,
borderWidth: 1,
backgroundColor: 'rgba(0,0,0,0.3)',
},
releasedBadge: {},
upcomingBadge: {},
badgeText: {
fontSize: 10,
fontWeight: 'bold',
marginLeft: 4,
statusBadgeText: {
fontSize: 11,
fontWeight: '700',
color: '#FFFFFF',
marginLeft: 6,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
ratingBadge: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 4,
borderRadius: 16,
borderWidth: 1,
backgroundColor: 'rgba(0,0,0,0.3)',
},
ratingText: {
fontSize: 10,
fontWeight: 'bold',
fontSize: 11,
fontWeight: '700',
color: '#FFFFFF',
marginLeft: 4,
},
content: {
width: '100%',
contentArea: {
justifyContent: 'flex-end',
},
seriesHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
seriesName: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 4,
fontSize: 18,
fontWeight: '700',
flex: 1,
marginRight: 8,
},
episodeNumber: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.2)',
},
episodeNumberText: {
fontSize: 12,
fontWeight: '700',
letterSpacing: 0.5,
},
episodeTitle: {
fontSize: 14,
marginBottom: 4,
fontSize: 15,
fontWeight: '600',
marginBottom: 6,
lineHeight: 20,
},
overview: {
fontSize: 12,
marginBottom: 4,
fontSize: 13,
lineHeight: 18,
marginBottom: 8,
opacity: 0.9,
},
dateContainer: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 4,
},
releaseDate: {
fontSize: 12,
fontWeight: 'bold',
fontSize: 13,
fontWeight: '600',
marginLeft: 6,
letterSpacing: 0.3,
},
});