Greenlens/components/ThemeBackdrop.tsx

109 lines
3.5 KiB
TypeScript

import React, { useMemo } from 'react';
import { StyleSheet, View } from 'react-native';
import { AppColors } from '../constants/Colors';
interface ThemeBackdropProps {
colors: AppColors;
}
const texturePoints = [
[4, 6, 2], [12, 14, 1], [20, 9, 2], [26, 19, 1], [34, 8, 2], [42, 16, 1],
[50, 10, 1], [58, 18, 2], [66, 7, 1], [74, 15, 2], [82, 11, 1], [90, 17, 2],
[8, 30, 1], [16, 25, 2], [24, 33, 1], [32, 27, 2], [40, 35, 1], [48, 28, 2],
[56, 36, 1], [64, 26, 2], [72, 34, 1], [80, 29, 2], [88, 37, 1], [94, 31, 2],
[6, 48, 2], [14, 44, 1], [22, 52, 2], [30, 46, 1], [38, 54, 2], [46, 49, 1],
[54, 56, 2], [62, 45, 1], [70, 53, 2], [78, 47, 1], [86, 55, 2], [92, 50, 1],
[10, 70, 1], [18, 64, 2], [26, 72, 1], [34, 67, 2], [42, 74, 1], [50, 68, 2],
[58, 76, 1], [66, 65, 2], [74, 73, 1], [82, 69, 2], [90, 77, 1], [96, 71, 2],
];
const parseColor = (value: string) => {
if (value.startsWith('#')) {
const cleaned = value.replace('#', '');
const normalized = cleaned.length === 3
? cleaned.split('').map((c) => `${c}${c}`).join('')
: cleaned;
const int = Number.parseInt(normalized, 16);
return {
r: (int >> 16) & 255,
g: (int >> 8) & 255,
b: int & 255,
a: 1,
};
}
const match = value.match(/rgba?\(([^)]+)\)/i);
if (!match) return { r: 0, g: 0, b: 0, a: 0 };
const parts = match[1].split(',').map((part) => part.trim());
return {
r: Number.parseFloat(parts[0]) || 0,
g: Number.parseFloat(parts[1]) || 0,
b: Number.parseFloat(parts[2]) || 0,
a: parts.length > 3 ? Number.parseFloat(parts[3]) || 0 : 1,
};
};
const mixColor = (start: string, end: string, ratio: number) => {
const a = parseColor(start);
const b = parseColor(end);
const t = Math.max(0, Math.min(1, ratio));
const r = Math.round(a.r + (b.r - a.r) * t);
const g = Math.round(a.g + (b.g - a.g) * t);
const bl = Math.round(a.b + (b.b - a.b) * t);
const alpha = a.a + (b.a - a.a) * t;
return `rgba(${r}, ${g}, ${bl}, ${alpha})`;
};
const ThemeBackdropInner: React.FC<ThemeBackdropProps> = ({ colors }) => {
const gradientStrips = useMemo(() =>
Array.from({ length: 18 }).map((_, index, arr) => {
const ratio = index / (arr.length - 1);
return mixColor(colors.pageGradientStart, colors.pageGradientEnd, ratio);
}),
[colors.pageGradientStart, colors.pageGradientEnd]);
return (
<View pointerEvents="none" style={[StyleSheet.absoluteFill, { backgroundColor: colors.pageBase }]}>
<View style={styles.gradientLayer}>
{gradientStrips.map((stripColor, index) => (
<View
key={`strip-${index}`}
style={[styles.gradientStrip, { backgroundColor: stripColor }]}
/>
))}
</View>
<View style={styles.textureLayer}>
{texturePoints.map(([x, y, size], index) => (
<View
key={index}
style={[
styles.noiseDot,
{
left: `${x}%`,
top: `${y}%`,
width: size,
height: size,
borderRadius: size / 2,
backgroundColor: colors.pageTexture,
},
]}
/>
))}
</View>
</View>
);
};
export const ThemeBackdrop = React.memo(ThemeBackdropInner);
const styles = StyleSheet.create({
gradientLayer: { ...StyleSheet.absoluteFillObject },
gradientStrip: { flex: 1 },
textureLayer: {
...StyleSheet.absoluteFillObject,
},
noiseDot: {
position: 'absolute',
},
});