Initial commit for Greenlens
This commit is contained in:
108
components/ThemeBackdrop.tsx
Normal file
108
components/ThemeBackdrop.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user