mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-03 16:59:08 +00:00
some ui changes
This commit is contained in:
parent
e0835ddbad
commit
ad2e1816dc
3 changed files with 331 additions and 240 deletions
|
|
@ -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;
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue