chahinebrini 6ac6a26b9c feat(native/dm): WhatsApp-style chat — bg pattern, bubble redesign, avatar + realtime fixes
- Header: partner avatar left-aligned (was centered)
- ChatBubble: replace bright blue with subtle mint/brand tint, asymmetric
  tail-corner radius, footer pinned bottom-right, reply-quote with green
  side-bar
- New DmChatBackground: SVG hex-offset doodle pattern (stars, hearts,
  clouds, dots) at 7% opacity — light-cream / dark-warm-green base
- Avatar in chat list: use resolveAvatar() consistently to handle
  hero-id, https, and null cases
- Realtime subscription: stabilize deps via partnerRef to stop
  re-subscribing on partner state change
- Pressable → TouchableOpacity throughout

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 08:50:12 +02:00

124 lines
3.3 KiB
TypeScript

import { useMemo } from 'react';
import { useWindowDimensions, View } from 'react-native';
import Svg, { Circle, Path, Line, Rect } from 'react-native-svg';
import { useColors } from '../../lib/theme';
const TILE = 80;
const OPACITY = 0.07;
type Symbol = 'star' | 'heart' | 'check' | 'dot' | 'cloud' | 'wave' | 'diamond';
const SEQUENCE: Symbol[] = [
'star', 'dot', 'heart', 'wave', 'check', 'dot', 'cloud',
'diamond', 'dot', 'star', 'check', 'heart', 'dot', 'wave',
];
function SymbolShape({ type, color }: { type: Symbol; color: string }) {
switch (type) {
case 'star':
return (
<Path
d="M10 2 L11.8 7.6 L18 7.6 L13 11.2 L14.8 16.8 L10 13.2 L5.2 16.8 L7 11.2 L2 7.6 L8.2 7.6 Z"
fill={color}
opacity={OPACITY}
/>
);
case 'heart':
return (
<Path
d="M10 15 C10 15 3 10 3 6 C3 3.8 4.8 2 7 2 C8.3 2 9.5 2.7 10 3.7 C10.5 2.7 11.7 2 13 2 C15.2 2 17 3.8 17 6 C17 10 10 15 10 15 Z"
fill={color}
opacity={OPACITY}
/>
);
case 'check':
return (
<Path
d="M3 9 L7 13 L17 4"
stroke={color}
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
opacity={OPACITY}
/>
);
case 'dot':
return <Circle cx="10" cy="10" r="3.5" fill={color} opacity={OPACITY} />;
case 'cloud':
return (
<Path
d="M5 12 C3.3 12 2 10.7 2 9 C2 7.4 3.2 6.1 4.7 6 C5 4.3 6.5 3 8.3 3 C9.8 3 11.1 3.9 11.7 5.2 C12 5.1 12.3 5 12.7 5 C14.5 5 16 6.5 16 8.3 C16 10.3 14.3 12 12.3 12 Z"
fill={color}
opacity={OPACITY}
/>
);
case 'wave':
return (
<Path
d="M2 10 Q5 7 8 10 Q11 13 14 10 Q17 7 20 10"
stroke={color}
strokeWidth={1.5}
strokeLinecap="round"
fill="none"
opacity={OPACITY}
/>
);
case 'diamond':
return (
<Path
d="M10 2 L18 10 L10 18 L2 10 Z"
fill={color}
opacity={OPACITY}
/>
);
}
}
export function DmChatBackground() {
const { width, height } = useWindowDimensions();
const colors = useColors();
const patternColor = colors.text;
const cols = Math.ceil(width / TILE) + 1;
const rows = Math.ceil(height / TILE) + 1;
const symbols = useMemo(() => {
const items: { x: number; y: number; type: Symbol; rotate: number }[] = [];
let seq = 0;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const offsetX = r % 2 === 0 ? 0 : TILE / 2;
items.push({
x: c * TILE + offsetX,
y: r * TILE,
type: SEQUENCE[seq % SEQUENCE.length],
rotate: [0, 15, -10, 30, -20, 5, -15][seq % 7],
});
seq++;
}
}
return items;
}, [cols, rows]);
return (
<View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} pointerEvents="none">
<Svg width={width} height={height}>
{symbols.map((s, i) => (
<Svg
key={i}
x={s.x - 10}
y={s.y - 10}
width={20}
height={20}
viewBox="0 0 20 20"
>
<SymbolShape type={s.type} color={patternColor} />
</Svg>
))}
</Svg>
</View>
);
}