/* * QR 자리표시(placeholder) — 실제 스캔 가능한 QR 생성 라이브러리 미설치(GAP). * react-native-svg로 시드 문자열 기반 결정론적 모듈 그리드 + 파인더 패턴을 그려 * "QR처럼 보이는" 미리보기를 제공한다. 스캔 불가 — 상시 "샘플" 라벨 노출. * 실제 QR은 M10 티켓 백엔드 + QR 인코더 도입 시 교체(갭: _workspace/port_mobile_m15.md). */ import React, { useMemo } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import Svg, { Rect } from 'react-native-svg'; import { colors, radius } from '../../theme'; interface Props { seed: string; size?: number; quiet?: boolean; // 여백(quiet zone) 포함 여부 } // 결정론적 해시(문자열 → 32bit) — 시드로 모듈 on/off 결정 function hashAt(seed: string, i: number): number { let h = 2166136261 ^ i; for (let k = 0; k < seed.length; k++) { h ^= seed.charCodeAt(k); h = Math.imul(h, 16777619); } return (h >>> 0) % 100; } const MODULES = 25; // 25x25 격자 export function QrPlaceholder({ seed, size = 192, quiet = true }: Props) { const cells = useMemo(() => { const out: { x: number; y: number }[] = []; for (let y = 0; y < MODULES; y++) { for (let x = 0; x < MODULES; x++) { if (isFinderZone(x, y)) continue; // 파인더 영역은 별도 렌더 if (hashAt(seed, y * MODULES + x) < 48) out.push({ x, y }); } } return out; }, [seed]); const pad = quiet ? 2 : 0; const total = MODULES + pad * 2; const cell = size / total; return ( {cells.map((c, idx) => ( ))} {/* 3개 파인더 패턴(좌상·우상·좌하) */} 샘플 ); } function isFinderZone(x: number, y: number): boolean { const inTL = x < 8 && y < 8; const inTR = x >= MODULES - 8 && y < 8; const inBL = x < 8 && y >= MODULES - 8; return inTL || inTR || inBL; } function Finder({ x, y, cell }: { x: number; y: number; cell: number }) { return ( <> ); } const styles = StyleSheet.create({ wrap: { borderRadius: radius.sm, overflow: 'hidden', alignItems: 'center', justifyContent: 'center', }, sampleTag: { position: 'absolute', right: 4, bottom: 4, backgroundColor: colors.aiAccent, borderRadius: radius.sm, paddingHorizontal: 6, paddingVertical: 2, }, sampleText: { color: colors.white, fontSize: 10, fontWeight: '700' }, });