some ui changes

This commit is contained in:
tapframe 2025-08-08 00:15:10 +05:30
parent e0835ddbad
commit ad2e1816dc
3 changed files with 331 additions and 240 deletions

View file

@ -786,16 +786,9 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
const logoScale = hasProgress ? 0.85 : 1; const logoScale = hasProgress ? 0.85 : 1;
return { return {
opacity: logoOpacity.value, opacity: logoOpacity.value,
transform: [ transform: [
{ // Keep logo stable by not applying translateY based on scroll
translateY: interpolate(
scrollY.value,
[0, 100],
[0, -20],
Extrapolate.CLAMP
)
},
{ scale: withTiming(logoScale, { duration: 300 }) } { scale: withTiming(logoScale, { duration: 300 }) }
] ]
}; };
@ -944,6 +937,22 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
locations={[0, 0.3, 0.55, 0.75, 0.9, 1]} locations={[0, 0.3, 0.55, 0.75, 0.9, 1]}
style={styles.heroGradient} style={styles.heroGradient}
> >
{/* Enhanced bottom fade with stronger gradient */}
<LinearGradient
colors={[
'transparent',
`${dynamicBackgroundColor || themeColors.darkBackground}10`,
`${dynamicBackgroundColor || themeColors.darkBackground}25`,
`${dynamicBackgroundColor || themeColors.darkBackground}45`,
`${dynamicBackgroundColor || themeColors.darkBackground}65`,
`${dynamicBackgroundColor || themeColors.darkBackground}85`,
`${dynamicBackgroundColor || themeColors.darkBackground}95`,
dynamicBackgroundColor || themeColors.darkBackground
]}
locations={[0, 0.1, 0.25, 0.4, 0.6, 0.75, 0.9, 1]}
style={styles.bottomFadeGradient}
pointerEvents="none"
/>
<View style={styles.heroContent}> <View style={styles.heroContent}>
{/* Optimized Title/Logo */} {/* Optimized Title/Logo */}
<View style={styles.logoContainer}> <View style={styles.logoContainer}>
@ -998,23 +1007,6 @@ const HeroSection: React.FC<HeroSectionProps> = memo(({
/> />
</View> </View>
</LinearGradient> </LinearGradient>
{/* Ultra-subtle bottom fade for feather-light seamless blend */}
<LinearGradient
colors={[
'transparent',
`${dynamicBackgroundColor || themeColors.darkBackground}05`,
`${dynamicBackgroundColor || themeColors.darkBackground}15`,
`${dynamicBackgroundColor || themeColors.darkBackground}30`,
`${dynamicBackgroundColor || themeColors.darkBackground}50`,
`${dynamicBackgroundColor || themeColors.darkBackground}70`,
`${dynamicBackgroundColor || themeColors.darkBackground}85`,
dynamicBackgroundColor || themeColors.darkBackground
]}
locations={[0, 0.15, 0.3, 0.45, 0.65, 0.8, 0.92, 1]}
style={styles.bottomFadeGradient}
pointerEvents="none"
/>
</Animated.View> </Animated.View>
); );
}); });
@ -1043,15 +1035,15 @@ const styles = StyleSheet.create({
bottom: 0, bottom: 0,
left: 0, left: 0,
right: 0, right: 0,
height: 200, height: 400,
zIndex: -1, zIndex: 1,
}, },
heroContent: { heroContent: {
padding: 16, padding: 16,
paddingTop: 8, paddingTop: 8,
paddingBottom: 8, paddingBottom: 8,
position: 'relative', position: 'relative',
zIndex: 5, zIndex: 2,
}, },
logoContainer: { logoContainer: {
alignItems: 'center', alignItems: 'center',
@ -1106,7 +1098,6 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
width: '100%', width: '100%',
position: 'relative', position: 'relative',
zIndex: 10,
}, },
actionButton: { actionButton: {
flexDirection: 'row', flexDirection: 'row',
@ -1276,153 +1267,153 @@ const styles = StyleSheet.create({
borderRadius: 25, borderRadius: 25,
backgroundColor: 'rgba(255,255,255,0.15)', backgroundColor: 'rgba(255,255,255,0.15)',
}, },
watchedIndicator: { watchedIndicator: {
position: 'absolute', position: 'absolute',
top: 4, top: 4,
right: 4, right: 4,
backgroundColor: 'rgba(0,0,0,0.6)', backgroundColor: 'rgba(0,0,0,0.6)',
borderRadius: 8, borderRadius: 8,
width: 16, width: 16,
height: 16, height: 16,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
watchedPlayButton: { watchedPlayButton: {
backgroundColor: '#1e1e1e', backgroundColor: '#1e1e1e',
borderWidth: 1, borderWidth: 1,
borderColor: 'rgba(255,255,255,0.3)', borderColor: 'rgba(255,255,255,0.3)',
shadowColor: '#000', shadowColor: '#000',
shadowOffset: { width: 0, height: 2 }, shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25, shadowOpacity: 0.25,
shadowRadius: 4, shadowRadius: 4,
elevation: 4, elevation: 4,
}, },
watchedPlayButtonText: { watchedPlayButtonText: {
color: '#fff', color: '#fff',
fontWeight: '700', fontWeight: '700',
marginLeft: 6, marginLeft: 6,
fontSize: 15, fontSize: 15,
}, },
// Enhanced progress indicator styles // Enhanced progress indicator styles
progressShimmer: { progressShimmer: {
position: 'absolute', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
borderRadius: 2, borderRadius: 2,
backgroundColor: 'rgba(255,255,255,0.1)', backgroundColor: 'rgba(255,255,255,0.1)',
}, },
completionGlow: { completionGlow: {
position: 'absolute', position: 'absolute',
top: -2, top: -2,
left: -2, left: -2,
right: -2, right: -2,
bottom: -2, bottom: -2,
borderRadius: 4, borderRadius: 4,
backgroundColor: 'rgba(0,255,136,0.2)', backgroundColor: 'rgba(0,255,136,0.2)',
}, },
completionIndicator: { completionIndicator: {
position: 'absolute', position: 'absolute',
right: 4, right: 4,
top: -6, top: -6,
bottom: -6, bottom: -6,
width: 16, width: 16,
height: 16, height: 16,
borderRadius: 8, borderRadius: 8,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
completionGradient: { completionGradient: {
width: 16, width: 16,
height: 16, height: 16,
borderRadius: 8, borderRadius: 8,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
sparkleContainer: { sparkleContainer: {
position: 'absolute', position: 'absolute',
top: -10, top: -10,
left: 0, left: 0,
right: 0, right: 0,
bottom: -10, bottom: -10,
borderRadius: 2, borderRadius: 2,
}, },
sparkle: { sparkle: {
position: 'absolute', position: 'absolute',
width: 8, width: 8,
height: 8, height: 8,
borderRadius: 4, borderRadius: 4,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
progressInfoMain: { progressInfoMain: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginBottom: 2, marginBottom: 2,
}, },
watchProgressMainText: { watchProgressMainText: {
fontSize: 11, fontSize: 11,
fontWeight: '600', fontWeight: '600',
textAlign: 'center', textAlign: 'center',
}, },
watchProgressSubText: { watchProgressSubText: {
fontSize: 9, fontSize: 9,
textAlign: 'center', textAlign: 'center',
opacity: 0.8, opacity: 0.8,
marginBottom: 1, marginBottom: 1,
}, },
syncStatusContainer: { syncStatusContainer: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginTop: 2, marginTop: 2,
width: '100%', width: '100%',
flexWrap: 'wrap', flexWrap: 'wrap',
}, },
syncStatusText: { syncStatusText: {
fontSize: 9, fontSize: 9,
marginLeft: 4, marginLeft: 4,
fontWeight: '500', fontWeight: '500',
}, },
traktSyncButtonEnhanced: { traktSyncButtonEnhanced: {
position: 'absolute', position: 'absolute',
top: 8, top: 8,
right: 8, right: 8,
width: 24, width: 24,
height: 24, height: 24,
borderRadius: 12, borderRadius: 12,
overflow: 'hidden', overflow: 'hidden',
}, },
traktSyncButtonInline: { traktSyncButtonInline: {
marginLeft: 8, marginLeft: 8,
width: 20, width: 20,
height: 20, height: 20,
borderRadius: 10, borderRadius: 10,
overflow: 'hidden', overflow: 'hidden',
}, },
syncButtonGradient: { syncButtonGradient: {
width: 24, width: 24,
height: 24, height: 24,
borderRadius: 12, borderRadius: 12,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
syncButtonGradientInline: { syncButtonGradientInline: {
width: 20, width: 20,
height: 20, height: 20,
borderRadius: 10, borderRadius: 10,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
traktIndicatorGradient: { traktIndicatorGradient: {
width: 16, width: 16,
height: 16, height: 16,
borderRadius: 8, borderRadius: 8,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
}); });
export default HeroSection; export default HeroSection;

View file

@ -11,6 +11,133 @@ interface DominantColorResult {
// Simple in-memory cache for extracted colors // Simple in-memory cache for extracted colors
const colorCache = new Map<string, string>(); const colorCache = new Map<string, string>();
// Helper function to calculate color vibrancy
const calculateVibrancy = (hex: string): number => {
const r = parseInt(hex.substr(1, 2), 16);
const g = parseInt(hex.substr(3, 2), 16);
const b = parseInt(hex.substr(5, 2), 16);
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const saturation = max === 0 ? 0 : (max - min) / max;
return saturation * (max / 255);
};
// Helper function to calculate color brightness
const calculateBrightness = (hex: string): number => {
const r = parseInt(hex.substr(1, 2), 16);
const g = parseInt(hex.substr(3, 2), 16);
const b = parseInt(hex.substr(5, 2), 16);
return (r * 299 + g * 587 + b * 114) / 1000;
};
// Helper function to darken a color
const darkenColor = (hex: string, factor: number = 0.1): string => {
const r = parseInt(hex.substr(1, 2), 16);
const g = parseInt(hex.substr(3, 2), 16);
const b = parseInt(hex.substr(5, 2), 16);
const newR = Math.floor(r * factor);
const newG = Math.floor(g * factor);
const newB = Math.floor(b * factor);
return `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`;
};
// Enhanced color selection logic
const selectBestColor = (result: ImageColorsResult): string => {
let candidates: string[] = [];
if (result.platform === 'android') {
// Collect all available colors
candidates = [
result.dominant,
result.vibrant,
result.darkVibrant,
result.muted,
result.darkMuted,
result.lightVibrant,
result.lightMuted,
result.average
].filter(Boolean);
} else if (result.platform === 'ios') {
candidates = [
result.primary,
result.secondary,
result.background,
result.detail
].filter(Boolean);
} else if (result.platform === 'web') {
candidates = [
result.dominant,
result.vibrant,
result.darkVibrant,
result.muted,
result.darkMuted,
result.lightVibrant,
result.lightMuted
].filter(Boolean);
}
if (candidates.length === 0) {
return '#1a1a1a';
}
// Score each color based on vibrancy and appropriateness for backgrounds
const scoredColors = candidates.map(color => {
const brightness = calculateBrightness(color);
const vibrancy = calculateVibrancy(color);
// Prefer colors that are:
// 1. Not too bright (good for backgrounds)
// 2. Have decent vibrancy (not too gray)
// 3. Not too dark (still visible)
let score = 0;
// Brightness scoring (prefer medium-dark colors)
if (brightness >= 30 && brightness <= 120) {
score += 3;
} else if (brightness >= 15 && brightness <= 150) {
score += 2;
} else if (brightness >= 5) {
score += 1;
}
// Vibrancy scoring (prefer some color over pure gray)
if (vibrancy >= 0.3) {
score += 3;
} else if (vibrancy >= 0.15) {
score += 2;
} else if (vibrancy >= 0.05) {
score += 1;
}
return { color, score, brightness, vibrancy };
});
// Sort by score (highest first)
scoredColors.sort((a, b) => b.score - a.score);
// Get the best color
let bestColor = scoredColors[0].color;
const bestBrightness = scoredColors[0].brightness;
// Apply more aggressive darkening to make colors darker overall
if (bestBrightness > 60) {
bestColor = darkenColor(bestColor, 0.18);
} else if (bestBrightness > 40) {
bestColor = darkenColor(bestColor, 0.3);
} else if (bestBrightness > 20) {
bestColor = darkenColor(bestColor, 0.5);
} else {
bestColor = darkenColor(bestColor, 0.7);
}
return bestColor;
};
// Preload function to start extraction early // Preload function to start extraction early
export const preloadDominantColor = async (imageUri: string | null) => { export const preloadDominantColor = async (imageUri: string | null) => {
if (!imageUri || colorCache.has(imageUri)) return; if (!imageUri || colorCache.has(imageUri)) return;
@ -22,34 +149,11 @@ export const preloadDominantColor = async (imageUri: string | null) => {
fallback: '#1a1a1a', fallback: '#1a1a1a',
cache: true, cache: true,
key: imageUri, key: imageUri,
quality: 'low', quality: 'high', // Use higher quality for better color extraction
pixelSpacing: 3, // Better sampling (Android only)
}); });
let extractedColor = '#1a1a1a'; const extractedColor = selectBestColor(result);
if (result.platform === 'android') {
extractedColor = result.darkMuted || result.muted || result.darkVibrant || result.dominant || '#1a1a1a';
} else if (result.platform === 'ios') {
extractedColor = result.background || result.primary || '#1a1a1a';
} else if (result.platform === 'web') {
extractedColor = result.darkMuted || result.muted || result.dominant || '#1a1a1a';
}
// Apply darkening logic
const hex = extractedColor.replace('#', '');
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
if (brightness > 50) {
const darkenFactor = 0.15;
const newR = Math.floor(r * darkenFactor);
const newG = Math.floor(g * darkenFactor);
const newB = Math.floor(b * darkenFactor);
extractedColor = `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`;
}
colorCache.set(imageUri, extractedColor); colorCache.set(imageUri, extractedColor);
} catch (err) { } catch (err) {
console.warn('[preloadDominantColor] Failed to preload color:', err); console.warn('[preloadDominantColor] Failed to preload color:', err);
@ -92,41 +196,11 @@ export const useDominantColor = (imageUri: string | null): DominantColorResult =
fallback: '#1a1a1a', fallback: '#1a1a1a',
cache: true, cache: true,
key: uri, key: uri,
quality: 'low', // Use low quality for better performance quality: 'high', // Use higher quality for better accuracy
pixelSpacing: 3, // Better pixel sampling (Android only)
}); });
let extractedColor = '#1a1a1a'; // Default fallback const extractedColor = selectBestColor(result);
// Handle different platform results
if (result.platform === 'android') {
// Prefer darker, more muted colors for background
extractedColor = result.darkMuted || result.muted || result.darkVibrant || result.dominant || '#1a1a1a';
} else if (result.platform === 'ios') {
// Use background color from iOS, or fallback to primary
extractedColor = result.background || result.primary || '#1a1a1a';
} else if (result.platform === 'web') {
// Use muted colors for web
extractedColor = result.darkMuted || result.muted || result.dominant || '#1a1a1a';
}
// Ensure the color is dark enough for a background
// Convert hex to RGB to check brightness
const hex = extractedColor.replace('#', '');
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
// Calculate brightness (0-255)
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
// If too bright, darken it significantly
if (brightness > 50) {
const darkenFactor = 0.15;
const newR = Math.floor(r * darkenFactor);
const newG = Math.floor(g * darkenFactor);
const newB = Math.floor(b * darkenFactor);
extractedColor = `#${newR.toString(16).padStart(2, '0')}${newG.toString(16).padStart(2, '0')}${newB.toString(16).padStart(2, '0')}`;
}
// Cache the extracted color for future use // Cache the extracted color for future use
colorCache.set(uri, extractedColor); colorCache.set(uri, extractedColor);

View file

@ -30,6 +30,7 @@ import Animated, {
useSharedValue, useSharedValue,
withTiming, withTiming,
runOnJS, runOnJS,
Easing,
} from 'react-native-reanimated'; } from 'react-native-reanimated';
import { RouteProp } from '@react-navigation/native'; import { RouteProp } from '@react-navigation/native';
import { NavigationProp } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native';
@ -115,13 +116,36 @@ const MetadataScreen: React.FC = () => {
const { dominantColor, loading: colorLoading } = useDominantColor(heroImageUri); const { dominantColor, loading: colorLoading } = useDominantColor(heroImageUri);
// Memoized background color with immediate fallback and smooth transition // Create a shared value for animated background color transitions
const backgroundColorShared = useSharedValue(currentTheme.colors.darkBackground);
// Update the shared value when dominant color changes
useEffect(() => {
if (dominantColor && dominantColor !== '#1a1a1a' && dominantColor !== null && dominantColor !== currentTheme.colors.darkBackground) {
// Smoothly transition to the new color
backgroundColorShared.value = withTiming(dominantColor, {
duration: 800, // Longer duration for smoother transition
easing: Easing.bezier(0.25, 0.1, 0.25, 1), // Smooth easing curve
});
} else {
// Transition back to theme background if needed
backgroundColorShared.value = withTiming(currentTheme.colors.darkBackground, {
duration: 800,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
});
}
}, [dominantColor, currentTheme.colors.darkBackground]);
// Create an animated style for the background color
const animatedBackgroundStyle = useAnimatedStyle(() => ({
backgroundColor: backgroundColorShared.value,
}));
// For compatibility with existing code, maintain the static value as well
const dynamicBackgroundColor = useMemo(() => { const dynamicBackgroundColor = useMemo(() => {
// Start with theme background, then use extracted color when available and different from fallback
if (dominantColor && dominantColor !== '#1a1a1a' && dominantColor !== null && dominantColor !== currentTheme.colors.darkBackground) { if (dominantColor && dominantColor !== '#1a1a1a' && dominantColor !== null && dominantColor !== currentTheme.colors.darkBackground) {
return dominantColor; return dominantColor;
} }
// Always return theme background as immediate fallback
return currentTheme.colors.darkBackground; return currentTheme.colors.darkBackground;
}, [dominantColor, currentTheme.colors.darkBackground]); }, [dominantColor, currentTheme.colors.darkBackground]);
@ -465,8 +489,9 @@ const MetadataScreen: React.FC = () => {
} }
return ( return (
<Animated.View style={[animatedBackgroundStyle, { flex: 1 }]}>
<SafeAreaView <SafeAreaView
style={[containerStyle, styles.container, { backgroundColor: dynamicBackgroundColor }]} style={[containerStyle, styles.container]}
edges={['bottom']} edges={['bottom']}
> >
<StatusBar translucent backgroundColor="transparent" barStyle="light-content" animated /> <StatusBar translucent backgroundColor="transparent" barStyle="light-content" animated />
@ -581,6 +606,7 @@ const MetadataScreen: React.FC = () => {
/> />
)} )}
</SafeAreaView> </SafeAreaView>
</Animated.View>
); );
}; };