guardia-messenger/app/(tabs)/whiteboard.tsx

103 lines
4.5 KiB
TypeScript

import React, { useState, useRef } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, PanResponder, Alert } from 'react-native';
import { Canvas, Path, Skia } from '@shopify/react-native-skia';
export default function WhiteboardScreen() {
const [paths, setPaths] = useState<Array<{ d: string; color: string; width: number }>>([]);
const [color, setColor] = useState('#00A0C8');
const [strokeWidth, setStrokeWidth] = useState(3);
const currentPath = useRef('');
const drawing = useRef(false);
const COLORS = ['#00A0C8', '#ff4444', '#44bb44', '#ffbb00', '#bb44bb', '#fff'];
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: (e) => {
const { locationX, locationY } = e.nativeEvent;
currentPath.current = `M${locationX} ${locationY}`;
drawing.current = true;
},
onPanResponderMove: (e) => {
if (!drawing.current) return;
const { locationX, locationY } = e.nativeEvent;
currentPath.current += ` L${locationX} ${locationY}`;
setPaths(prev => {
const next = [...prev];
if (next.length && next[next.length - 1].d === 'drawing') {
next[next.length - 1] = { d: currentPath.current, color, width: strokeWidth };
} else {
next.push({ d: currentPath.current, color, width: strokeWidth });
}
return next;
});
},
onPanResponderRelease: () => { drawing.current = false; },
});
const clear = () => { setPaths([]); currentPath.current = ''; };
const undo = () => setPaths(prev => prev.slice(0, -1));
const share = () => Alert.alert('공유', 'SR 채팅방으로 화이트보드를 공유합니다');
return (
<View style={s.container}>
<View style={s.header}>
<Text style={s.title}></Text>
<View style={s.headerActions}>
<TouchableOpacity style={s.headerBtn} onPress={undo}><Text style={s.headerBtnText}></Text></TouchableOpacity>
<TouchableOpacity style={s.headerBtn} onPress={clear}><Text style={s.headerBtnText}></Text></TouchableOpacity>
<TouchableOpacity style={[s.headerBtn, s.shareBtn]} onPress={share}><Text style={s.shareBtnText}></Text></TouchableOpacity>
</View>
</View>
<View style={s.canvas} {...panResponder.panHandlers}>
<Canvas style={{ flex: 1, backgroundColor: '#1A1F2E' }}>
{paths.map((p, i) => {
const skiaPath = Skia.Path.MakeFromSVGString(p.d);
if (!skiaPath) return null;
const paint = Skia.Paint();
paint.setColor(Skia.Color(p.color));
paint.setStrokeWidth(p.width);
paint.setStyle(1);
return <Path key={i} path={skiaPath} paint={paint} />;
})}
</Canvas>
</View>
<View style={s.toolbar}>
<View style={s.colorRow}>
{COLORS.map(c => (
<TouchableOpacity key={c} style={[s.colorBtn, { backgroundColor: c, borderWidth: color === c ? 2 : 0, borderColor: '#fff' }]} onPress={() => setColor(c)} />
))}
</View>
<View style={s.widthRow}>
{[2, 4, 8].map(w => (
<TouchableOpacity key={w} style={[s.widthBtn, strokeWidth === w && s.widthBtnActive]} onPress={() => setStrokeWidth(w)}>
<View style={[s.widthDot, { width: w * 4, height: w * 4, borderRadius: w * 2 }]} />
</TouchableOpacity>
))}
</View>
</View>
</View>
);
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0E1A' },
header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 12, borderBottomWidth: 1, borderBottomColor: '#333' },
title: { color: '#fff', fontSize: 18, fontWeight: '700' },
headerActions: { flexDirection: 'row', gap: 8 },
headerBtn: { paddingHorizontal: 12, paddingVertical: 6, backgroundColor: '#1A1F2E', borderRadius: 8 },
headerBtnText: { color: '#aaa', fontSize: 13 },
shareBtn: { backgroundColor: '#003366' },
shareBtnText: { color: '#fff', fontWeight: '700', fontSize: 13 },
canvas: { flex: 1 },
toolbar: { padding: 12, borderTopWidth: 1, borderTopColor: '#333', backgroundColor: '#0A0E1A' },
colorRow: { flexDirection: 'row', gap: 10, marginBottom: 10, justifyContent: 'center' },
colorBtn: { width: 28, height: 28, borderRadius: 14 },
widthRow: { flexDirection: 'row', gap: 16, justifyContent: 'center', alignItems: 'center' },
widthBtn: { padding: 8, borderRadius: 8 },
widthBtnActive: { backgroundColor: '#1A1F2E' },
widthDot: { backgroundColor: '#fff' },
});