import type { ReactNode } from 'react'; import { Line, Rect, Circle } from 'react-native-svg'; import QRCode from 'qrcode'; import { DarkTheme, LightTheme } from '../utils/ThemeUtil'; type CoordinateMapping = [number, number[]]; const CONNECTING_ERROR_MARGIN = 0.1; const CIRCLE_SIZE_MODIFIER = 2.5; const QRCODE_MATRIX_MARGIN = 7; function isAdjecentDots(cy: number, otherCy: number, cellSize: number) { if (cy === otherCy) { return false; } const diff = cy - otherCy < 0 ? otherCy - cy : cy - otherCy; return diff <= cellSize + CONNECTING_ERROR_MARGIN; } function getMatrix(value: string, errorCorrectionLevel: QRCode.QRCodeErrorCorrectionLevel) { const arr = Array.prototype.slice.call( QRCode.create(value, { errorCorrectionLevel }).modules.data, 0 ); const sqrt = Math.sqrt(arr.length); return arr.reduce( (rows, key, index) => (index % sqrt === 0 ? rows.push([key]) : rows[rows.length - 1].push(key)) && rows, [] ); } export const QRCodeUtil = { generate(uri: string, size: number, logoSize: number) { const dotColor = DarkTheme['bg-100']; const edgeColor = LightTheme['bg-100']; const dots: ReactNode[] = []; const matrix = getMatrix(uri, 'Q'); const cellSize = size / matrix.length; const qrList = [ { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 } ]; qrList.forEach(({ x, y }) => { const x1 = (matrix.length - QRCODE_MATRIX_MARGIN) * cellSize * x; const y1 = (matrix.length - QRCODE_MATRIX_MARGIN) * cellSize * y; const borderRadius = 0.32; for (let i = 0; i < qrList.length; i += 1) { const dotSize = cellSize * (QRCODE_MATRIX_MARGIN - i * 2); dots.push( ); } }); const clearArenaSize = Math.floor((logoSize + 25) / cellSize); const matrixMiddleStart = matrix.length / 2 - clearArenaSize / 2; const matrixMiddleEnd = matrix.length / 2 + clearArenaSize / 2 - 1; const circles: [number, number][] = []; // Getting coordinates for each of the QR code dots matrix.forEach((row: QRCode.QRCode[], i: number) => { row.forEach((_, j: number) => { if (matrix[i][j]) { if ( !( (i < QRCODE_MATRIX_MARGIN && j < QRCODE_MATRIX_MARGIN) || (i > matrix.length - (QRCODE_MATRIX_MARGIN + 1) && j < QRCODE_MATRIX_MARGIN) || (i < QRCODE_MATRIX_MARGIN && j > matrix.length - (QRCODE_MATRIX_MARGIN + 1)) ) ) { if ( !( i > matrixMiddleStart && i < matrixMiddleEnd && j > matrixMiddleStart && j < matrixMiddleEnd ) ) { const cx = i * cellSize + cellSize / 2; const cy = j * cellSize + cellSize / 2; circles.push([cx, cy]); } } } }); }); // Cx to multiple cys const circlesToConnect: Record = {}; // Mapping all dots cicles on the same x axis circles.forEach(([cx, cy]) => { if (circlesToConnect[cx]) { circlesToConnect[cx]?.push(cy); } else { circlesToConnect[cx] = [cy]; } }); // Drawing lonely dots Object.entries(circlesToConnect) // Only get dots that have neighbors .map(([cx, cys]) => { const newCys = cys.filter(cy => cys.every(otherCy => !isAdjecentDots(cy, otherCy, cellSize)) ); return [Number(cx), newCys] as CoordinateMapping; }) .forEach(([cx, cys]) => { cys.forEach(cy => { dots.push( ); }); }); // Drawing lines for dots that are close to each other Object.entries(circlesToConnect) // Only get dots that have more than one dot on the x axis .filter(([_, cys]) => cys.length > 1) // Removing dots with no neighbors .map(([cx, cys]) => { const newCys = cys.filter(cy => cys.some(otherCy => isAdjecentDots(cy, otherCy, cellSize))); return [Number(cx), newCys] as CoordinateMapping; }) // Get the coordinates of the first and last dot of a line .map(([cx, cys]) => { cys.sort((a, b) => (a < b ? -1 : 1)); const groups: number[][] = []; for (const cy of cys) { const group = groups.find(item => item.some(otherCy => isAdjecentDots(cy, otherCy, cellSize)) ); if (group) { group.push(cy); } else { groups.push([cy]); } } return [cx, groups.map(item => [item[0], item[item.length - 1]])] as [number, number[][]]; }) .forEach(([cx, groups]) => { groups.forEach(([y1, y2]) => { dots.push( ); }); }); return dots; } };