Online-Status (Phase 1+):
- UserAvatar mit 4 Size-Variants (sm/md/lg/xl) + integrierter Online-Dot
- OnlinePresenceProvider: Supabase-Channel + Following-Filter
- ChatHeaderStatus: "Online" neutral / "vor X min" offline
- useLastSeen + Heartbeat (60s interval + AppState-background ping)
- Privatsphäre-Toggle in profile/index
Sheets:
- FormSheet Android-keyboard-fix (Dimensions.get('screen'), kein
useWindowDimensions-Kollaps), useKeyboardHandler statt manual
Keyboard.addListener, state-reset on re-open
- PostCommentsSheet same Pattern + close-after-submit + drag bis under
app-header
- ConnectMailSheet form-view refactor: scrollable, AES-Banner als
footnote, field-order email→pw→label, fixed 0.85 über alle Steps
Chat:
- DmChatBackground iOS klecks fix (G transform statt nested Svg)
- ChatInput Lyra-1:1 (keyboardWillShow, surfaceElevated bubble,
arrow-up send, attachment links)
- dm/room/chat headers + conversation-list nutzen UserAvatar
- Foreign-Profile "Nachricht"-Button öffnet richtige DM
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
120 lines
3.3 KiB
TypeScript
120 lines
3.3 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { useWindowDimensions, View } from 'react-native';
|
|
import Svg, { Circle, Path, G } 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 + TILE / 2,
|
|
y: r * TILE + TILE / 2,
|
|
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) => (
|
|
<G
|
|
key={i}
|
|
transform={`translate(${s.x - 10}, ${s.y - 10}) rotate(${s.rotate}, 10, 10)`}
|
|
>
|
|
<SymbolShape type={s.type} color={patternColor} />
|
|
</G>
|
|
))}
|
|
</Svg>
|
|
</View>
|
|
);
|
|
}
|