-
Notifications
You must be signed in to change notification settings - Fork 51
chore: QR changes #291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: QR changes #291
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @ignaciosantise's task —— View job Code Review Complete✅ No issues found Summary: Reviewed 3 changed files:
Changes analyzed:
Automated checks:
Memo comparison note (non-blocking observation): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a comprehensive patch to the react-native-qrcode-skia library (v0.3.1) to enable more customization options for QR code rendering. The changes introduce new shape styles, custom eye patterns, and rounded logo areas, then applies these new features to the POS app's QR code component.
Key changes:
- Adds new QR code customization features:
logoAreaBorderRadius,eyeShape,pupilShape, andlineshape style - Implements rounded rectangle logic for logo areas with corner circle detection
- Adds custom eye rendering with circle, rounded, and square options for both the eye frame and pupil
- Implements a "line" shape that renders QR modules as vertical pills
- Updates the QR code component to use the new "line" shape with custom eye patterns
- Increments the Android versionCode from 12 to 13
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
dapps/pos-app/patches/react-native-qrcode-skia+0.3.1.patch |
Comprehensive patch adding new shape options, rounded logo areas, custom eye patterns, and line rendering mode |
dapps/pos-app/components/qr-code.tsx |
Updates QR configuration to use new line shape with rounded eyes, circular pupils, and reduced padding |
dapps/pos-app/app.json |
Increments Android versionCode to 13 for the release |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
| + const logoAreaX = (center - logoRadius) * cellSize; | ||
| + const logoAreaY = (center - logoRadius) * cellSize; | ||
| + const logoAreaSize = (logoRadius * 2 + 1) * cellSize; | ||
| + const r = Math.min(logoAreaBorderRadius, logoAreaSize / 2); | ||
| + const cellCenterX = j * cellSize + cellSize / 2; | ||
| + const cellCenterY = i * cellSize + cellSize / 2; | ||
| + | ||
| + // Center rectangle (excluding corners) | ||
| + if (cellCenterX >= logoAreaX + r && cellCenterX <= logoAreaX + logoAreaSize - r && cellCenterY >= logoAreaY && cellCenterY <= logoAreaY + logoAreaSize) return true; | ||
| + if (cellCenterY >= logoAreaY + r && cellCenterY <= logoAreaY + logoAreaSize - r && cellCenterX >= logoAreaX && cellCenterX <= logoAreaX + logoAreaSize) return true; |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The variable name logoAreaSize represents a single dimension (width/height) but the rounded rectangle logic uses it as both width and height. This is correct since it's a square, but the calculation (logoRadius * 2 + 1) * cellSize may not be semantically accurate. The + 1 accounts for the center cell, making it an odd number of cells, but this should be verified against the expected logo area dimensions.
| + const logoAreaX = (center - logoRadius) * cellSize; | |
| + const logoAreaY = (center - logoRadius) * cellSize; | |
| + const logoAreaSize = (logoRadius * 2 + 1) * cellSize; | |
| + const r = Math.min(logoAreaBorderRadius, logoAreaSize / 2); | |
| + const cellCenterX = j * cellSize + cellSize / 2; | |
| + const cellCenterY = i * cellSize + cellSize / 2; | |
| + | |
| + // Center rectangle (excluding corners) | |
| + if (cellCenterX >= logoAreaX + r && cellCenterX <= logoAreaX + logoAreaSize - r && cellCenterY >= logoAreaY && cellCenterY <= logoAreaY + logoAreaSize) return true; | |
| + if (cellCenterY >= logoAreaY + r && cellCenterY <= logoAreaY + logoAreaSize - r && cellCenterX >= logoAreaX && cellCenterX <= logoAreaX + logoAreaSize) return true; | |
| + // logoAreaSideLength is the length of one side of the square logo area, covering (logoRadius * 2 + 1) cells centered in the matrix. | |
| + const logoAreaX = (center - logoRadius) * cellSize; | |
| + const logoAreaY = (center - logoRadius) * cellSize; | |
| + const logoAreaSideLength = (logoRadius * 2 + 1) * cellSize; | |
| + const r = Math.min(logoAreaBorderRadius, logoAreaSideLength / 2); | |
| + const cellCenterX = j * cellSize + cellSize / 2; | |
| + const cellCenterY = i * cellSize + cellSize / 2; | |
| + | |
| + // Center rectangle (excluding corners) | |
| + if (cellCenterX >= logoAreaX + r && cellCenterX <= logoAreaX + logoAreaSideLength - r && cellCenterY >= logoAreaY && cellCenterY <= logoAreaY + logoAreaSideLength) return true; | |
| + if (cellCenterY >= logoAreaY + r && cellCenterY <= logoAreaY + logoAreaSideLength - r && cellCenterX >= logoAreaX && cellCenterX <= logoAreaX + logoAreaSideLength) return true; |
| + } | ||
| + return false; | ||
| + }; | ||
| + if (shape === 'line') { | ||
| + const processed = Array.from({ | ||
| + length: matrix.length | ||
| + }, () => Array(matrix.length).fill(false)); | ||
| + for (let j = 0; j < matrix.length; j++) { | ||
| + for (let i = 0; i < matrix.length; i++) { | ||
| + if (matrix[i][j] === 1 && !isLogoArea(i, j) && !processed[i][j]) { | ||
| + if (isDetectionPatternCell(i, j)) { | ||
| + if (!handleEyeCell(i, j)) { | ||
| + const corners = getCorners(i, j, true); | ||
| + const neighbors = getNeighbors(i, j); | ||
| + path += renderElement(corners, neighbors, i, j); | ||
| + } | ||
| + processed[i][j] = true; | ||
| + continue; | ||
| + } | ||
| + let runEnd = i; | ||
| + while (runEnd < matrix.length && matrix[runEnd][j] === 1 && !isLogoArea(runEnd, j) && !isDetectionPatternCell(runEnd, j)) { | ||
| + processed[runEnd][j] = true; | ||
| + runEnd++; | ||
| + } | ||
| + if (runEnd - i === 1) { | ||
| + const corners = getCorners(i, j, false); | ||
| + const effectiveCellSize = cellSize - gap; | ||
| + path += `M${corners.center.x} ${corners.center.y} m-${effectiveCellSize / 2}, 0 a${effectiveCellSize / 2},${effectiveCellSize / 2} 0 1,0 ${effectiveCellSize},0 a${effectiveCellSize / 2},${effectiveCellSize / 2} 0 1,0 -${effectiveCellSize},0`; | ||
| + } else { | ||
| + path += renderVerticalPill(j, i, runEnd - 1); | ||
| + } |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The 'line' shape implementation processes the QR matrix column by column (outer loop on j), creating vertical pills. However, the non-null assertion operator (!) is used extensively (e.g., matrix[i]![j], processed[i]![j]). While TypeScript may require this due to how the loops are structured, it could indicate a potential runtime issue if the matrix is not properly initialized. Consider adding defensive checks or restructuring to avoid the need for non-null assertions.
| + path += renderEye(origin.row, origin.col); | ||
| + } | ||
| + return true; | ||
| } |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The handleEyeCell function uses a Set to track rendered eyes with a key format ${origin.row},${origin.col}. This ensures each eye is only rendered once. However, if this function is called multiple times for the same eye pattern cells, the deduplication works correctly. Consider if the early return at line 416 could skip legitimate rendering in edge cases where the origin calculation returns null unexpectedly.
| } | |
| + } else { | |
| + // Defensive: log a warning if origin is unexpectedly null for a detection pattern cell | |
| + if (typeof console !== "undefined" && console.warn) { | |
| + console.warn( | |
| + `[QRCode] Warning: getDetectionPatternOrigin returned null for cell (${i},${j}) in custom eye shape mode.` | |
| + ); | |
| + } | |
| + } |
| + if (cellCenterX < logoAreaX + r && cellCenterY < logoAreaY + r) return inCornerCircle(logoAreaX + r, logoAreaY + r); | ||
| + | ||
| + // Top-right | ||
| + if (cellCenterX > logoAreaX + logoAreaSize - r && cellCenterY < logoAreaY + r) return inCornerCircle(logoAreaX + logoAreaSize - r, logoAreaY + r); | ||
| + | ||
| + // Bottom-right | ||
| + if (cellCenterX > logoAreaX + logoAreaSize - r && cellCenterY > logoAreaY + logoAreaSize - r) return inCornerCircle(logoAreaX + logoAreaSize - r, logoAreaY + logoAreaSize - r); | ||
| + | ||
| + // Bottom-left | ||
| + if (cellCenterX < logoAreaX + r && cellCenterY > logoAreaY + logoAreaSize - r) return inCornerCircle(logoAreaX + r, logoAreaY + logoAreaSize - r); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inCornerCircle function checks if a point is within a circular corner region. However, the logic for the corner checks at lines 293-302 uses > and < comparisons which might miss cells exactly on the boundary. Consider if cells exactly at logoAreaX + r should be included or excluded, and ensure the logic is consistent with the center rectangle checks above.
| + if (cellCenterX < logoAreaX + r && cellCenterY < logoAreaY + r) return inCornerCircle(logoAreaX + r, logoAreaY + r); | |
| + | |
| + // Top-right | |
| + if (cellCenterX > logoAreaX + logoAreaSize - r && cellCenterY < logoAreaY + r) return inCornerCircle(logoAreaX + logoAreaSize - r, logoAreaY + r); | |
| + | |
| + // Bottom-right | |
| + if (cellCenterX > logoAreaX + logoAreaSize - r && cellCenterY > logoAreaY + logoAreaSize - r) return inCornerCircle(logoAreaX + logoAreaSize - r, logoAreaY + logoAreaSize - r); | |
| + | |
| + // Bottom-left | |
| + if (cellCenterX < logoAreaX + r && cellCenterY > logoAreaY + logoAreaSize - r) return inCornerCircle(logoAreaX + r, logoAreaY + logoAreaSize - r); | |
| + if (cellCenterX <= logoAreaX + r && cellCenterY <= logoAreaY + r) return inCornerCircle(logoAreaX + r, logoAreaY + r); | |
| + | |
| + // Top-right | |
| + if (cellCenterX >= logoAreaX + logoAreaSize - r && cellCenterY <= logoAreaY + r) return inCornerCircle(logoAreaX + logoAreaSize - r, logoAreaY + r); | |
| + | |
| + // Bottom-right | |
| + if (cellCenterX >= logoAreaX + logoAreaSize - r && cellCenterY >= logoAreaY + logoAreaSize - r) return inCornerCircle(logoAreaX + logoAreaSize - r, logoAreaY + logoAreaSize - r); | |
| + | |
| + // Bottom-left | |
| + if (cellCenterX <= logoAreaX + r && cellCenterY >= logoAreaY + logoAreaSize - r) return inCornerCircle(logoAreaX + r, logoAreaY + logoAreaSize - r); |
| + const currentEyeShape = eyeShape ?? 'square'; | ||
| + const currentPupilShape = pupilShape ?? 'square'; |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default values for currentEyeShape and currentPupilShape use the nullish coalescing operator (??) with 'square' as the default, but the type definitions suggest these should be optional. When eyeShape or pupilShape are undefined, the eyes will render as squares. However, the original behavior (when useCustomEyeShape is false) is not clear. Consider documenting this behavior change or ensuring backward compatibility.
| + const currentEyeShape = eyeShape ?? 'square'; | |
| + const currentPupilShape = pupilShape ?? 'square'; | |
| + const currentEyeShape = eyeShape ?? eyePatternShape; | |
| + const currentPupilShape = pupilShape ?? eyePatternShape; |
| + const centerX = outerX + outerSize / 2; | ||
| + const centerY = outerY + outerSize / 2; | ||
| + // Outer circle | ||
| + eyePath += `M${centerX - outerRadius} ${centerY} a${outerRadius},${outerRadius} 0 1,0 ${outerSize},0 a${outerRadius},${outerRadius} 0 1,0 -${outerSize},0 `; | ||
| + // Inner cutout circle (reverse winding for cutout) | ||
| + eyePath += `M${centerX - cutoutRadius} ${centerY} a${cutoutRadius},${cutoutRadius} 0 1,1 ${cutoutSize},0 a${cutoutRadius},${cutoutRadius} 0 1,1 -${cutoutSize},0 `; | ||
| + } else if (currentEyeShape === 'rounded') { | ||
| + const outerR = outerSize * 0.35; | ||
| + const cutoutR = cutoutSize * 0.3; | ||
| + // Outer rounded rect |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] In the renderEye function, the reverse winding for cutout paths uses opposite sweep flags (0 1,1 vs 0 1,0) to create holes. Ensure this is tested across different rendering engines, as SVG path winding rules can be implementation-specific. The even-odd fill rule should handle this correctly, but verify the QR codes render properly on both iOS and Android.
| + const yEnd = (endRow + 1) * cellSize - padding; | ||
| + const width = cellSize - gap; | ||
| + const radius = width / 2; | ||
| + return `M${x} ${yStart + radius} A${radius} ${radius} 0 0 1 ${x + width} ${yStart + radius} L${x + width} ${yEnd - radius} A${radius} ${radius} 0 0 1 ${x} ${yEnd - radius} Z`; |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The renderVerticalPill function creates pill-shaped modules for the 'line' shape style. The arc commands use flags 0 0 1 which means: rx-axis-rotation=0, large-arc-flag=0, sweep-flag=1. However, for a semicircle (180 degrees), the large-arc-flag should typically be 0. Verify this creates the intended pill shape visually, especially when the height spans multiple cells.
| + return `M${x} ${yStart + radius} A${radius} ${radius} 0 0 1 ${x + width} ${yStart + radius} L${x + width} ${yEnd - radius} A${radius} ${radius} 0 0 1 ${x} ${yEnd - radius} Z`; | |
| + return `M${x} ${yStart + radius} A${radius} ${radius} 0 0 0 ${x + width} ${yStart + radius} L${x + width} ${yEnd - radius} A${radius} ${radius} 0 0 0 ${x} ${yEnd - radius} Z`; |
Summary