[PM-34125] feat: Add card text analysis pipeline#6720
Conversation
Bitwarden Claude Code ReviewOverall Assessment: APPROVE This PR adds a complete text analysis pipeline for credit card scanning, including ML Kit text recognition integration, regex-based card data parsing with Luhn validation, a camera overlay composable, and card brand detection. The implementation follows established codebase patterns (mirroring the Code Review DetailsNo findings to report. The code is well-structured and follows established patterns. Notable positives:
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #6720 +/- ##
==========================================
- Coverage 85.54% 85.47% -0.07%
==========================================
Files 949 956 +7
Lines 60571 60695 +124
Branches 8574 8634 +60
==========================================
+ Hits 51817 51882 +65
- Misses 5773 5800 +27
- Partials 2981 3013 +32
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
New Issues (128)Checkmarx found the following issues in this Pull Request
|
2333a61 to
d776756
Compare
| ) { | ||
| Canvas(modifier = modifier) { | ||
| val strokeWidthPx = strokeWidth.toPx() | ||
| val cornerRadiusPx = 12.dp.toPx() |
There was a problem hiding this comment.
Should this be passed in as a param too?
There was a problem hiding this comment.
I don't think so. The corner radius is a fixed visual property of the card shape, not something the callers should modify.
| val number = panMatch | ||
| ?.value | ||
| ?.filter { it.isDigit() } | ||
| ?.takeIf { it.isValidLuhn() } |
There was a problem hiding this comment.
Do we want to return null if there is no number.
I noticed that later on you have a check for the number here anyways.
So maybe just return null if it's not valid
There was a problem hiding this comment.
I think that couples the parser logic too tightly to the consumers use case. The purpose of this parser is to extract all of the card data it can. It's up to the consumer to decide whether the extracted data is sufficient for its needs. This feel like the correct separation of concerns boundary to me.
There was a problem hiding this comment.
It just feels very weird to have a data class that has all nullable properties.
But I see your point, maybe that is OK in this case.
There was a problem hiding this comment.
I agree. I'm not against a check to ensure at least some data is parsed, otherwise we return null. That would at least ensure callers don't receive an empty object.
| override lateinit var onCardScanned: (CardScanData) -> Unit | ||
|
|
||
| override fun close() { | ||
| recognizer.close() |
There was a problem hiding this comment.
I am unsure how we will call this when it is provided via a LocalComposition
There was a problem hiding this comment.
Good point. I don't actually think we need it, anyways. At least not until we see actual memory issues. If we need to, we might be able to leverage remember and DisposableEffect to make sure it's closed properly. I'll revert for now and we can come back if issues start showing up.
|
After discussion with Design team, cardholder name extraction will be omitted. It is too error prone, and comparable features in other applications also omit cardholder name when scanning with a camera. |
The base branch was changed.
Add the complete text analysis pipeline for credit card scanning: - CardNumberUtils: sanitize, Luhn validation, brand detection - CardDataParser: interface and implementation for OCR text parsing - CardTextAnalyzer: ML Kit-based camera frame analysis - CardScanOverlay: camera overlay composable - CardScanData: data class for parsed card fields - FakeCardTextAnalyzer: test fixture - LocalProviders: composition local for CardTextAnalyzer
- Move empty digits check into when block as first case - Remove unnecessary @Suppress("MagicNumber") from parseCardData - Remove unused default values from CardScanData - Use @OptIn(ExperimentalGetImage::class) with androidx.annotation.OptIn - Remove default CardDataParserImpl from CardTextAnalyzerImpl constructor
- Add Closeable to CardTextAnalyzerImpl to release ML Kit native resources - Override toString on CardScanData to mask sensitive fields - Add missing RuPay brand detection test coverage
Instead of returning a CardScanData with all null properties, return null to avoid creating meaningless objects. Consolidate tests to assert full objects rather than individual properties.
LocalComposition doesn't provide a lifecycle hook for cleanup, so close() would never be invoked. Removes Closeable to stay consistent with QrCodeAnalyzerImpl.
OCR-based name detection is unreliable and inconsistent per Design team decision. Remove the field, regex, and related tests.
35fa5f1 to
8299421
Compare
| } | ||
| } | ||
|
|
||
| @Suppress("MagicNumber") |
There was a problem hiding this comment.
Is this suppression needed?


🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-34125
📔 Objective
Add the complete text analysis pipeline for credit card scanning:
detectCardBrand) usingVaultCardBrandIncludes full test coverage for parser logic, Luhn validation, and sanitization.