-
Notifications
You must be signed in to change notification settings - Fork 21
feat: implement New design with hard coded support for United States merchants #729
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
Conversation
… display, and country notice - Add enabled field to ExplorePointOfUse.Merchant model for CTX API integration - Update subtitleForCTX to use denominationsType field and handle "min-max" as flexible amounts - Implement "Temporarily unavailable" state based on merchantInfo.enabled from CTX API - Update buy button state to respect CTX API enabled status with fallback to local active status - Add comprehensive debugging for merchant field investigation - Fix GameStop and other merchants to correctly show "Flexible amounts" instead of "Fixed amounts" - Add createCountryNotice function to display country-specific gift card usage information - Add gift card icon asset for improved UI consistency - Create iOS development patterns documentation for future sessions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
WalkthroughAdds documentation and developer guides, bumps MARKETING_VERSION to 8.4.2, introduces a gift-card icon asset, extends merchant model with an optional enabled flag and fallback logic, refactors details and list UIs with map/drag behaviors and location integration, hardens map/annotation safety, threads filters/map bounds through flows, and adds merchant-availability checks in payments. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant DetailsVC as PointOfUseDetailsViewController
participant CTX as CTXSpendService
participant UI as PointOfUseDetailsView
User->>DetailsVC: Open merchant
DetailsVC->>CTX: tryRefreshCtxToken + fetch merchant info
CTX-->>DetailsVC: MerchantInfo(denominations, enabled?)
DetailsVC->>DetailsVC: updatingMerchant(..., enabled)
DetailsVC->>UI: refreshDetailsViewWithUpdatedMerchant
UI-->>User: Updated details (enabled state considered)
alt Error fetching
CTX-->>DetailsVC: Error
DetailsVC->>UI: Show existing data (no enabled) / fallback
end
sequenceDiagram
autonumber
participant List as ExplorePointOfUseListVC
participant Map as ExploreMapView
participant Provider as AllMerchantLocationsDataProvider
List->>Map: Display / pan map
Map-->>List: didChangeVisibleBounds(bounds)
List->>Provider: items(..., currentFilters, currentMapBounds=bounds)
alt Location denied/needs auth
Provider->>Provider: finalBounds=nil (global)
else Bounds provided
Provider->>Provider: Use currentMapBounds
else Authorized with location
Provider->>Provider: Compute MKCircle(rect from radius)
end
Provider-->>List: Paginated active locations
List-->>List: Update UI list
sequenceDiagram
autonumber
actor User
participant VM as DashSpendPayViewModel
participant API as CTXSpendService
User->>VM: purchaseGiftCardAndPay()
VM->>VM: guard isMerchantEnabled
alt Disabled
VM-->>User: Error: merchantUnavailable
else Enabled
VM->>API: purchaseGiftCard(...)
API-->>VM: Result
VM-->>User: Success or error
end
Note over VM,API: updateMerchantInfo() updates isMerchantEnabled from API.enabled
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift (1)
281-286: Prevent purchases when merchant is disabled (defense-in-depth).UI may block the button, but add a server-side guard in the flow too.
@@ func purchaseGiftCardAndPay() async throws -> Data { isProcessingPayment = true defer { isProcessingPayment = false } + guard isMerchantEnabled else { + DSLogger.log("Purchase blocked: merchant disabled") + throw CTXSpendError.paymentProcessingError("Merchant temporarily unavailable") + } @@ private func purchaseGiftCardAPI() async throws -> GiftCardResponse { - guard !merchantId.isEmpty, ctxSpendService.isUserSignedIn else { + guard !merchantId.isEmpty, ctxSpendService.isUserSignedIn else { DSLogger.log("Purchase gift card failed: User not signed in or merchant ID is empty") throw CTXSpendError.unauthorized } + guard isMerchantEnabled else { + DSLogger.log("CTX API call blocked: merchant disabled") + throw CTXSpendError.paymentProcessingError("Merchant temporarily unavailable") + }Also applies to: 166-186
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (1)
33-44: Crash risk: forced unwrap of coordinates.
pointOfUse.latitude!/longitude!can be nil; this will crash. Unwrap safely and hide the subtitle otherwise.- let distance = CLLocation(latitude: pointOfUse.latitude!, longitude: pointOfUse.longitude!) - .distance(from: currentLocation) + guard let lat = pointOfUse.latitude, let lon = pointOfUse.longitude else { + subLabel.isHidden = true + return + } + let distance = CLLocation(latitude: lat, longitude: lon) + .distance(from: currentLocation)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
49-54: Prevent IUO crash when mapView is nil in viewDidLayoutSubviews
mapView?.contentInset = ... mapView.frame.height ...dereferencesmapVieweven when it’s nil. Also guarddetailsView.override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - - mapView?.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: mapView.frame.height - detailsView.frame.height - 10, - right: 0) + guard let mapView = mapView, let detailsView = detailsView else { return } + let bottomInset = max(0, mapView.frame.height - detailsView.frame.height - 10) + mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottomInset, right: 0) }
282-309: Nil-safe details view construction
detailsView(for:)returns optional but the code immediately dereferencesdetailsView. Use a guarded unwrap to avoid runtime crashes when category is.unknown.- detailsView = detailsView(for: pointOfUse) - detailsView.payWithDashHandler = payWithDashHandler + guard let builtDetailsView = detailsView(for: pointOfUse) else { return } + detailsView = builtDetailsView + builtDetailsView.payWithDashHandler = payWithDashHandler - detailsView.sellDashHandler = sellDashHandler + builtDetailsView.sellDashHandler = sellDashHandler - detailsView.showAllLocationsActionBlock = { [weak self] in + builtDetailsView.showAllLocationsActionBlock = { [weak self] in ... - detailsView.buyGiftCardHandler = { [weak self] in + builtDetailsView.buyGiftCardHandler = { [weak self] in ... - detailsView.dashSpendAuthHandler = { [weak self] in + builtDetailsView.dashSpendAuthHandler = { [weak self] in ... - detailsView.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(detailsView) + builtDetailsView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(builtDetailsView) ... - detailsView.topAnchor.constraint(equalTo: contentView.topAnchor), + builtDetailsView.topAnchor.constraint(equalTo: contentView.topAnchor), ... - detailsView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - detailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + builtDetailsView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + builtDetailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
176-180: Harden websiteAction URL handlingAdd https:// when the merchant URL is missing a scheme.
@objc func websiteAction() { - guard let website = merchant.website, let url = URL(string: website) else { return } + guard var website = merchant.website, !website.isEmpty else { return } + if !website.lowercased().hasPrefix("http") { website = "https://\(website)" } + guard let url = URL(string: website) else { return } UIApplication.shared.open(url, options: [:], completionHandler: nil) }
🧹 Nitpick comments (19)
DashWallet/Resources/AppAssets.xcassets/gift-card-icon.imageset/Contents.json (1)
1-12: Set icon as template + preserve vector; confirm minimum iOS support for SVG assets.
- If you intend to tint this icon, mark it as a template image and preserve vector data.
- Ensure the deployment target is iOS 13+ (SVG support in asset catalogs starts there). Otherwise, consider raster fallbacks. (sarunw.com, avanderlee.com)
Apply this diff:
{ "images" : [ { "filename" : "gift-card-icon.svg", "idiom" : "universal" } ], + "properties" : { + "template-rendering-intent" : "template", + "preserves-vector-representation" : true + }, "info" : { "author" : "xcode", "version" : 1 } }DashWallet.xcodeproj/project.pbxproj (1)
10356-10356: Centralize MARKETING_VERSION to avoid drift across targets.Consider moving MARKETING_VERSION to a shared .xcconfig (e.g., Config/Version.xcconfig) and referencing it from all targets to keep them in lockstep.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (1)
89-90: Unify default radius across list and mapList controller reportedly uses 5 miles; this view now uses 1 mile. Pick one and centralize (e.g., ExploreMapConstants.defaultMiles) to avoid UX whiplash.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift (2)
64-70: Button height consistencyOnly payButton has an explicit height. When both buttons are present, they should share height. Either constrain both to 48 or tie sellButton’s height to payButton’s.
Example patch (after creating sellButton):
sellButton.heightAnchor.constraint(equalTo: payButton.heightAnchor).isActive = true
28-28: Trim trailing whitespace flagged by SwiftLintMinor cleanliness.
Also applies to: 30-30, 36-36, 40-40, 63-63
.claude/agents/ios-development-patterns.md (4)
143-164: SwiftUI snippet conflicts with UIKit realityThe doc shows struct PointOfUseDetailsView: View with Combine/SwiftUI patterns, but in code PointOfUseDetailsView is a UIKit class. Clarify that this is pseudo‑code or provide a UIKit‑accurate example to avoid confusion.
450-458: Invalid Swift sample: nil-coalescing Bool? with StringThe prints use ?? "nil" on Bool?, which won’t compile. Use String mapping.
#if DEBUG extension MerchantViewModel { func debugDataFlow() { print("🎯 CTX Data: \(ctxMerchant?.enabled.map(String.init) ?? "nil")") print("🎯 Local Data: \(localMerchant?.acceptsDash.map(String.init) ?? "nil")") print("🎯 Final Value: \((merchant?.safeEnabled).map(String.init) ?? "nil")") } } #endif
499-509: Migration snippet: add IF NOT EXISTS and idempotency notesTo make the example safer to re-run, include IF NOT EXISTS and guard the UPDATE.
ALTER TABLE IF NOT EXISTS merchant ADD COLUMN enabled BOOLEAN DEFAULT 1; -- Only backfill when the column exists and value is NULL UPDATE merchant SET enabled = acceptsDash WHERE enabled IS NULL;Add a reminder in the doc to wrap migrations in a recorded “applied” table/flag.
63-91: Sample accessors reference undefined propertiessafeEnabled uses ctxEnabled/localEnabled that aren’t on Merchant in the sample. Either inline the resolution or show where these come from to prevent copy/paste errors.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (2)
26-26: Remove the unused distanceLabel or make it non-IUO.You render distance via
subLabeland immediately hidedistanceLabel. Either remove it (preferred) or avoid IUO (UILabel!) per SwiftLint.Option A (remove):
- private var distanceLabel: UILabel! + // distanceLabel removed; subLabel is the canonical distance UI @@ - // Hide separate distance label since we're using subLabel - distanceLabel.isHidden = true @@ - // Distance label (separate from merchant name) - distanceLabel = UILabel() - distanceLabel.translatesAutoresizingMaskIntoConstraints = false - distanceLabel.font = .systemFont(ofSize: 12, weight: .regular) - distanceLabel.textColor = .dw_tertiaryText() - distanceLabel.isHidden = true - distanceLabel.textAlignment = .right - mainStackView.addArrangedSubview(distanceLabel)Option B (keep, but safe):
- private var distanceLabel: UILabel! + private var distanceLabel = UILabel() @@ - distanceLabel = UILabel() distanceLabel.translatesAutoresizingMaskIntoConstraints = falseAlso applies to: 46-47, 66-74
45-45: Trim trailing whitespace.DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift (1)
64-76: Clarifyenabledsemantics and add helper for fallback logic.Document that CTX may omit
enabledand provide a helper to merge with localactive.let redeemType: String? - let enabled: Bool? + /// CTX "enabled" flag. If nil, the state is unknown (fallback to local "active"). + let enabled: Bool? @@ - init(merchantId: String, paymentMethod: PaymentMethod, type: `Type`, deeplink: String?, savingsBasisPoints: Int, denominationsType: String?, denominations: [Int] = [], redeemType: String?, enabled: Bool? = nil) { + init( + merchantId: String, + paymentMethod: PaymentMethod, + type: `Type`, + deeplink: String?, + savingsBasisPoints: Int, + denominationsType: String?, + denominations: [Int] = [], + redeemType: String?, + enabled: Bool? = nil + ) { @@ self.redeemType = redeemType self.enabled = enabled } + + /// Effective enabled status given local "active" fallback. + func isEnabled(fallbackActive: Bool) -> Bool { + enabled ?? fallbackActive + }Also addresses the SwiftLint long-line warning.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (3)
25-26: “Max 50% of screen height” comment mismatches constant. Consider dynamic value.-internal let kDefaultOpenedMapPosition: CGFloat = 320.0 // Max 50% of screen height +internal var kDefaultOpenedMapPosition: CGFloat { min(UIScreen.main.bounds.height * 0.5, 320.0) } // Cap at 50% or 320pt
532-579: Improve pan UX: update contentInset while dragging and clamp consistently.Currently, you allow dragging to 80% height but snap to 320 on end; update inset during
.changedfor visual consistency and optionally clamp to the same range you’ll snap to.case .changed: - // Only handle vertical movement and constrain within bounds - let newY = currentY + translatedPoint.y - - // Constrain movement between closed position and maximum expanded position - let maxY = view.frame.size.height * 0.8 // Allow dragging down to 80% of screen - let minY = kDefaultClosedMapPosition // Don't allow dragging above closed position - - contentViewTopLayoutConstraint.constant = max(minY, min(maxY, newY)) + let newY = currentY + translatedPoint.y + // Constrain between closed and opened positions for predictable snapping + let minY = kDefaultClosedMapPosition + let maxY = kDefaultOpenedMapPosition + let clampedY = max(minY, min(maxY, newY)) + contentViewTopLayoutConstraint.constant = clampedY + // Keep map inset in sync while dragging + self.mapView.contentInset = .init(top: 0, left: 0, bottom: self.mapView.frame.height - clampedY, right: 0) sender.setTranslation(.zero, in: view)If you do want 80% drag freedom, consider snapping thresholds that include that range or animate to that expanded value too.
567-570: Parameter alignment + trailing whitespace (SwiftLint).- UIView.animate(withDuration: animationDuration, delay: 0, - usingSpringWithDamping: 0.8, initialSpringVelocity: 0, - options: .curveEaseOut) { + UIView.animate(withDuration: animationDuration, delay: 0, + usingSpringWithDamping: 0.8, initialSpringVelocity: 0, + options: .curveEaseOut) {Also trim trailing spaces on nearby lines 540, 544, 567–568.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
179-223: Remove dead Map button code (unused + potential nil usage)
showMapButtonand related methods are no longer wired; keeping them risks accidental calls against an uninitialized button. Recommend removing the property and these methods.- private func setupMapButton() { ... } - @objc - private func showMapAction() { ... } - private func updateMapButtonVisibility() { ... }Also remove
private var showMapButton: UIButton!at Line 36.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (3)
789-811: Country notice is hard-coded to USIf this should only show for US merchants/users, add a condition (e.g., merchant country or device locale). If “US-only” is intentional for this release, ignore.
Would you like a quick follow-up patch to gate this on a country check?
594-600: Replace placeholder location count
getLocationCount()returns a hard-coded 11. Wire this up to the real data source and pass it from the VC.I can add a small API on the VC to inject the count and remove this placeholder.
25-51: Reduce IUOs for view properties
grabberContainer,logoImageView, etc. as IUOs are fragile. Prefer optional or lazyletwhere possible.If desired, I can prepare a small refactor to remove IUOs without changing behavior.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
DashWallet/Resources/AppAssets.xcassets/gift-card-icon.imageset/gift-card-icon.svgis excluded by!**/*.svg
📒 Files selected for processing (12)
.claude/agents/ios-development-patterns.md(1 hunks)DashWallet.xcodeproj/project.pbxproj(12 hunks)DashWallet/Resources/AppAssets.xcassets/gift-card-icon.imageset/Contents.json(1 hunks)DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift(2 hunks)DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(7 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(10 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift(3 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift(3 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift(1 hunks)DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift(2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-05T04:46:12.717Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#728
File: DashWallet.xcodeproj/project.pbxproj:1595-1599
Timestamp: 2025-09-05T04:46:12.717Z
Learning: In iOS projects, the `DashWallet.xcodeproj/project.pbxproj` file is automatically generated and managed by Xcode. Manual changes to this file should not be made, and the changes shown in diffs are typically the result of Xcode updating project configuration, dependencies, or build settings.
Applied to files:
DashWallet/Resources/AppAssets.xcassets/gift-card-icon.imageset/Contents.jsonDashWallet.xcodeproj/project.pbxproj
📚 Learning: 2025-08-25T21:01:26.493Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#723
File: DashWallet/da.lproj/Localizable.strings:2987-2987
Timestamp: 2025-08-25T21:01:26.493Z
Learning: The DashWallet project uses Transifex for translation management rather than direct manual edits to .lproj/Localizable.strings files.
Applied to files:
DashWallet.xcodeproj/project.pbxproj
📚 Learning: 2025-09-05T04:46:21.894Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#728
File: DashWallet.xcodeproj/project.pbxproj:3174-3180
Timestamp: 2025-09-05T04:46:21.894Z
Learning: Xcode project files (.pbxproj) are automatically generated by Xcode and should not be manually edited. Hash changes in Pod library references (like libPods-dashwallet.a) are normal and occur when CocoaPods updates or when Xcode regenerates the project structure.
Applied to files:
DashWallet.xcodeproj/project.pbxproj
🧬 Code graph analysis (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (6)
DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/UI/Views/BaseController/BaseViewController+NetworkReachability.swift (1)
stopNetworkMonitoring(52-54)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
callAction(84-99)payAction(79-82)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift (1)
configureGiftCardSection(22-71)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (3)
locationManagerDidChangeCurrentLocation(283-285)locationManagerDidChangeCurrentReversedLocation(295-295)locationManagerDidChangeServiceAvailability(287-293)DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift (1)
toSavingPercentages(78-80)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift (1)
update(41-49)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/AtmItemCell.swift (1)
update(28-48)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (2)
configureGiftCardSection(276-286)createHeaderSection(658-728)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (3)
setupInfoButton(272-276)configureHierarchy(232-238)infoButtonAction(278-283)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (3)
configureHierarchy(203-226)callAction(130-158)setupGrabberPanGesture(1084-1095)DashWallet/Sources/Categories/UIHostingController+DashWallet.swift (1)
setDetent(21-31)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (3)
mapView(239-244)mapView(246-256)mapView(272-278)
🪛 LanguageTool
.claude/agents/ios-development-patterns.md
[grammar] ~9-~9: There might be a mistake here.
Context: ... #### Local vs Remote Database Confusion Critical Understanding: There are two ...
(QB_NEW_EN)
[grammar] ~12-~12: There might be a mistake here.
Context: ...ems: 1. Local Database (explore.db) - SQLite database stored locally on device...
(QB_NEW_EN)
[grammar] ~13-~13: There might be a mistake here.
Context: ...SQLite database stored locally on device - Contains cached merchant data and user p...
(QB_NEW_EN)
[grammar] ~14-~14: There might be a mistake here.
Context: ...ached merchant data and user preferences - Managed through `DatabaseConnection.swif...
(QB_NEW_EN)
[grammar] ~15-~15: There might be a mistake here.
Context: ...d user preferences - Managed through DatabaseConnection.swift - Used for offline functionality and quick...
(QB_NEW_EN)
[grammar] ~18-~18: There might be a mistake here.
Context: ...base.appspot.com/explore/explore-v3.db`)** - Firebase-hosted SQLite database - Sou...
(QB_NEW_EN)
[grammar] ~19-~19: There might be a mistake here.
Context: ...)** - Firebase-hosted SQLite database - Source of truth for merchant information...
(QB_NEW_EN)
[grammar] ~20-~20: There might be a mistake here.
Context: ...Source of truth for merchant information - Downloaded and synced periodically - ...
(QB_NEW_EN)
[grammar] ~21-~21: There might be a mistake here.
Context: ... - Downloaded and synced periodically - Contains the complete merchant directory...
(QB_NEW_EN)
[grammar] ~53-~53: There might be a mistake here.
Context: ...e` (represents payment type flexibility) - Always check for both old and new field ...
(QB_NEW_EN)
[grammar] ~57-~57: There might be a mistake here.
Context: ...ft card vendors (GameStop, Target, etc.) - merchant: Main merchant directory table - **Rela...
(QB_NEW_EN)
[grammar] ~58-~58: There might be a mistake here.
Context: ...merchant`: Main merchant directory table - Relationship: Some merchants may be li...
(QB_NEW_EN)
[grammar] ~196-~196: There might be a mistake here.
Context: ...tions #### Required Initializer Updates Rule: When adding properties to Swift ...
(QB_NEW_EN)
[grammar] ~319-~319: There might be a mistake here.
Context: ...itfall 1: Confusing Local vs Remote Data Problem: Querying wrong database or as...
(QB_NEW_EN)
[grammar] ~320-~320: There might be a mistake here.
Context: ...using Local vs Remote Data Problem: Querying wrong database or assuming data exists ...
(QB_NEW_EN)
[grammar] ~320-~320: There might be a mistake here.
Context: ...atabase or assuming data exists locally. Solution: ```swift // Always specif...
(QB_NEW_EN)
[grammar] ~488-~488: There might be a mistake here.
Context: ...ion #### 1. Property Addition Checklist - [ ] Add property to model struct/class -...
(QB_NEW_EN)
[grammar] ~489-~489: There might be a mistake here.
Context: ...- [ ] Add property to model struct/class - [ ] Update all initializers with default...
(QB_NEW_EN)
[grammar] ~490-~490: There might be a mistake here.
Context: ...ate all initializers with default values - [ ] Update database schema if needed - [...
(QB_NEW_EN)
[grammar] ~491-~491: There might be a mistake here.
Context: ...s - [ ] Update database schema if needed - [ ] Add migration script for existing da...
(QB_NEW_EN)
[grammar] ~492-~492: There might be a mistake here.
Context: ...] Add migration script for existing data - [ ] Update API serialization/deserializa...
(QB_NEW_EN)
[grammar] ~493-~493: There might be a mistake here.
Context: ...Update API serialization/deserialization - [ ] Add unit tests for new property - [ ...
(QB_NEW_EN)
[grammar] ~494-~494: There might be a mistake here.
Context: ...on - [ ] Add unit tests for new property - [ ] Update UI components that use the mo...
(QB_NEW_EN)
[grammar] ~534-~534: There might be a mistake here.
Context: ...Points ### CTXSpendService Architecture - Location: `DashWallet/Sources/Models/S...
(QB_NEW_EN)
[grammar] ~535-~535: There might be a mistake here.
Context: ...endService Architecture - Location: DashWallet/Sources/Models/Services/CTXSpendService.swift - Purpose: Handles communication with CT...
(QB_NEW_EN)
[grammar] ~537-~537: There might be a mistake here.
Context: ...API for merchant data - Key Methods: - refreshTokenAndMerchantInfo(): Updates merchant data from CTX - `ge...
(QB_NEW_EN)
[grammar] ~542-~542: There might be a mistake here.
Context: ... ### PointOfUseDetailsView Architecture - Location: `DashWallet/Sources/UI/Explo...
(QB_NEW_EN)
[grammar] ~543-~543: There might be a mistake here.
Context: ...etailsView Architecture - Location: DashWallet/Sources/UI/Explore/PointOfUseDetailsView.swift - Purpose: Displays detailed merchant in...
(QB_NEW_EN)
[grammar] ~545-~545: There might be a mistake here.
Context: ...dService → updatingMerchant → UI refresh - Key Pattern: Combine publishers trigge...
(QB_NEW_EN)
[grammar] ~548-~548: There might be a mistake here.
Context: ...es ### ExplorePointOfUse.Merchant Model - Location: Model definitions for mercha...
(QB_NEW_EN)
[grammar] ~549-~549: There might be a mistake here.
Context: ...definitions for merchant data structures - Evolution: Handles both legacy and cur...
(QB_NEW_EN)
[grammar] ~550-~550: There might be a mistake here.
Context: ...dles both legacy and current field names - Integration: Works with both CTX API a...
(QB_NEW_EN)
[grammar] ~555-~555: There might be a mistake here.
Context: ...ctices Summary ### Development Workflow 1. Always verify data sources before impl...
(QB_NEW_EN)
[grammar] ~562-~562: There might be a mistake here.
Context: ...rty addition checklist ### Code Quality 1. Prefer explicit over implicit when dea...
(QB_NEW_EN)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 80-80: Force unwrapping should be avoided
(force_unwrapping)
[Warning] 81-81: Line should be 120 characters or less; currently it has 159 characters
(line_length)
[Warning] 82-82: Line should be 120 characters or less; currently it has 154 characters
(line_length)
[Warning] 84-84: Line should be 120 characters or less; currently it has 156 characters
(line_length)
[Warning] 78-78: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 130-130: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 133-133: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 138-138: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 142-142: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 143-143: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 145-145: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 147-147: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 153-153: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 159-159: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 205-205: Prefer object literals over image and color inits
(object_literal)
[Warning] 206-206: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 209-209: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 276-276: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 382-382: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 434-434: Line should be 120 characters or less; currently it has 181 characters
(line_length)
[Warning] 547-547: Line should be 120 characters or less; currently it has 125 characters
(line_length)
[Warning] 617-617: Line should be 120 characters or less; currently it has 126 characters
(line_length)
[Warning] 350-350: Prefer object literals over image and color inits
(object_literal)
[Warning] 481-481: Prefer object literals over image and color inits
(object_literal)
[Warning] 547-547: Prefer object literals over image and color inits
(object_literal)
[Warning] 247-247: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 250-250: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 255-255: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 259-259: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 261-261: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 265-265: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 269-269: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 278-278: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 287-287: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 295-295: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 303-303: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 307-307: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 315-315: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 323-323: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 331-331: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 335-335: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 339-339: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 343-343: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 352-352: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 363-363: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 366-366: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 374-374: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 377-377: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 389-389: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 395-395: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 402-402: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 408-408: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 414-414: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 421-421: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 429-429: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 439-439: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 445-445: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 448-448: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 454-454: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 459-459: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 464-464: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 468-468: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 473-473: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 478-478: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 484-484: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 488-488: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 491-491: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 494-494: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 501-501: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 504-504: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 510-510: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 519-519: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 527-527: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 530-530: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 534-534: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 539-539: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 544-544: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 551-551: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 554-554: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 556-556: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 564-564: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 567-567: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 570-570: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 574-574: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 586-586: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 590-590: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 593-593: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 600-600: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 604-604: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 609-609: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 614-614: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 623-623: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 626-626: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 628-628: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 636-636: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 639-639: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 642-642: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 654-654: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 657-657: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 661-661: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 668-668: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 677-677: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 683-683: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 692-692: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 701-701: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 708-708: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 711-711: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 714-714: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 716-716: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 726-726: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 729-729: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 733-733: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 739-739: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 746-746: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 751-751: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 756-756: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 759-759: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 763-763: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 766-766: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 775-775: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 777-777: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 785-785: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 788-788: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 792-792: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 800-800: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 802-802: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 809-809: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 816-816: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 814-814: Limit vertical whitespace to a single empty line; currently 3
(vertical_whitespace)
[Warning] 908-908: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 947-947: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 981-981: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 959-959: Prefer object literals over image and color inits
(object_literal)
[Warning] 869-869: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 872-872: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 874-874: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 889-889: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 892-892: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 898-898: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 906-906: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 909-909: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 916-916: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 919-919: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 925-925: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 933-933: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 940-940: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 945-945: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 948-948: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 951-951: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 954-954: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 961-961: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 966-966: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 968-968: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 974-974: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 979-979: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 982-982: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 986-986: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 989-989: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 996-996: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1010-1010: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1014-1014: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1021-1021: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 898-898: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 940-940: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 974-974: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1054-1054: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1063-1063: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1069-1069: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1075-1075: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1082-1082: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1083-1083: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1085-1085: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1087-1087: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1096-1096: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 1083-1083: Limit vertical whitespace to a single empty line; currently 2
(vertical_whitespace)
[Warning] 1096-1096: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift
[Warning] 26-26: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift
[Error] 66-66: Line should be 200 characters or less; currently it has 220 characters
(line_length)
[Warning] 65-65: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift
[Warning] 28-28: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 30-30: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 36-36: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 40-40: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 63-63: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 36-36: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 67-67: Line should be 120 characters or less; currently it has 121 characters
(line_length)
[Warning] 68-68: Line should be 120 characters or less; currently it has 124 characters
(line_length)
[Warning] 160-160: Line should be 120 characters or less; currently it has 136 characters
(line_length)
[Warning] 210-210: Line should be 120 characters or less; currently it has 132 characters
(line_length)
[Warning] 71-71: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 78-78: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 90-90: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 94-94: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 100-100: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 104-104: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 106-106: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 108-108: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 111-111: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 116-116: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 125-125: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 128-128: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 137-137: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 139-139: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 150-150: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 159-159: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 167-167: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 178-178: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 192-192: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 195-195: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 203-203: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 209-209: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 214-214: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 217-217: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 264-264: Force unwrapping should be avoided
(force_unwrapping)
[Warning] 260-260: Line should be 120 characters or less; currently it has 127 characters
(line_length)
[Warning] 261-261: Line should be 120 characters or less; currently it has 134 characters
(line_length)
[Warning] 256-256: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 262-262: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 309-309: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift
[Warning] 540-540: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 544-544: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 567-567: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 568-568: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 568-568: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 569-569: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
🔇 Additional comments (19)
DashWallet.xcodeproj/project.pbxproj (11)
10496-10496: LGTM: Version bump is consistent.
10670-10670: LGTM: WatchApp target reflects 8.4.2.
10692-10692: LGTM: WatchApp target (other config) updated too.
10717-10717: LGTM: WatchKit extension updated.
10744-10744: LGTM: WatchKit extension (other config) updated.
11484-11484: LGTM: iOS target config shows 8.4.2.
11653-11653: LGTM: WatchApp config aligned.
11678-11678: LGTM: WatchKit extension config aligned.
11795-11795: LGTM: iOS target config aligned.
11962-11962: LGTM: WatchApp config aligned.
11987-11987: LGTM: WatchKit extension config aligned.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift (1)
22-41: Method naming/context checkOverriding configureGiftCardSection() for an ATM view reads oddly. Confirm the superclass actually calls this for ATM flows (vs a header/location section hook) to avoid dead code.
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift (1)
58-58: Confirm DTO optionality and fallback seeding
I couldn’t locateExplorePointOfUseor the type ofmerchantInfo.enabledin the codebase—please confirm thatmerchant.merchant?.enabledis an optionalBool(and if not, simplify to direct assignment) and seedisMerchantEnabledfrommerchant.activeuntil the CTX fetch completes.DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift (1)
250-252: Confirm DB persistence forenabledinExplorePointOfUse
TheRowDecodableinitializer never callsrow.get(ExplorePointOfUse.enabled)(the property is declared at line 64 but only set to its default), soenabledalways remainsniland falls back to.active. If you intend to persist this flag, add theenabledcolumn, update the decoder withrow.get(ExplorePointOfUse.enabled)and migrate the DB; otherwise remove or ignore it.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
466-480: Merchant.enabled propagation looks correctThe updated
updatingMerchantcorrectly carries over all existing fields and overridesenabledwhen provided.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
865-871: Button enabling logic aligns with PR goals
isEnabled = m.enabled ?? merchant.activewith network and sync gating matches “Temporarily unavailable” behavior.
658-726: Header layout looks solidGood use of content hugging/compression to prevent truncation and maintain logo sizing.
730-787: CTX section copy and discount formatting look correct“Temporarily unavailable” path and savings percentage rendering are consistent with model fields.
1046-1095: Grabber implementation is clean and reusableGood separation and constraints; pairs nicely with controller’s pan handler.
...allet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
Show resolved
Hide resolved
...allet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
Outdated
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift
Outdated
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift
Outdated
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
...llet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift
Outdated
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift
Show resolved
Hide resolved
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.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
408-416: UI not refreshed after CTX merchant update; details view remains stale.The updated enabled/denominations from CTX are not applied to the on-screen PointOfUseDetailsView created earlier.
Minimal fix: rebuild details view after updating pointOfUse.
if try await tryRefreshCtxToken(), let merchantId = pointOfUse.merchant?.merchantId { let merchantInfo = try await CTXSpendService.shared.getMerchant(merchantId: merchantId) pointOfUse = pointOfUse.updatingMerchant( denominationsType: merchantInfo.denominationsType, denominations: merchantInfo.denominations.compactMap { Int($0) }, enabled: merchantInfo.enabled ) + await MainActor.run { + self.rebuildDetailsView(with: self.pointOfUse) + } }Add this helper (outside the changed hunk):
private func rebuildDetailsView(with pou: ExplorePointOfUse) { detailsView.removeFromSuperview() detailsView = detailsView(for: pou) detailsView.payWithDashHandler = payWithDashHandler detailsView.sellDashHandler = sellDashHandler detailsView.buyGiftCardHandler = { [weak self] in self?.showDashSpendPayScreen() } detailsView.dashSpendAuthHandler = { [weak self] in self?.showCTXSpendLoginInfo() } detailsView.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(detailsView) NSLayoutConstraint.activate([ detailsView.topAnchor.constraint(equalTo: contentView.topAnchor), detailsView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), detailsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), ]) }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
176-180: Normalize website URL (trim + scheme) before opening.Matches controller behavior; handles scheme-less inputs.
- guard let website = merchant.website, let url = URL(string: website) else { return } + guard var website = merchant.website?.trimmingCharacters(in: .whitespacesAndNewlines), !website.isEmpty else { return } + if !website.lowercased().hasPrefix("http") { website = "https://" + website } + guard let url = URL(string: website) else { return } UIApplication.shared.open(url, options: [:], completionHandler: nil)
♻️ Duplicate comments (3)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
173-186: Harden websiteAction URL normalization (trim + scheme check, case-insensitive).Handles uppercase schemes/whitespace; avoids invalid URLs.
Apply:
- guard let website = pointOfUse.website else { return } - - // Normalize URL by adding https scheme if missing - let normalizedWebsite: String - if website.hasPrefix("http://") || website.hasPrefix("https://") { - normalizedWebsite = website - } else { - normalizedWebsite = "https://" + website - } - - guard let url = URL(string: normalizedWebsite) else { return } + guard var website = pointOfUse.website?.trimmingCharacters(in: .whitespacesAndNewlines), !website.isEmpty else { return } + if !website.lowercased().hasPrefix("http") { website = "https://" + website } + guard let url = URL(string: website) else { return }
267-275: Remove force unwrap of contentViewTopConstraint.Prevents crash; aligns with prior lint feedback.
- // Create the top constraint and store reference to it - // Start higher than bottom half by about an inch (72 points) - let screenHeight = UIScreen.main.bounds.height - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Higher by about an inch - 72 // Higher by about an inch - contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) - - constraint = [ - contentViewTopConstraint!, + // Create the top constraint and store reference to it + let screenHeight = UIScreen.main.bounds.height + let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 + let topConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + contentViewTopConstraint = topConstraint + + constraint = [ + topConstraint,DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
130-158: Gate verbose phone debug prints behind DEBUG.Prevents noisy prod logs and potential PII leakage.
- print("DEBUG: Original phone: \(phone)") + #if DEBUG + print("DEBUG: Original phone: \(phone)") + #endif ... - print("DEBUG: Extracted digits: \(digits)") + #if DEBUG + print("DEBUG: Extracted digits: \(digits)") + #endif ... - print("DEBUG: URL string: \(urlString)") + #if DEBUG + print("DEBUG: URL string: \(urlString)") + #endif ... - print("DEBUG: Failed to create URL") + #if DEBUG + print("DEBUG: Failed to create URL") + #endif ... - print("DEBUG: Cannot open URL - phone not available") + #if DEBUG + print("DEBUG: Cannot open URL - phone not available") + #endif ... - print("DEBUG: Opening URL: \(url)") + #if DEBUG + print("DEBUG: Opening URL: \(url)") + #endif - UIApplication.shared.open(url, options: [:], completionHandler: { success in - print("DEBUG: URL open result: \(success)") - }) + UIApplication.shared.open(url, options: [:], completionHandler: { success in + #if DEBUG + print("DEBUG: URL open result: \(success)") + #endif + })
🧹 Nitpick comments (11)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendEndpoint.swift (1)
83-89: Method choice and auth look correct; consider avoiding token refresh for logout.POST + Bearer is standard. Optional: ensure the 401-refresh retry in CTXSpendAPI does not refresh tokens for .logout (users typically don’t want a refresh during sign-out). If desired, special-case .logout in CTXSpendAPI’s unauthorized handler.
// In CTXSpendAPI.request overrides, early-return on .logout for 401 without refresh if case .logout = target { throw HTTPClientError.statusCode(r) }DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (2)
91-107: UX: sign-out state is delayed until the network call returns.If the UI should reflect sign-out immediately, optimistically clear auth state first (or expose a completion to allow views to await). Otherwise, show a brief progress indicator during server revoke.
func logout() { - // Attempt to revoke token on server before clearing local storage - Task { + // Option A: Optimistic local sign-out, then best-effort server revoke + updateSignInState() // if you also delete tokens here, move the block below + Task { do { try await CTXSpendAPI.shared.request(.logout) } catch { DSLogger.log("Logout API call failed: \(error). Proceeding with local cleanup.") } // Always clear local tokens regardless of server response await MainActor.run { KeychainService.delete(key: Keys.accessToken) KeychainService.delete(key: Keys.refreshToken) KeychainService.delete(key: Keys.email) userDefaults.removeObject(forKey: Keys.deviceUUID) updateSignInState() } } }
98-98: Fix SwiftLint trailing whitespace (line 98).Remove the trailing spaces on the blank line to satisfy trailing_whitespace.
- +DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
103-109: Avoid shadowing property; gate debug prints; minor cleanups.Improves readability and removes prod logs.
- print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") - guard let contentViewTopConstraint = contentViewTopConstraint else { - print("DEBUG: contentViewTopConstraint is nil!") + #if DEBUG + print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #endif + guard let topConstraint = contentViewTopConstraint else { + #if DEBUG + print("DEBUG: contentViewTopConstraint is nil!") + #endif return } - let translatedPoint: CGPoint = sender.translation(in: view) - let currentY = contentViewTopConstraint.constant + let translatedPoint: CGPoint = sender.translation(in: view) + let currentY = topConstraint.constant ... - contentViewTopConstraint.constant = max(minY, min(maxY, newY)) + topConstraint.constant = max(minY, min(maxY, newY)) ... - let finalCurrentY = contentViewTopConstraint.constant + let finalCurrentY = topConstraint.constant ... - contentViewTopConstraint.constant = finalY + topConstraint.constant = finalYAlso applies to: 129-166
214-226: Unify “closed map” position with pan gesture constants.Avoids inconsistent sheet positions.
- // Animate to show more map (closed position) - let kDefaultClosedMapPosition: CGFloat = 100.0 + // Animate to show more map (closed position) + let screenHeight = view.frame.size.height + let kDefaultClosedMapPosition = screenHeight * 0.75
189-213: Remove dead Map button code or ensure it is always initialized.Current IUO showMapButton is never set up; if any caller triggers updateMapButtonVisibility it would crash.
If unused, delete setupMapButton/showMapAction/updateMapButtonVisibility and the showMapButton property. Otherwise, call setupMapButton() when showMap is true.
Also applies to: 225-233, 320-326
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
382-421: Contact info section composition LGTM; but replace placeholder location count.UI structure looks good. Remove hardcoded getLocationCount() once data is wired.
Would you like a small protocol to inject location count from the VC?
Also applies to: 422-462
730-787: Gate merchant field debug logs behind DEBUG in subtitleForCTX.Avoids leaking merchant metadata in prod logs.
- if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") { + #if DEBUG + if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") { print("🎯 MERCHANT DEBUG: \(self.merchant.name)") ... - } + } + #endif ... - if self.merchant.name.lowercased().contains("gamestop") { + #if DEBUG + if self.merchant.name.lowercased().contains("gamestop") { print("🎯 GAMESTOP DENOMINATIONS TYPE DEBUG:") ... - } + } + #endifAlso applies to: 997-1041
1085-1095: Gate grabber setup debug prints behind DEBUG.Consistent with logging policy.
- print("DEBUG: grabberContainer is nil!") + #if DEBUG + print("DEBUG: grabberContainer is nil!") + #endif ... - print("DEBUG: Setting up pan gesture on grabber container") + #if DEBUG + print("DEBUG: Setting up pan gesture on grabber container") + #endif ... - print("DEBUG: Pan gesture added to grabber container") + #if DEBUG + print("DEBUG: Pan gesture added to grabber container") + #endif
640-656: US-only formatting noted — consider resilient fallback.If digits don’t match US patterns, fall back to original string (you already do). Optional: label as “United States only” conditionally next to phone.
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift (1)
174-174: Trim trailing whitespace (SwiftLint).Line 174 has trailing spaces flagged by SwiftLint.
- +
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendEndpoint.swift(3 hunks)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(7 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(8 hunks)DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (2)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendAPI.swift (2)
request(82-90)request(92-113)DashWallet/Sources/UI/Explore Dash/Views/DashSpend/CTXSpendUserAuthViewModel.swift (1)
logout(83-85)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (7)
DashWallet/Sources/UI/Views/UIButton+Dash.swift (3)
action(60-70)configuration(72-94)image(21-29)DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/UI/Views/BaseController/BaseViewController+NetworkReachability.swift (2)
stopNetworkMonitoring(52-54)startNetworkMonitoring(36-50)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
callAction(84-99)payAction(79-82)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift (1)
configureGiftCardSection(22-71)DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift (1)
toSavingPercentages(78-80)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (1)
logout(90-108)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (3)
setupInfoButton(272-276)configureHierarchy(232-238)infoButtonAction(278-283)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
configureHierarchy(203-226)callAction(130-158)websiteAction(176-180)setupGrabberPanGesture(1084-1095)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (3)
configureHierarchy(337-493)showMapAction(526-529)showMap(109-120)DashWallet/Sources/Categories/UIHostingController+DashWallet.swift (1)
setDetent(21-31)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendEndpoint.swift (2)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (2)
logout(90-108)refreshToken(124-126)DashWallet/Sources/UI/Explore Dash/Views/DashSpend/CTXSpendUserAuthViewModel.swift (1)
logout(83-85)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift
[Warning] 98-98: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift
[Warning] 174-174: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 33-33: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 81-81: Line should be 120 characters or less; currently it has 159 characters
(line_length)
[Warning] 82-82: Line should be 120 characters or less; currently it has 154 characters
(line_length)
[Warning] 84-84: Line should be 120 characters or less; currently it has 156 characters
(line_length)
[Warning] 35-35: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 36-36: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 37-37: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 38-38: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 35-35: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 36-36: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 37-37: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 38-38: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 32-32: Prefer -> Void over -> ()
(void_return)
[Warning] 35-35: Prefer -> Void over -> ()
(void_return)
[Warning] 36-36: Prefer -> Void over -> ()
(void_return)
[Warning] 37-37: Prefer -> Void over -> ()
(void_return)
[Warning] 38-38: Prefer -> Void over -> ()
(void_return)
[Warning] 39-39: Prefer -> Void over -> ()
(void_return)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 130-130: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 160-160: Limit vertical whitespace to a single empty line; currently 2
(vertical_whitespace)
[Warning] 205-205: Prefer object literals over image and color inits
(object_literal)
[Warning] 276-276: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 382-382: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 434-434: Line should be 120 characters or less; currently it has 181 characters
(line_length)
[Warning] 547-547: Line should be 120 characters or less; currently it has 125 characters
(line_length)
[Warning] 617-617: Line should be 120 characters or less; currently it has 126 characters
(line_length)
[Warning] 350-350: Prefer object literals over image and color inits
(object_literal)
[Warning] 481-481: Prefer object literals over image and color inits
(object_literal)
[Warning] 547-547: Prefer object literals over image and color inits
(object_literal)
[Warning] 814-814: Limit vertical whitespace to a single empty line; currently 3
(vertical_whitespace)
[Warning] 908-908: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 947-947: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 981-981: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 1049-1049: Line should be 120 characters or less; currently it has 129 characters
(line_length)
[Warning] 959-959: Prefer object literals over image and color inits
(object_literal)
[Warning] 1083-1083: Limit vertical whitespace to a single empty line; currently 2
(vertical_whitespace)
[Warning] 1096-1096: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
[Warning] 898-898: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 940-940: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 974-974: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1128-1128: Unimplemented functions should be marked as unavailable
(unavailable_function)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 36-36: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 67-67: Line should be 120 characters or less; currently it has 121 characters
(line_length)
[Warning] 68-68: Line should be 120 characters or less; currently it has 124 characters
(line_length)
[Warning] 160-160: Line should be 120 characters or less; currently it has 136 characters
(line_length)
[Warning] 220-220: Line should be 120 characters or less; currently it has 132 characters
(line_length)
[Warning] 71-71: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 78-78: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 90-90: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 94-94: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 100-100: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 104-104: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 106-106: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 108-108: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 111-111: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 116-116: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 125-125: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 128-128: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 137-137: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 139-139: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 150-150: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 159-159: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 167-167: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 176-176: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 184-184: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 188-188: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 202-202: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 205-205: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 213-213: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 219-219: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 224-224: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 227-227: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 274-274: Force unwrapping should be avoided
(force_unwrapping)
[Warning] 270-270: Line should be 120 characters or less; currently it has 127 characters
(line_length)
[Warning] 271-271: Line should be 120 characters or less; currently it has 134 characters
(line_length)
[Warning] 266-266: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 272-272: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 319-319: Lines should not have trailing whitespace
(trailing_whitespace)
🔇 Additional comments (10)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendEndpoint.swift (2)
49-53: LGTM: new logout endpoint case added.Enum surface looks consistent with the existing style.
71-80: Confirm backend contract for logout path.Verify the server expects POST /logout (no body). If it’s a different verb or expects payload, adjust here accordingly.
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (1)
91-107: Server-then-local logout with guaranteed cleanup.Good: server attempt wrapped in Task; local token/device cleanup always runs on MainActor.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
66-77: Info button integration LGTM.
Uses SwiftUI sheet detent correctly; consistent with list screen.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (3)
248-258: Subtitle now excludes distance; address adds distance — LGTM.Clear separation; refreshSubtitle updates both correctly.
Also applies to: 920-931
865-871: Button enablement logic LGTM.Respects CTX enabled with fallback to local active + online + synced.
669-682: Ensure SDWebImage import/availability.You call sd_setImage; verify SDWebImage is linked in this target.
Also applies to: 678-681
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift (3)
170-173: Good guard before purchase.Blocking purchase when the CTX API marks the merchant unavailable is correct and aligns with the UX goal.
269-269: Correctly mapping CTXenabledto local state.Assigning
merchantInfo.enabledtoisMerchantEnabledon successful fetch is spot on.
58-58: Initialize isMerchantEnabled from merchant.enabled with fallback to merchant.active
Seed the initial state ininit(merchant:)instead of defaulting totrue:self.isMerchantEnabled = merchant.enabled ?? merchant.activeVerify that
activeexists onExplorePointOfUseand represents the local availability flag.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift
Show resolved
Hide resolved
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.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
176-181: Normalize website URL before opening.Mirror the controller’s fix to handle missing schemes.
- guard let website = merchant.website, let url = URL(string: website) else { return } + guard let website = merchant.website else { return } + let normalized = (website.hasPrefix("http://") || website.hasPrefix("https://")) ? website : "https://\(website)" + guard let url = URL(string: normalized) else { return } UIApplication.shared.open(url, options: [:], completionHandler: nil)
♻️ Duplicate comments (3)
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift (1)
283-285: Don’t disable on CTX fetch failure; preserve prior or local state.Setting isMerchantEnabled = false conflates network errors with disablement; this regresses offline/temporary-outage UX.
} catch { DSLogger.log("Failed to get merchant info: \(error)") - // Set merchant as disabled if we can't fetch info - isMerchantEnabled = false }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
266-278: Avoid force unwrapping contentViewTopConstraint in constraints array.Use a local non-optional var; silences SwiftLint and removes a footgun.
- contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + let topConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + contentViewTopConstraint = topConstraint ... - constraint = [ - contentViewTopConstraint!, + constraint = [ + topConstraint,DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
130-158: Wrap verbose phone debug prints with DEBUG.Prevents leaking debug logs in production.
- print("DEBUG: Original phone: \(phone)") + #if DEBUG + print("DEBUG: Original phone: \(phone)") + #endif ... - print("DEBUG: Extracted digits: \(digits)") + #if DEBUG + print("DEBUG: Extracted digits: \(digits)") + #endif ... - print("DEBUG: URL string: \(urlString)") + #if DEBUG + print("DEBUG: URL string: \(urlString)") + #endif ... - print("DEBUG: Failed to create URL") + #if DEBUG + print("DEBUG: Failed to create URL") + #endif ... - print("DEBUG: Cannot open URL - phone not available") + #if DEBUG + print("DEBUG: Cannot open URL - phone not available") + #endif ... - print("DEBUG: Opening URL: \(url)") - UIApplication.shared.open(url, options: [:], completionHandler: { success in - print("DEBUG: URL open result: \(success)") - }) + #if DEBUG + print("DEBUG: Opening URL: \(url)") + #endif + UIApplication.shared.open(url, options: [:], completionHandler: { success in + #if DEBUG + print("DEBUG: URL open result: \(success)") + #endif + })
🧹 Nitpick comments (13)
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift (2)
58-58: Consider tri-state availability (unknown/enabled/disabled).Bool can’t represent “unknown”; an enum would make fallback logic explicit and avoid optimistic assumptions.
174-174: Trim trailing whitespace (SwiftLint).Minor lint cleanup.
Also applies to: 299-299
DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift (1)
67-77: Wrap long initializer for readability and lint.Break parameters across lines; no behavior change.
- init(merchantId: String, paymentMethod: PaymentMethod, type: `Type`, deeplink: String?, savingsBasisPoints: Int, denominationsType: String?, denominations: [Int] = [], redeemType: String?, enabled: Bool? = nil) { + init( + merchantId: String, + paymentMethod: PaymentMethod, + type: `Type`, + deeplink: String?, + savingsBasisPoints: Int, + denominationsType: String?, + denominations: [Int] = [], + redeemType: String?, + enabled: Bool? = nil + ) {DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
101-108: Gate debug prints behind DEBUG.Avoid noisy logs in release.
- print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #if DEBUG + print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #endif guard let contentViewTopConstraint = contentViewTopConstraint else { - print("DEBUG: contentViewTopConstraint is nil!") + #if DEBUG + print("DEBUG: contentViewTopConstraint is nil!") + #endif return } ... - } completion: { [weak self] _ in - // Map button visibility update removed since button is removed - // self?.updateMapButtonVisibility() - } + } completion: { _ in }Also applies to: 160-167
215-227: Use same position model as drag handler; avoid magic 100.Compute kDefaultClosedMapPosition from screen height for consistency.
- // Animate to show more map (closed position) - let kDefaultClosedMapPosition: CGFloat = 100.0 + // Animate to show more map (closed position) + let screenHeight = view.frame.size.height + let kDefaultClosedMapPosition = screenHeight * 0.75
228-234: Derive visibility threshold instead of hard-coded 350.Base on the same constants to adapt across devices.
- // Show map button when content is mostly expanded (less map visible) - let shouldShow = contentViewTopConstraint.constant > 350 + // Show when closer to closed position than open position + let h = view.frame.size.height + let open = h * 0.2 + let closed = h * 0.75 + let mid = (open + closed) / 2 + let shouldShow = contentViewTopConstraint.constant > midDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (7)
988-1007: Gate CTX field debug prints with DEBUG (and prefer DSLogger).Avoid noisy stdout; keep diagnostics behind compile flag.
- if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") { - print("🎯 MERCHANT DEBUG: \(self.merchant.name)") - ... - print(" ----------") - } + #if DEBUG + if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") { + DSLogger.log("MERCHANT DEBUG: \(self.merchant.name) id=\(merchant.merchantId) denomType=\(merchant.denominationsType ?? "nil")") + } + #endif
1082-1093: Wrap grabber pan debug prints with DEBUG.Same rationale as above.
- guard let grabberContainer = grabberContainer else { - print("DEBUG: grabberContainer is nil!") + guard let grabberContainer = grabberContainer else { + #if DEBUG + print("DEBUG: grabberContainer is nil!") + #endif return } - print("DEBUG: Setting up pan gesture on grabber container") + #if DEBUG + print("DEBUG: Setting up pan gesture on grabber container") + #endif ... - print("DEBUG: Pan gesture added to grabber container") + #if DEBUG + print("DEBUG: Pan gesture added to grabber container") + #endif
29-30: Avoid IUO for grabberContainer.Make it optional; code already guards against nil.
- private var grabberContainer: UIView! + private var grabberContainer: UIView?
35-41: Align access control with enclosing type (SwiftLint lower_acl_than_parent).These can be internal; no external API needed.
- public var payWithDashHandler: (()->())? - public var sellDashHandler: (()->())? - public var dashSpendAuthHandler: (()->())? - public var buyGiftCardHandler: (()->())? - public var showAllLocationsActionBlock: (() -> ())? - public var infoButtonActionBlock: (() -> ())? // For parent view controller to handle info button tap + var payWithDashHandler: (() -> Void)? + var sellDashHandler: (() -> Void)? + var dashSpendAuthHandler: (() -> Void)? + var buyGiftCardHandler: (() -> Void)? + var showAllLocationsActionBlock: (() -> Void)? + var infoButtonActionBlock: (() -> Void)? // For parent view controller to handle info button tap
789-811: Country notice is hard-coded to US.If expansion is planned, inject country/locale so text isn’t hard-coded.
594-600: Replace placeholder getLocationCount().Wire actual count from data source or pass via initializer; prevents stale UI labels.
I can draft the parameter plumbing from the controller to this view; want me to?
81-86: Minor lint fixes: long lines and object literals.Break long lines and prefer UIColor/UIImage literals to satisfy SwiftLint.
Also applies to: 617-621, 961-963
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift(3 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(7 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(8 hunks)DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (6)
DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/UI/Views/BaseController/BaseViewController+NetworkReachability.swift (2)
stopNetworkMonitoring(52-54)startNetworkMonitoring(36-50)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
callAction(84-99)payAction(79-82)websiteAction(173-187)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift (1)
configureGiftCardSection(22-71)DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift (2)
toSavingPercentages(79-81)isEnabled(88-90)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (1)
logout(90-108)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (2)
setupInfoButton(272-276)infoButtonAction(278-283)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (3)
callAction(130-158)websiteAction(176-180)setupGrabberPanGesture(1082-1093)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (2)
showMapAction(526-529)showMap(109-120)DashWallet/Sources/Categories/UIHostingController+DashWallet.swift (1)
setDetent(21-31)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift
[Warning] 174-174: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 299-299: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 33-33: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 81-81: Line should be 120 characters or less; currently it has 159 characters
(line_length)
[Warning] 82-82: Line should be 120 characters or less; currently it has 154 characters
(line_length)
[Warning] 84-84: Line should be 120 characters or less; currently it has 156 characters
(line_length)
[Warning] 35-35: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 36-36: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 37-37: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 38-38: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 35-35: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 36-36: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 37-37: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 38-38: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 32-32: Prefer -> Void over -> ()
(void_return)
[Warning] 35-35: Prefer -> Void over -> ()
(void_return)
[Warning] 36-36: Prefer -> Void over -> ()
(void_return)
[Warning] 37-37: Prefer -> Void over -> ()
(void_return)
[Warning] 38-38: Prefer -> Void over -> ()
(void_return)
[Warning] 39-39: Prefer -> Void over -> ()
(void_return)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 130-130: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 160-160: Limit vertical whitespace to a single empty line; currently 2
(vertical_whitespace)
[Warning] 205-205: Prefer object literals over image and color inits
(object_literal)
[Warning] 276-276: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 382-382: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 434-434: Line should be 120 characters or less; currently it has 181 characters
(line_length)
[Warning] 547-547: Line should be 120 characters or less; currently it has 125 characters
(line_length)
[Warning] 617-617: Line should be 120 characters or less; currently it has 126 characters
(line_length)
[Warning] 350-350: Prefer object literals over image and color inits
(object_literal)
[Warning] 481-481: Prefer object literals over image and color inits
(object_literal)
[Warning] 547-547: Prefer object literals over image and color inits
(object_literal)
[Warning] 814-814: Limit vertical whitespace to a single empty line; currently 3
(vertical_whitespace)
[Warning] 906-906: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 945-945: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 979-979: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 1047-1047: Line should be 120 characters or less; currently it has 129 characters
(line_length)
[Warning] 957-957: Prefer object literals over image and color inits
(object_literal)
[Warning] 1081-1081: Limit vertical whitespace to a single empty line; currently 2
(vertical_whitespace)
[Warning] 1094-1094: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
[Warning] 896-896: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 938-938: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 972-972: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1126-1126: Unimplemented functions should be marked as unavailable
(unavailable_function)
DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift
[Error] 67-67: Line should be 200 characters or less; currently it has 220 characters
(line_length)
[Warning] 66-66: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 87-87: Line should be 120 characters or less; currently it has 128 characters
(line_length)
[Warning] 86-86: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 36-36: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 67-67: Line should be 120 characters or less; currently it has 121 characters
(line_length)
[Warning] 68-68: Line should be 120 characters or less; currently it has 124 characters
(line_length)
[Warning] 160-160: Line should be 120 characters or less; currently it has 136 characters
(line_length)
[Warning] 220-220: Line should be 120 characters or less; currently it has 132 characters
(line_length)
[Warning] 71-71: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 78-78: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 90-90: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 94-94: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 100-100: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 104-104: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 106-106: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 108-108: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 111-111: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 116-116: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 125-125: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 128-128: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 137-137: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 139-139: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 150-150: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 159-159: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 167-167: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 176-176: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 184-184: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 188-188: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 202-202: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 205-205: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 213-213: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 219-219: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 224-224: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 227-227: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 274-274: Force unwrapping should be avoided
(force_unwrapping)
[Warning] 271-271: Line should be 120 characters or less; currently it has 134 characters
(line_length)
[Warning] 266-266: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 272-272: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 319-319: Lines should not have trailing whitespace
(trailing_whitespace)
🔇 Additional comments (3)
DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift (1)
64-66: Enabled flag + fallback helper look good.Optional enabled with isEnabled(fallbackActive:) matches the intended semantics.
Also applies to: 86-91
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
173-187: Hardened websiteAction LGTM.Normalizing the URL fixes scheme-less inputs.
59-62: Info button integration LGTM.Simple and self-contained; matches patterns used elsewhere.
Also applies to: 66-77
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift
Show resolved
Hide resolved
|
Claude made changes for everything except the marketing version issues and this last comment (CR seems to find something new on each review...) |
|
there is always something new. |
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.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
235-251: Avoid force-unwrapping latitude/longitude in showMapIfNeededGuard lat/lon to prevent a crash on malformed data.
- mapView.show(merchants: [pointOfUse]) - mapView.centerRadius = 5 - mapView.initialCenterLocation = .init(latitude: pointOfUse.latitude!, longitude: pointOfUse.longitude!) + mapView.show(merchants: [pointOfUse]) + mapView.centerRadius = 5 + if let lat = pointOfUse.latitude, let lon = pointOfUse.longitude { + mapView.initialCenterLocation = .init(latitude: lat, longitude: lon) + }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
176-180: Normalize website URL and check canOpenURL before openingMirror controller logic and handle empty strings; prefer https default.
- func websiteAction() { - guard let website = merchant.website, let url = URL(string: website) else { return } - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } + func websiteAction() { + guard var website = merchant.website?.trimmingCharacters(in: .whitespacesAndNewlines), + !website.isEmpty else { return } + if !website.lowercased().hasPrefix("http") { website = "https://" + website } + guard let url = URL(string: website), UIApplication.shared.canOpenURL(url) else { return } + UIApplication.shared.open(url, options: [:], completionHandler: nil) + }
♻️ Duplicate comments (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
173-186: Harden websiteAction URL normalization and empty-string handlingTrim, add scheme case-insensitively, and guard against empty strings; keep behavior consistent with List VC.
- func websiteAction() { - guard let website = pointOfUse.website else { return } - - // Normalize URL by adding https scheme if missing - let normalizedWebsite: String - if website.hasPrefix("http://") || website.hasPrefix("https://") { - normalizedWebsite = website - } else { - normalizedWebsite = "https://" + website - } - - guard let url = URL(string: normalizedWebsite) else { return } - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } + func websiteAction() { + guard var website = pointOfUse.website?.trimmingCharacters(in: .whitespacesAndNewlines), + !website.isEmpty else { return } + if !website.lowercased().hasPrefix("http") { website = "https://" + website } + guard let url = URL(string: website) else { return } + UIApplication.shared.open(url, options: [:], completionHandler: nil) + }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
130-158: Wrap phone debug prints with #if DEBUGThese logs will ship to production as-is.
- @objc func callAction() { + @objc func callAction() { guard let phone = merchant.phone, !phone.isEmpty else { return } - print("DEBUG: Original phone: \(phone)") + #if DEBUG + print("DEBUG: Original phone: \(phone)") + #endif @@ - print("DEBUG: Extracted digits: \(digits)") + #if DEBUG + print("DEBUG: Extracted digits: \(digits)") + #endif @@ - print("DEBUG: URL string: \(urlString)") + #if DEBUG + print("DEBUG: URL string: \(urlString)") + #endif @@ - guard let url = URL(string: urlString) else { - print("DEBUG: Failed to create URL") + guard let url = URL(string: urlString) else { + #if DEBUG + print("DEBUG: Failed to create URL") + #endif return } @@ - guard UIApplication.shared.canOpenURL(url) else { - print("DEBUG: Cannot open URL - phone not available") + guard UIApplication.shared.canOpenURL(url) else { + #if DEBUG + print("DEBUG: Cannot open URL - phone not available") + #endif return } @@ - print("DEBUG: Opening URL: \(url)") - UIApplication.shared.open(url, options: [:], completionHandler: { success in - print("DEBUG: URL open result: \(success)") - }) + #if DEBUG + print("DEBUG: Opening URL: \(url)") + #endif + UIApplication.shared.open(url, options: [:], completionHandler: { success in + #if DEBUG + print("DEBUG: URL open result: \(success)") + #endif + }) }
🧹 Nitpick comments (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
189-233: Remove dead map button code (and property) or feature-flag itshowMapButton is not used (comment says it’s removed). Keeping code adds maintenance and confuses readers.
- private var showMapButton: UIButton! @@ - private func setupMapButton() { ... } - - @objc - private func showMapAction() { ... } - - private func updateMapButtonVisibility() { ... }If you intend to keep it, hide behind a compile-time flag and ensure constants are unified with handlePanGesture().
Also applies to: 35-37
498-512: Prefer model helper for enabled fallbackReuse ExplorePointOfUse.Merchant.isEnabled(fallbackActive:) to avoid logic drift.
- enabled: enabled ?? currentMerchant.enabled + enabled: enabled ?? currentMerchant.enabledAnd downstream, use m.isEnabled(fallbackActive: self.active) where you check availability.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (3)
1056-1096: Gate grabber pan gesture debug prints behind DEBUGPrevent noisy logs in production.
- guard let grabberContainer = grabberContainer else { - print("DEBUG: grabberContainer is nil!") + guard let grabberContainer = grabberContainer else { + #if DEBUG + print("DEBUG: grabberContainer is nil!") + #endif return } - print("DEBUG: Setting up pan gesture on grabber container") + #if DEBUG + print("DEBUG: Setting up pan gesture on grabber container") + #endif @@ - print("DEBUG: Pan gesture added to grabber container") + #if DEBUG + print("DEBUG: Pan gesture added to grabber container") + #endif
856-869: Use model helper for availability and minimize duplicationPrefer m.isEnabled(fallbackActive:) to avoid repeating fallback logic and keep semantics in one place.
- let isEnabled = m.enabled ?? merchant.active + let isEnabled = m.isEnabled(fallbackActive: merchant.active)
610-623: Trim website text before displayingMinor polish: trim and strip scheme once, so “www.example.com/…” shows cleanly.
- let displayText = website.replacingOccurrences(of: "https://", with: "").replacingOccurrences(of: "http://", with: "") + let displayText = website + .trimmingCharacters(in: .whitespacesAndNewlines) + .replacingOccurrences(of: "https://", with: "") + .replacingOccurrences(of: "http://", with: "")
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(7 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(8 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (5)
DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/UI/Views/BaseController/BaseViewController+NetworkReachability.swift (2)
stopNetworkMonitoring(52-54)startNetworkMonitoring(36-50)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
callAction(84-99)payAction(79-82)websiteAction(173-187)DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift (2)
toSavingPercentages(79-81)isEnabled(88-90)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (1)
logout(90-108)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (3)
setupInfoButton(272-276)configureHierarchy(232-238)infoButtonAction(278-283)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (2)
configureHierarchy(203-226)setupGrabberPanGesture(1085-1096)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (3)
configureHierarchy(337-493)showMapAction(526-529)showMap(109-120)DashWallet/Sources/Categories/UIHostingController+DashWallet.swift (1)
setDetent(21-31)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 33-33: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 81-81: Line should be 120 characters or less; currently it has 159 characters
(line_length)
[Warning] 82-82: Line should be 120 characters or less; currently it has 154 characters
(line_length)
[Warning] 84-84: Line should be 120 characters or less; currently it has 156 characters
(line_length)
[Warning] 35-35: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 36-36: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 37-37: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 38-38: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 35-35: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 36-36: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 37-37: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 38-38: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 32-32: Prefer -> Void over -> ()
(void_return)
[Warning] 35-35: Prefer -> Void over -> ()
(void_return)
[Warning] 36-36: Prefer -> Void over -> ()
(void_return)
[Warning] 37-37: Prefer -> Void over -> ()
(void_return)
[Warning] 38-38: Prefer -> Void over -> ()
(void_return)
[Warning] 39-39: Prefer -> Void over -> ()
(void_return)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 130-130: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 160-160: Limit vertical whitespace to a single empty line; currently 2
(vertical_whitespace)
[Warning] 205-205: Prefer object literals over image and color inits
(object_literal)
[Warning] 276-276: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 382-382: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 434-434: Line should be 120 characters or less; currently it has 181 characters
(line_length)
[Warning] 547-547: Line should be 120 characters or less; currently it has 125 characters
(line_length)
[Warning] 617-617: Line should be 120 characters or less; currently it has 126 characters
(line_length)
[Warning] 350-350: Prefer object literals over image and color inits
(object_literal)
[Warning] 481-481: Prefer object literals over image and color inits
(object_literal)
[Warning] 547-547: Prefer object literals over image and color inits
(object_literal)
[Warning] 814-814: Limit vertical whitespace to a single empty line; currently 3
(vertical_whitespace)
[Warning] 906-906: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 945-945: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 979-979: Line should be 120 characters or less; currently it has 135 characters
(line_length)
[Warning] 996-996: Line should be 120 characters or less; currently it has 175 characters
(line_length)
[Warning] 1050-1050: Line should be 120 characters or less; currently it has 129 characters
(line_length)
[Warning] 957-957: Prefer object literals over image and color inits
(object_literal)
[Warning] 1084-1084: Limit vertical whitespace to a single empty line; currently 2
(vertical_whitespace)
[Warning] 1097-1097: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
[Warning] 896-896: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 938-938: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 972-972: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1129-1129: Unimplemented functions should be marked as unavailable
(unavailable_function)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 36-36: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 67-67: Line should be 120 characters or less; currently it has 121 characters
(line_length)
[Warning] 68-68: Line should be 120 characters or less; currently it has 124 characters
(line_length)
[Warning] 160-160: Line should be 120 characters or less; currently it has 136 characters
(line_length)
[Warning] 220-220: Line should be 120 characters or less; currently it has 132 characters
(line_length)
[Warning] 71-71: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 78-78: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 90-90: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 94-94: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 100-100: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 104-104: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 106-106: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 108-108: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 111-111: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 116-116: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 125-125: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 128-128: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 137-137: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 139-139: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 150-150: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 159-159: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 167-167: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 176-176: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 184-184: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 188-188: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 202-202: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 205-205: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 213-213: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 219-219: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 224-224: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 227-227: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 274-274: Force unwrapping should be avoided
(force_unwrapping)
[Warning] 271-271: Line should be 120 characters or less; currently it has 134 characters
(line_length)
[Warning] 266-266: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 272-272: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 319-319: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 413-413: Line should be 120 characters or less; currently it has 122 characters
(line_length)
[Warning] 411-411: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 419-419: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 425-425: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 433-433: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 437-437: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 441-441: Lines should not have trailing whitespace
(trailing_whitespace)
...allet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
Show resolved
Hide resolved
...allet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
Outdated
Show resolved
Hide resolved
...allet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
Outdated
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendEndpoint.swift
Outdated
Show resolved
Hide resolved
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendEndpoint.swift
Outdated
Show resolved
Hide resolved
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.
Actionable comments posted: 2
♻️ Duplicate comments (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
104-174: Unify sheet positions, avoid shadowing, and gate debug prints
- Don’t shadow
contentViewTopConstraint; use a localtopConstraint.- Centralize positions via one helper to keep logic consistent across methods.
- Gate debug prints with DEBUG.
- private func handlePanGesture(_ sender: UIPanGestureRecognizer) { - print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") - guard let contentViewTopConstraint = contentViewTopConstraint else { - print("DEBUG: contentViewTopConstraint is nil!") + private func handlePanGesture(_ sender: UIPanGestureRecognizer) { + #if DEBUG + print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #endif + guard let topConstraint = contentViewTopConstraint else { + #if DEBUG + print("DEBUG: contentViewTopConstraint is nil!") + #endif return } let translatedPoint: CGPoint = sender.translation(in: view) - let currentY = contentViewTopConstraint.constant + let currentY = topConstraint.constant @@ - // Constrain movement between closed position and maximum expanded position - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 // Mostly closed (more map visible) - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Default position higher by about an inch - let kDefaultOpenedMapPosition = screenHeight * 0.2 // Mostly open (less map visible, more content) - // Allow dragging between open position (top) and closed position (bottom) - let maxY = kDefaultClosedMapPosition // Don't allow dragging below closed position - let minY = kDefaultOpenedMapPosition // Don't allow dragging above open position + // Constrain movement between open and closed positions + let pos = mapPositions() + let maxY = pos.closed + let minY = pos.opened - contentViewTopConstraint.constant = max(minY, min(maxY, newY)) + topConstraint.constant = max(minY, min(maxY, newY)) sender.setTranslation(.zero, in: view) @@ - let finalCurrentY = contentViewTopConstraint.constant - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 // Mostly closed (more map visible) - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Higher by about an inch - let kDefaultOpenedMapPosition = screenHeight * 0.2 + let finalCurrentY = topConstraint.constant + let pos = mapPositions() var finalY: CGFloat @@ - finalY = kDefaultClosedMapPosition + finalY = pos.closed @@ - finalY = kDefaultOpenedMapPosition + finalY = pos.opened @@ - let midPoint1 = (kDefaultOpenedMapPosition + kDefaultBottomHalfPosition) / 2 - let midPoint2 = (kDefaultBottomHalfPosition + kDefaultClosedMapPosition) / 2 + let midPoint1 = (pos.opened + pos.half) / 2 + let midPoint2 = (pos.half + pos.closed) / 2 if finalCurrentY < midPoint1 { - finalY = kDefaultOpenedMapPosition + finalY = pos.opened } else if finalCurrentY < midPoint2 { - finalY = kDefaultBottomHalfPosition + finalY = pos.half } else { - finalY = kDefaultClosedMapPosition + finalY = pos.closed } } UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut) { - contentViewTopConstraint.constant = finalY + topConstraint.constant = finalY self.view.layoutIfNeeded() } completion: { [weak self] _ in // Map button visibility update removed since button is removed // self?.updateMapButtonVisibility() }Add once in the type:
private func mapPositions() -> (opened: CGFloat, half: CGFloat, closed: CGFloat) { let h = view.bounds.height return (opened: h * 0.2, half: h * 0.5 - 72, closed: h * 0.75) }
178-189: Harden website URL normalization (trim + case-insensitive scheme)- guard let website = pointOfUse.website else { return } - - // Normalize URL by adding https scheme if missing - let normalizedWebsite: String - if website.hasPrefix("http://") || website.hasPrefix("https://") { - normalizedWebsite = website - } else { - normalizedWebsite = "https://" + website - } - - guard let url = URL(string: normalizedWebsite) else { return } + guard var website = pointOfUse.website?.trimmingCharacters(in: .whitespacesAndNewlines), + !website.isEmpty else { return } + if URL(string: website)?.scheme == nil { + website = "https://\(website)" + } + guard let url = URL(string: website) else { return }
269-277: Avoid force unwrap and reuse unified position helper- // Create the top constraint and store reference to it - // Start higher than bottom half by about an inch (72 points) - let screenHeight = UIScreen.main.bounds.height - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Higher by about an inch - contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + // Create the top constraint and store reference to it + let pos = mapPositions() + let topConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: pos.half) + contentViewTopConstraint = topConstraint @@ - constraint = [ - contentViewTopConstraint!, + constraint = [ + topConstraint,
419-427: Gate CTX merchant debug prints behind DEBUGAvoid leaking merchant details in production logs.
- // Debug logging for CTX merchant info - if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { - print("🎯 CTX MERCHANT INFO DEBUG: \(pointOfUse.name)") - print(" CTX enabled: \(merchantInfo.enabled)") - print(" Local active: \(pointOfUse.active)") - print(" Will update view with enabled: \(merchantInfo.enabled)") - } + #if DEBUG + // Debug logging for CTX merchant info + if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { + print("🎯 CTX MERCHANT INFO DEBUG: \(pointOfUse.name)") + print(" CTX enabled: \(merchantInfo.enabled)") + print(" Local active: \(pointOfUse.active)") + print(" Will update view with enabled: \(merchantInfo.enabled)") + } + #endifDashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m (1)
48-50: Logout constant is not a real logout; add token revoke endpoint and use it.Keep dashboard URL for post-logout navigation, but add a revoke URL constant and call it before navigating. This was raised earlier; still applicable.
Add alongside logout (and declare in the header):
+ (NSString *)revokeTokenURLString { return @"https://api.uphold.com/oauth2/revoke"; }Please confirm sandbox parity (DWUpholdSandboxConstants.{h,m}) and that the logout flow actually calls revoke, clears local creds, then opens the dashboard.
🧹 Nitpick comments (11)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (5)
26-26: Avoid IUO for distanceLabel (or remove it entirely).Using UILabel! risks a crash if update(with:) runs before configureHierarchy(). Since the label is never shown (kept hidden) and subLabel now owns distance, prefer removing it.
Apply this diff to delete the unused property:
- private var distanceLabel: UILabel!
33-42: DRY up distance formatting; consider a shared helper.This block duplicates distance logic used in AtmItemCell. Extract to a common helper (e.g., ExploreDash.formattedDistance(from:to:)) to keep rounding/units consistent and simplify testing.
Do we intentionally hide subLabel when location is unavailable for merchants (while ATM shows a source fallback)? Confirm UX parity or divergence.
46-46: Trim trailing whitespace.Minor SwiftLint warning; remove trailing spaces on this line.
47-48: Remove redundant hiding of an unused label.Since distance is shown via subLabel and distanceLabel is never displayed, this hide is unnecessary.
Apply this diff:
- // Hide separate distance label since we're using subLabel - distanceLabel.isHidden = true
67-75: Don't add unused views to the stack.distanceLabel is created, configured, and added to mainStackView but never used. This adds view hierarchy overhead and noise. Remove it; use subLabel exclusively.
Apply this diff:
- // Distance label (separate from merchant name) - distanceLabel = UILabel() - distanceLabel.translatesAutoresizingMaskIntoConstraints = false - distanceLabel.font = .systemFont(ofSize: 12, weight: .regular) - distanceLabel.textColor = .dw_tertiaryText() - distanceLabel.isHidden = true - distanceLabel.textAlignment = .right - mainStackView.addArrangedSubview(distanceLabel)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (5)
52-56: Clamp bottom inset to non-negativeAvoid negative insets when detailsView > mapView.
- mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: mapView.frame.height - detailsView.frame.height - 10, - right: 0) + let bottom = max(mapView.frame.height - detailsView.frame.height - 10, 0) + mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottom, right: 0)
89-101: Add tel: fallback if telprompt: can’t openSome devices/contexts don’t support telprompt:. Fallback to tel: improves reliability.
- let urlString = "telprompt:\(digits)" - guard let url = URL(string: urlString) else { return } - - // Check if device can open the URL - guard UIApplication.shared.canOpenURL(url) else { return } - - UIApplication.shared.open(url, options: [:], completionHandler: nil) + let prompt = URL(string: "telprompt:\(digits)") + let tel = URL(string: "tel:\(digits)") + if let url = prompt, UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } else if let url = tel, UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + }
192-215: Remove unused Map button setup blockFeature is removed; keeping this risks future misuse.
- private func setupMapButton() { - showMapButton = UIButton(type: .custom) - showMapButton.translatesAutoresizingMaskIntoConstraints = false - showMapButton.isHidden = true // Initially hidden - showMapButton.tintColor = .white - showMapButton.imageEdgeInsets = .init(top: 0, left: -10, bottom: 0, right: 0) - showMapButton.addTarget(self, action: #selector(showMapAction), for: .touchUpInside) - showMapButton.setImage(UIImage(systemName: "map.fill"), for: .normal) - showMapButton.setTitle(NSLocalizedString("Map", comment: ""), for: .normal) - showMapButton.layer.masksToBounds = true - showMapButton.layer.cornerRadius = 20 - showMapButton.layer.backgroundColor = UIColor.black.cgColor - contentView.addSubview(showMapButton) - - let showMapButtonWidth: CGFloat = 92 - let showMapButtonHeight: CGFloat = 40 - - NSLayoutConstraint.activate([ - showMapButton.widthAnchor.constraint(equalToConstant: showMapButtonWidth), - showMapButton.heightAnchor.constraint(equalToConstant: showMapButtonHeight), - showMapButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - showMapButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -15), - ]) - }
217-229: Drop orphaned showMapAction (no caller) or align with unified positionsSince the Map button is gone, remove this method to avoid confusion.
- @objc - private func showMapAction() { - // Animate to show more map (closed position) - let kDefaultClosedMapPosition: CGFloat = 100.0 - guard let contentViewTopConstraint = contentViewTopConstraint else { return } - - UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut) { - contentViewTopConstraint.constant = kDefaultClosedMapPosition - self.view.layoutIfNeeded() - } - - updateMapButtonVisibility() - }
231-236: Remove unused updateMapButtonVisibility (and crash-prone IUO access)- private func updateMapButtonVisibility() { - guard let contentViewTopConstraint = contentViewTopConstraint else { return } - // Show map button when content is mostly expanded (less map visible) - let shouldShow = contentViewTopConstraint.constant > 350 - showMapButton.isHidden = !shouldShow - }DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m (1)
24-26: Avoid duplicating the client ID in the authorize URL; construct it from the single source.Keeps ID rotatable, and prevents divergence. Also preserves the state placeholder for a second formatting pass.
- return @"https://wallet.uphold.com/authorize/c184650d0cb44e73d8e5cb2021753a721c41f74a?scope=accounts:read%%20cards:read%%20cards:write%%20transactions:deposit%%20transactions:read%%20transactions:transfer:application%%20transactions:transfer:others%%20transactions:transfer:self%%20transactions:withdraw%%20transactions:commit:otp%%20user:read&state=%@"; + return [NSString stringWithFormat: + @"https://wallet.uphold.com/authorize/%@?scope=accounts:read%%20cards:read%%20cards:write%%20transactions:deposit%%20transactions:read%%20transactions:transfer:application%%20transactions:transfer:others%%20transactions:transfer:self%%20transactions:withdraw%%20transactions:commit:otp%%20user:read&state=%%@", + [self clientID]];Nice-to-have: build this with NSURLComponents and URL-encode scopes instead of pre-encoding with %20.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(8 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (4)
mapView(239-244)mapView(246-256)mapView(272-278)configureHierarchy(203-233)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (2)
configureHierarchy(337-493)showMap(109-120)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
configureHierarchy(203-226)payAction(182-196)callAction(130-158)setupGrabberPanGesture(1085-1096)DashWallet/Sources/Categories/UIHostingController+DashWallet.swift (1)
setDetent(21-31)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift (1)
update(41-49)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/AtmItemCell.swift (1)
update(28-48)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 36-36: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 54-54: Line should be 120 characters or less; currently it has 126 characters
(line_length)
[Warning] 70-70: Line should be 120 characters or less; currently it has 121 characters
(line_length)
[Warning] 71-71: Line should be 120 characters or less; currently it has 124 characters
(line_length)
[Warning] 163-163: Line should be 120 characters or less; currently it has 136 characters
(line_length)
[Warning] 223-223: Line should be 120 characters or less; currently it has 132 characters
(line_length)
[Warning] 74-74: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 81-81: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 93-93: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 97-97: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 103-103: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 107-107: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 109-109: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 111-111: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 114-114: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 119-119: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 128-128: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 131-131: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 140-140: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 142-142: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 153-153: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 162-162: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 170-170: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 179-179: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 187-187: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 191-191: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 205-205: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 208-208: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 216-216: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 222-222: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 227-227: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 230-230: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 55-55: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 277-277: Force unwrapping should be avoided
(force_unwrapping)
[Warning] 274-274: Line should be 120 characters or less; currently it has 134 characters
(line_length)
[Warning] 269-269: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 275-275: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 299-299: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 327-327: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 421-421: Line should be 120 characters or less; currently it has 122 characters
(line_length)
[Warning] 419-419: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 427-427: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 433-433: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 441-441: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 445-445: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 449-449: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift
[Warning] 26-26: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 46-46: Lines should not have trailing whitespace
(trailing_whitespace)
🔇 Additional comments (7)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (6)
62-63: Info button integration looks good
69-81: Detented SwiftUI info sheet: OKClean, self-contained, consistent with setDetent helper.
295-335: Nice nil-safety for detailsView creation and targeted pan gestureGood guard + targeted gesture on grabber.
434-437: MainActor UI refresh: good
442-448: Recreate details view after merchant update: good
507-520: updatingMerchant merges enabled safely and preserves fieldsLooks correct; denom parsing with compactMap is safe.
DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m (1)
40-46: LGTM on wallet deep-links for card add and transaction view.URLs look consistent and readable.
...allet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
Show resolved
Hide resolved
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.
Actionable comments posted: 2
♻️ Duplicate comments (6)
DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m (1)
48-50: Logout URL doesn’t revoke tokens — track via follow-upThis navigates to a dashboard page and won’t invalidate OAuth tokens. Prior comment suggested adding a revokeTokenURLString and calling the revoke endpoint before navigation; agreed to handle in a separate story. Please add a TODO with the ticket ID and ensure sandbox parity when you tackle it.
Verify header parity and presence of a sandbox constant:
#!/bin/bash fd DWUphold*Constants.h -t f | xargs -I{} rg -n 'logoutURLString|revokeTokenURLString' {} fd DWUpholdSandboxConstants.m -t f | xargs -I{} rg -n 'logoutURLString|revoke' {}DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (5)
104-174: Unify sheet positions, avoid shadowing, animate during drag, and gate debug prints
- Centralize opened/half/closed constants to a helper used everywhere.
- Don’t shadow contentViewTopConstraint; use a local alias with a different name.
- Call layoutIfNeeded() in .changed for smooth dragging.
- Wrap debug prints in DEBUG.
- private func handlePanGesture(_ sender: UIPanGestureRecognizer) { - print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") - guard let contentViewTopConstraint = contentViewTopConstraint else { - print("DEBUG: contentViewTopConstraint is nil!") - return - } + private func handlePanGesture(_ sender: UIPanGestureRecognizer) { + #if DEBUG + print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #endif + guard let topConstraint = contentViewTopConstraint else { + #if DEBUG + print("DEBUG: contentViewTopConstraint is nil!") + #endif + return + } let translatedPoint: CGPoint = sender.translation(in: view) - let currentY = contentViewTopConstraint.constant + let currentY = topConstraint.constant switch sender.state { case .changed: // Only handle vertical movement and constrain within bounds let newY = currentY + translatedPoint.y // Constrain movement between closed position and maximum expanded position - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 // Mostly closed (more map visible) - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Default position higher by about an inch - let kDefaultOpenedMapPosition = screenHeight * 0.2 // Mostly open (less map visible, more content) + let pos = mapPositions() // Allow dragging between open position (top) and closed position (bottom) - let maxY = kDefaultClosedMapPosition // Don't allow dragging below closed position - let minY = kDefaultOpenedMapPosition // Don't allow dragging above open position + let maxY = pos.closed // Don't allow dragging below closed position + let minY = pos.opened // Don't allow dragging above open position - contentViewTopConstraint.constant = max(minY, min(maxY, newY)) + topConstraint.constant = max(minY, min(maxY, newY)) sender.setTranslation(.zero, in: view) + self.view.layoutIfNeeded() case .ended: let velocityInView = sender.velocity(in: view) let velocityY: CGFloat = velocityInView.y - let finalCurrentY = contentViewTopConstraint.constant - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 // Mostly closed (more map visible) - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Higher by about an inch - let kDefaultOpenedMapPosition = screenHeight * 0.2 + let finalCurrentY = topConstraint.constant + let pos = mapPositions() var finalY: CGFloat if velocityY > 300 { // Fast downward swipe - snap to closed position (more map visible) - finalY = kDefaultClosedMapPosition + finalY = pos.closed } else if velocityY < -300 { // Fast upward swipe - snap to open position (less map visible) - finalY = kDefaultOpenedMapPosition + finalY = pos.opened } else { // No strong velocity, snap to nearest position based on current position - let midPoint1 = (kDefaultOpenedMapPosition + kDefaultBottomHalfPosition) / 2 - let midPoint2 = (kDefaultBottomHalfPosition + kDefaultClosedMapPosition) / 2 + let midPoint1 = (pos.opened + pos.half) / 2 + let midPoint2 = (pos.half + pos.closed) / 2 if finalCurrentY < midPoint1 { - finalY = kDefaultOpenedMapPosition + finalY = pos.opened } else if finalCurrentY < midPoint2 { - finalY = kDefaultBottomHalfPosition + finalY = pos.half } else { - finalY = kDefaultClosedMapPosition + finalY = pos.closed } } UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut) { - contentViewTopConstraint.constant = finalY + topConstraint.constant = finalY self.view.layoutIfNeeded() } completion: { [weak self] _ in // Map button visibility update removed since button is removed // self?.updateMapButtonVisibility() }Add this helper in the type (outside the range above):
// Consistent map “sheet” positions private func mapPositions() -> (opened: CGFloat, half: CGFloat, closed: CGFloat) { let h = view.bounds.height return (opened: h * 0.2, half: h * 0.5 - 72, closed: h * 0.75) }
176-189: Harden websiteAction URL handling (trim, case-insensitive, canOpenURL, safer parsing)Implements prior suggestion but still misses trimming and robust parsing.
- guard let website = pointOfUse.website else { return } - - // Normalize URL by adding https scheme if missing - let normalizedWebsite: String - if website.hasPrefix("http://") || website.hasPrefix("https://") { - normalizedWebsite = website - } else { - normalizedWebsite = "https://" + website - } - - guard let url = URL(string: normalizedWebsite) else { return } - UIApplication.shared.open(url, options: [:], completionHandler: nil) + guard var website = pointOfUse.website?.trimmingCharacters(in: .whitespacesAndNewlines), + !website.isEmpty else { return } + if !website.lowercased().hasPrefix("http") { website = "https://\(website)" } + guard let comps = URLComponents(string: website), let url = comps.url else { return } + guard UIApplication.shared.canOpenURL(url) else { return } + UIApplication.shared.open(url, options: [:], completionHandler: nil)
218-229: Use unified positions and avoid magic 100.0 in showMapActionMirror the same sheet constants used elsewhere; also avoid shadowing.
- // Animate to show more map (closed position) - let kDefaultClosedMapPosition: CGFloat = 100.0 - guard let contentViewTopConstraint = contentViewTopConstraint else { return } + // Animate to show more map (closed position) + let pos = mapPositions() + guard let topConstraint = contentViewTopConstraint else { return } UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut) { - contentViewTopConstraint.constant = kDefaultClosedMapPosition + topConstraint.constant = pos.closed self.view.layoutIfNeeded() } updateMapButtonVisibility()
415-427: Gate verbose CTX merchant debug logs behind DEBUGPrevents leaking merchant state to release logs.
- // Debug logging for CTX merchant info - if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { + #if DEBUG + // Debug logging for CTX merchant info + if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { print("🎯 CTX MERCHANT INFO DEBUG: \(pointOfUse.name)") print(" CTX enabled: \(merchantInfo.enabled)") print(" Local active: \(pointOfUse.active)") print(" Will update view with enabled: \(merchantInfo.enabled)") } + #endif
270-277: Avoid force unwrap on contentViewTopConstraint to prevent crashCreate the constraint, assign it to the property, and use the local reference.
- let screenHeight = UIScreen.main.bounds.height - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Higher by about an inch - contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + let screenHeight = UIScreen.main.bounds.height + let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Higher by about an inch + let topConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + contentViewTopConstraint = topConstraint @@ - constraint = [ - contentViewTopConstraint!, + constraint = [ + topConstraint,
🧹 Nitpick comments (11)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (4)
26-26: Remove unused distanceLabel (and avoid IUO).The label is never shown and only increases view hierarchy/complexity. Drop it to eliminate the implicitly unwrapped optional.
- private var distanceLabel: UILabel!
33-45: Show a fallback subtitle when distance is unavailable (align with ATM cell).Keep the subtitle useful instead of hiding it; mirror AtmItemCell’s behavior by showing the source if available.
- } else { - subLabel.isHidden = true - } + } else if let source = pointOfUse.source { + subLabel.isHidden = false + subLabel.text = source + } else { + subLabel.isHidden = true + }
46-49: Remove redundant hide and trailing whitespace.Label is already configured hidden (and slated for removal); this block is unnecessary. Also fixes SwiftLint trailing whitespace at Line 46.
- - // Hide separate distance label since we're using subLabel - distanceLabel.isHidden = true
67-75: Delete unused distanceLabel setup.It’s not used; removing avoids extra arrangedSubview and layout work.
- // Distance label (separate from merchant name) - distanceLabel = UILabel() - distanceLabel.translatesAutoresizingMaskIntoConstraints = false - distanceLabel.font = .systemFont(ofSize: 12, weight: .regular) - distanceLabel.textColor = .dw_tertiaryText() - distanceLabel.isHidden = true - distanceLabel.textAlignment = .right - mainStackView.addArrangedSubview(distanceLabel)DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m (1)
44-46: Clarify that this is a web UI deep link, not an API endpointAdd a brief doc comment to prevent misuse.
-+ (NSString *)transactionURLFormat { +// Web UI deep link for viewing a transaction on Uphold (not an API endpoint). ++ (NSString *)transactionURLFormat { return @"https://wallet.uphold.com/reserve/transactions/%@"; }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (6)
52-56: Clamp negative map inset to avoid odd layout on small screensIf detailsView is taller than mapView, bottom inset becomes negative. Clamp to 0.
- mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: mapView.frame.height - detailsView.frame.height - 10, - right: 0) + let bottomInset = max(0, mapView.frame.height - detailsView.frame.height - 10) + mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottomInset, right: 0)
69-73: Prefer template image + tint over pre-tinted original for adaptabilityLet the bar button inherit nav bar tint and dynamic colors.
- let infoImage = UIImage(systemName: "info.circle")?.withRenderingMode(.alwaysOriginal).withTintColor(.systemBlue) - let infoButton = UIBarButtonItem(image: infoImage, style: .plain, target: self, action: #selector(infoButtonAction)) + let infoImage = UIImage(systemName: "info.circle") + let infoButton = UIBarButtonItem(image: infoImage, style: .plain, target: self, action: #selector(infoButtonAction)) + infoButton.tintColor = .systemBlue navigationItem.rightBarButtonItem = infoButton
192-215: Dead code: map button setup appears unusedsetupMapButton() isn’t called, and showMapButton stays hidden. Remove these bits or wire them back in; otherwise they add maintenance cost.
231-236: Derive visibility threshold from unified sheet positionsIf keeping the button, base the threshold on mapPositions() instead of a hard-coded 350.
- // Show map button when content is mostly expanded (less map visible) - let shouldShow = contentViewTopConstraint.constant > 350 + // Show when content is mostly expanded (near opened/half) + let pos = mapPositions() + let shouldShow = contentViewTopConstraint.constant <= pos.half showMapButton.isHidden = !shouldShow
265-269: Conflicting clipping flagsclipsToBounds = false followed by layer.masksToBounds = true is contradictory. Set a single source of truth.
- contentView.clipsToBounds = false - contentView.layer.masksToBounds = true + contentView.clipsToBounds = true
416-440: Handle CTX merchant fetch errors to avoid unhandled task failuresWrap awaits in do/catch and surface a non-blocking path on failure.
- Task { - if try await tryRefreshCtxToken(), let merchantId = pointOfUse.merchant?.merchantId { - let merchantInfo = try await CTXSpendService.shared.getMerchant(merchantId: merchantId) + Task { + do { + if try await tryRefreshCtxToken(), let merchantId = pointOfUse.merchant?.merchantId { + let merchantInfo = try await CTXSpendService.shared.getMerchant(merchantId: merchantId) @@ - pointOfUse = pointOfUse.updatingMerchant( + pointOfUse = pointOfUse.updatingMerchant( @@ - await MainActor.run { - refreshDetailsViewWithUpdatedMerchant() - } - } + await MainActor.run { refreshDetailsViewWithUpdatedMerchant() } + } + } catch { + #if DEBUG + print("CTX merchant fetch failed: \(error)") + #endif + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(8 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (4)
mapView(239-244)mapView(246-256)mapView(272-278)configureHierarchy(203-233)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (3)
configureHierarchy(337-493)showMapAction(526-529)showMap(109-120)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
configureHierarchy(203-226)callAction(130-158)websiteAction(176-180)setupGrabberPanGesture(1085-1096)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift (1)
update(41-49)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/AtmItemCell.swift (1)
update(28-48)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 36-36: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 54-54: Line should be 120 characters or less; currently it has 126 characters
(line_length)
[Warning] 70-70: Line should be 120 characters or less; currently it has 121 characters
(line_length)
[Warning] 71-71: Line should be 120 characters or less; currently it has 124 characters
(line_length)
[Warning] 163-163: Line should be 120 characters or less; currently it has 136 characters
(line_length)
[Warning] 223-223: Line should be 120 characters or less; currently it has 132 characters
(line_length)
[Warning] 74-74: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 81-81: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 93-93: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 97-97: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 103-103: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 107-107: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 109-109: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 111-111: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 114-114: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 119-119: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 128-128: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 131-131: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 140-140: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 142-142: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 153-153: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 162-162: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 170-170: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 179-179: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 187-187: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 191-191: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 205-205: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 208-208: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 216-216: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 222-222: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 227-227: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 230-230: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 55-55: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 277-277: Force unwrapping should be avoided
(force_unwrapping)
[Warning] 274-274: Line should be 120 characters or less; currently it has 134 characters
(line_length)
[Warning] 269-269: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 275-275: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 299-299: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 327-327: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 421-421: Line should be 120 characters or less; currently it has 122 characters
(line_length)
[Warning] 419-419: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 427-427: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 433-433: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 441-441: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 445-445: Lines should not have trailing whitespace
(trailing_whitespace)
[Warning] 449-449: Lines should not have trailing whitespace
(trailing_whitespace)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift
[Warning] 26-26: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 46-46: Lines should not have trailing whitespace
(trailing_whitespace)
🔇 Additional comments (5)
DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m (2)
32-34: Client ID constant: OKShipping client IDs in public apps is expected. No change needed.
40-42: No changes needed for buyCardURLFormat
Thecard.identifierproperty is aNSString *onDWUpholdCardObjectand is passed directly into the%@placeholder, matching the API’s expected card UUID format.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
89-101: Phone dialing flow is now robustNice digit sanitization and canOpenURL check; reduces failures on malformed input.
295-301: Good safety improvements around details view creation and pan wiringGuarding nil from detailsView(for:) removes an IUO crash class, and scoping the pan gesture to the grabber is appropriate.
Also applies to: 321-326, 329-334
428-433: Model plumbing for denominations/enabled looks correctPassing enabled through updatingMerchant and preserving existing fields is clean.
Also applies to: 507-520
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift
Outdated
Show resolved
Hide resolved
|
This PR has a lot of changes to the old Merchant Details screen (not sure why) - this will cause a huge amount of conflicts with the If the intent is to make modifications to the live DashDirect feature, I'd recommend keeping the changes as small as possible. Also, Uphold secrets are exposed. |
339a09f to
614a46a
Compare
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.
Actionable comments posted: 1
♻️ Duplicate comments (11)
DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift (3)
170-174: Remove duplicate merchant availability check.This check is redundant as
purchaseGiftCardAPI()already performs the same validation at line 295-298.Remove this duplicate guard to avoid maintenance burden:
- // Check if merchant is enabled before attempting purchase - guard isMerchantEnabled else { - throw CTXSpendError.merchantUnavailable - } - let response = try await purchaseGiftCardAPI()
283-285: Preserve merchant availability state on network failures.Setting
isMerchantEnabled = falseon any fetch failure will incorrectly disable the merchant during transient network issues, preventing offline purchases or retries.Don't conflate network failures with merchant availability:
} catch { DSLogger.log("Failed to get merchant info: \(error)") - // Set merchant as disabled if we can't fetch info - isMerchantEnabled = false + // Keep the last known state or fall back to local active status + // Network failures shouldn't disable the merchant }Consider showing a retry option or network error banner instead of blocking purchases entirely.
294-299: Use consistent error type for merchant unavailable.The error type should be
CTXSpendError.merchantUnavailableto match the check at line 172 and provide consistent error handling.// Additional server-side check for merchant availability guard isMerchantEnabled else { DSLogger.log("Purchase blocked: merchant disabled") - throw CTXSpendError.paymentProcessingError("Merchant temporarily unavailable") + throw CTXSpendError.merchantUnavailable }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (3)
594-600: Replace hardcoded location count with dynamic value.The
getLocationCount()method returns a hardcoded value of 11, which won't reflect the actual number of merchant locations.Add a property to receive the actual location count from the parent view controller:
+ var locationCount: Int = 1 + private func getLocationCount() -> Int { - // TODO: This should be injected from the parent view controller or data source - // For now, return a placeholder value + 1 to include the current location. - // The actual implementation should come from the data source that knows how many locations this merchant has - return 11 // 10 + 1 for current location, as requested + return locationCount }Then update the parent view controller to set this value when initializing the view.
996-1010: Gate merchant debug logging behind DEBUG flag.These debug prints include sensitive merchant data like merchantId and should not appear in production logs.
// Comprehensive debug logging for merchant fields investigation + #if DEBUG if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") || self.merchant.name.lowercased().contains("buffalo") { print("🎯 MERCHANT DEBUG: \(self.merchant.name)") print(" merchantId: '\(merchant.merchantId)'") print(" paymentMethod: '\(merchant.paymentMethod)'") print(" type: '\(merchant.type)'") print(" deeplink: '\(merchant.deeplink ?? "nil")'") print(" savingsBasisPoints: '\(merchant.savingsBasisPoints)'") print(" denominationsType: '\(merchant.denominationsType ?? "nil")'") print(" denominations: '\(merchant.denominations)'") print(" redeemType: '\(merchant.redeemType ?? "nil")'") print(" enabled: '\(merchant.enabled?.description ?? "nil")'") print(" self.merchant.active: '\(self.merchant.active)'") print(" isEnabled: '\(isEnabled)'") print(" ----------") } + #endif
130-158: Remove debug print statements from production code.These verbose debug prints will appear in production logs and should be gated behind DEBUG compilation flag.
@objc func callAction() { guard let phone = merchant.phone, !phone.isEmpty else { return } + #if DEBUG print("DEBUG: Original phone: \(phone)") + #endif // Extract only digits for phone call let digits = phone.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() guard !digits.isEmpty else { return } + #if DEBUG print("DEBUG: Extracted digits: \(digits)") + #endif // Use telprompt: to directly open phone app (tel: shows options) let urlString = "telprompt:\(digits)" + #if DEBUG print("DEBUG: URL string: \(urlString)") + #endif guard let url = URL(string: urlString) else { + #if DEBUG print("DEBUG: Failed to create URL") + #endif return } // Check if device can open the URL guard UIApplication.shared.canOpenURL(url) else { + #if DEBUG print("DEBUG: Cannot open URL - phone not available") + #endif return } + #if DEBUG print("DEBUG: Opening URL: \(url)") + #endif UIApplication.shared.open(url, options: [:], completionHandler: { success in + #if DEBUG print("DEBUG: URL open result: \(success)") + #endif }) }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (5)
218-227: Fix inconsistent map position constant.The
showMapActionmethod uses a hardcoded value of 100.0 which doesn't match the sheet position calculations elsewhere.@objc private func showMapAction() { // Animate to show more map (closed position) - let kDefaultClosedMapPosition: CGFloat = 100.0 + let positions = mapSheetPositions guard let contentViewTopConstraint = contentViewTopConstraint else { return } UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut) { - contentViewTopConstraint.constant = kDefaultClosedMapPosition + contentViewTopConstraint.constant = positions.closed self.view.layoutIfNeeded() }
420-427: Gate CTX merchant debug logging behind DEBUG flag.Debug prints with merchant information should not appear in production logs.
+ #if DEBUG // Debug logging for CTX merchant info if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { print("🎯 CTX MERCHANT INFO DEBUG: \(pointOfUse.name)") print(" CTX enabled: \(merchantInfo.enabled)") print(" Local active: \(pointOfUse.active)") print(" Will update view with enabled: \(merchantInfo.enabled)") } + #endif
277-277: Avoid force unwrapping the contentViewTopConstraint.Force unwrapping can cause crashes if the constraint is nil.
- contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + let topConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + contentViewTopConstraint = topConstraint constraint = [ - contentViewTopConstraint!, + topConstraint, contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
104-174: Consolidate map sheet positions and remove debug logging.The sheet positions are computed inline multiple times with inconsistent values. Also, debug prints should be gated.
+ private var mapSheetPositions: (opened: CGFloat, half: CGFloat, closed: CGFloat) { + let height = view.frame.size.height + return (opened: height * 0.2, half: height * 0.5 - 72, closed: height * 0.75) + } + @objc private func handlePanGesture(_ sender: UIPanGestureRecognizer) { + #if DEBUG print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #endif guard let contentViewTopConstraint = contentViewTopConstraint else { + #if DEBUG print("DEBUG: contentViewTopConstraint is nil!") + #endif return } let translatedPoint: CGPoint = sender.translation(in: view) let currentY = contentViewTopConstraint.constant + let positions = mapSheetPositions switch sender.state { case .changed: // Only handle vertical movement and constrain within bounds let newY = currentY + translatedPoint.y - // Constrain movement between closed position and maximum expanded position - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 // Mostly closed (more map visible) - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Default position higher by about an inch - let kDefaultOpenedMapPosition = screenHeight * 0.2 // Mostly open (less map visible, more content) // Allow dragging between open position (top) and closed position (bottom) - let maxY = kDefaultClosedMapPosition // Don't allow dragging below closed position - let minY = kDefaultOpenedMapPosition // Don't allow dragging above open position + let maxY = positions.closed + let minY = positions.opened contentViewTopConstraint.constant = max(minY, min(maxY, newY)) sender.setTranslation(.zero, in: view) case .ended: let velocityInView = sender.velocity(in: view) let velocityY: CGFloat = velocityInView.y let finalCurrentY = contentViewTopConstraint.constant - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 // Mostly closed (more map visible) - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 // Higher by about an inch - let kDefaultOpenedMapPosition = screenHeight * 0.2 var finalY: CGFloat if velocityY > 300 { // Fast downward swipe - snap to closed position (more map visible) - finalY = kDefaultClosedMapPosition + finalY = positions.closed } else if velocityY < -300 { // Fast upward swipe - snap to open position (less map visible) - finalY = kDefaultOpenedMapPosition + finalY = positions.opened } else { // No strong velocity, snap to nearest position based on current position - let midPoint1 = (kDefaultOpenedMapPosition + kDefaultBottomHalfPosition) / 2 - let midPoint2 = (kDefaultBottomHalfPosition + kDefaultClosedMapPosition) / 2 + let midPoint1 = (positions.opened + positions.half) / 2 + let midPoint2 = (positions.half + positions.closed) / 2 if finalCurrentY < midPoint1 { - finalY = kDefaultOpenedMapPosition + finalY = positions.opened } else if finalCurrentY < midPoint2 { - finalY = kDefaultBottomHalfPosition + finalY = positions.half } else { - finalY = kDefaultClosedMapPosition + finalY = positions.closed } }
36-36: Remove unused implicitly unwrapped optional property.The
showMapButtonproperty is never initialized and is commented out in the code. This creates a crash risk if accidentally accessed.private var contentViewTopConstraint: NSLayoutConstraint? - private var showMapButton: UIButton!Also remove the related unused methods
setupMapButton()at line 192 andupdateMapButtonVisibility()at line 231 since they reference this removed property.
🧹 Nitpick comments (8)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (6)
38-42: Let the formatter round; avoid pre-flooring distance.floor(distance) can under-report by up to 0.99 m/ft before formatting. The formatter should handle rounding.
- let distanceText: String = ExploreDash.distanceFormatter - .string(from: Measurement(value: floor(distance), unit: UnitLength.meters)) + let distanceText: String = ExploreDash.distanceFormatter + .string(from: Measurement(value: distance, unit: .meters))
26-26: Remove unused distanceLabel property (and fix SwiftLint IUO warning).distanceLabel is never shown and immediately hidden; drop it to simplify the view hierarchy and silence the IUO lint.
- private var distanceLabel: UILabel! + // distanceLabel removed; distance is rendered via subLabel
47-49: Dead code: distanceLabel is always hidden and unused. Remove.No UI effect; reduces noise.
- // Hide separate distance label since we're using subLabel - distanceLabel.isHidden = true
67-75: Avoid adding a permanently hidden arranged subview.Adding a hidden arranged subview increases complexity and can affect spacing. Remove this block (and the property) entirely.
- // Distance label (separate from merchant name) - distanceLabel = UILabel() - distanceLabel.translatesAutoresizingMaskIntoConstraints = false - distanceLabel.font = .systemFont(ofSize: 12, weight: .regular) - distanceLabel.textColor = .dw_tertiaryText() - distanceLabel.isHidden = true - distanceLabel.textAlignment = .right - mainStackView.addArrangedSubview(distanceLabel) + // removed distanceLabel; subLabel is the single source of subtitle/distance
33-36: Minimize churn on legacy cells to reduce merge conflicts with feature/piggycards.Avoid structural additions (like extra labels) unless needed; gate behavior behind a feature flag to ease future merges.
Also applies to: 67-75
33-45: Fall back to source when distance is unavailable
Mirror AtmItemCell by showingpointOfUse.sourceinstead of hiding the subtitle:- } else { - subLabel.isHidden = true - } + } else if let source = pointOfUse.source, !source.isEmpty { + subLabel.isHidden = false + subLabel.text = source + } else { + subLabel.isHidden = true + }DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (1)
91-107: Refactor logout to propagate errors.The
.logoutcase is defined inCTXSpendEndpoint.swift, so the endpoint exists. CurrentlyCTXSpendService.logoutswallows any failures (catch { DSLogger.log(...) }) and always clears tokens without notifying callers. Change it to an async‐throws or Result-returning API so callers (e.g.CTXSpendUserAuthViewModel) can handle and surface logout errors, for example:func logout() async throws { try await CTXSpendAPI.shared.request(.logout) await MainActor.run { KeychainService.delete(key: Keys.accessToken) … updateSignInState() } }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
1085-1096: Remove debug logging from grabber pan gesture setup.Debug prints should be gated for production builds.
func setupGrabberPanGesture(target: Any, action: Selector) { guard let grabberContainer = grabberContainer else { + #if DEBUG print("DEBUG: grabberContainer is nil!") + #endif return } + #if DEBUG print("DEBUG: Setting up pan gesture on grabber container") + #endif let panRecognizer = UIPanGestureRecognizer(target: target, action: action) panRecognizer.minimumNumberOfTouches = 1 panRecognizer.maximumNumberOfTouches = 1 grabberContainer.addGestureRecognizer(panRecognizer) + #if DEBUG print("DEBUG: Pan gesture added to grabber container") + #endif }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift(3 hunks)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendEndpoint.swift(3 hunks)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(8 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(8 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendPayViewModel.swift(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- DashWallet/Sources/Models/Explore Dash/Services/CTXSpendEndpoint.swift
- DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is confirmed unused after code analysis. Radius functionality is properly handled through model.currentRadius and filters?.currentRadius, making the controller property redundant and safe to remove.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is unused and should be removed. Radius functionality is handled through the model.currentRadius and filters system instead of a controller-level property.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
🧬 Code graph analysis (4)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (2)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendAPI.swift (2)
request(82-90)request(92-113)DashWallet/Sources/UI/Explore Dash/Views/DashSpend/CTXSpendUserAuthViewModel.swift (1)
logout(83-85)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (5)
DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/UI/Views/BaseController/BaseViewController+NetworkReachability.swift (2)
stopNetworkMonitoring(52-54)startNetworkMonitoring(36-50)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
callAction(87-102)websiteAction(176-190)DashWallet/Sources/Models/Explore Dash/Model/Entites/ExplorePointOfUse.swift (2)
toSavingPercentages(79-81)isEnabled(88-90)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (1)
logout(90-108)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (4)
mapView(239-244)mapView(246-256)mapView(272-278)configureHierarchy(203-233)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (2)
configureHierarchy(337-493)showMap(109-120)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
configureHierarchy(203-226)payAction(182-196)callAction(130-158)setupGrabberPanGesture(1085-1096)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift (1)
update(41-49)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/AtmItemCell.swift (1)
update(28-48)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 33-33: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 35-35: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 36-36: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 37-37: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 38-38: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 35-35: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 36-36: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 37-37: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 38-38: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 32-32: Prefer -> Void over -> ()
(void_return)
[Warning] 35-35: Prefer -> Void over -> ()
(void_return)
[Warning] 36-36: Prefer -> Void over -> ()
(void_return)
[Warning] 37-37: Prefer -> Void over -> ()
(void_return)
[Warning] 38-38: Prefer -> Void over -> ()
(void_return)
[Warning] 39-39: Prefer -> Void over -> ()
(void_return)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 130-130: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 205-205: Prefer object literals over image and color inits
(object_literal)
[Warning] 276-276: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 382-382: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 350-350: Prefer object literals over image and color inits
(object_literal)
[Warning] 481-481: Prefer object literals over image and color inits
(object_literal)
[Warning] 547-547: Prefer object literals over image and color inits
(object_literal)
[Warning] 957-957: Prefer object literals over image and color inits
(object_literal)
[Warning] 1097-1097: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
[Warning] 896-896: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 938-938: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 972-972: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1129-1129: Unimplemented functions should be marked as unavailable
(unavailable_function)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 36-36: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 55-55: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 277-277: Force unwrapping should be avoided
(force_unwrapping)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift
[Warning] 26-26: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
- Fix AllMerchantLocationsViewController to respect current filter radius settings - Add currentFilters parameter to AllMerchantLocationsDataProvider constructor - Ensure "Show all locations" list matches the location count displayed - Update PointOfUseDetailsView to fetch actual location count using same bounds calculation as list view - Pass current filter settings through view hierarchy from MerchantListViewController to AllMerchantLocationsViewController 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
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.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (1)
109-112: Fix typo in user-facing copy (“redius” → “radius”).User-visible string has a spelling error.
Apply:
- NSLocalizedString("Your location is used to show your position on the map, merchants in the selected redius and improve search results.", + NSLocalizedString("Your location is used to show your position on the map, merchants in the selected radius and improve search results.", comment: "")DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
246-248: Guard latitude/longitude to avoid crash.- mapView.initialCenterLocation = .init(latitude: pointOfUse.latitude!, longitude: pointOfUse.longitude!) + guard let lat = pointOfUse.latitude, let lon = pointOfUse.longitude else { return } + mapView.initialCenterLocation = .init(latitude: lat, longitude: lon)
375-377: Avoid force-unwrap on external URL.- UIApplication.shared.open(URL(string: CTXConstants.ctxGiftCardAgreementUrl)!, options: [:], completionHandler: nil) + if let url = URL(string: CTXConstants.ctxGiftCardAgreementUrl) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
182-186: Normalize website URL (prepend https when missing).- guard let website = merchant.website, let url = URL(string: website) else { return } - UIApplication.shared.open(url, options: [:], completionHandler: nil) + guard var website = merchant.website, !website.isEmpty else { return } + if !website.lowercased().hasPrefix("http") { website = "https://\(website)" } + guard let url = URL(string: website) else { return } + UIApplication.shared.open(url, options: [:], completionHandler: nil)
♻️ Duplicate comments (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (1)
67-67: Remove unused radius property (confirmed in prior review).Radius is driven by model/filters; this controller property is redundant.
- internal var radius = 5 // In miles - 5 mile radius as requestedDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
37-38: Delete unused IUO property.Eliminate UIButton! to remove a crash path.
- private var contentViewTopConstraint: NSLayoutConstraint? - private var showMapButton: UIButton! + private var contentViewTopConstraint: NSLayoutConstraint?
422-429: Gate CTX merchant debug prints behind DEBUG.- // Debug logging for CTX merchant info - if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { + #if DEBUG + // Debug logging for CTX merchant info + if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { print("🎯 CTX MERCHANT INFO DEBUG: \(pointOfUse.name)") print(" CTX enabled: \(merchantInfo.enabled)") print(" Local active: \(pointOfUse.active)") print(" Will update view with enabled: \(merchantInfo.enabled)") } + #endif
272-280: Remove force unwrap of contentViewTopConstraint.- constraint = [ - contentViewTopConstraint!, + guard let topConstraint = contentViewTopConstraint else { return } + constraint = [ + topConstraint,DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
136-164: Gate phone debug logs to DEBUG (repeat of prior review).- print("DEBUG: Original phone: \(phone)") + #if DEBUG + print("DEBUG: Original phone: \(phone)") + #endif @@ - print("DEBUG: Extracted digits: \(digits)") + #if DEBUG + print("DEBUG: Extracted digits: \(digits)") + #endif @@ - print("DEBUG: URL string: \(urlString)") + #if DEBUG + print("DEBUG: URL string: \(urlString)") + #endif @@ - print("DEBUG: Failed to create URL") + #if DEBUG + print("DEBUG: Failed to create URL") + #endif @@ - print("DEBUG: Cannot open URL - phone not available") + #if DEBUG + print("DEBUG: Cannot open URL - phone not available") + #endif @@ - print("DEBUG: Opening URL: \(url)") + #if DEBUG + print("DEBUG: Opening URL: \(url)") + #endif UIApplication.shared.open(url, options: [:], completionHandler: { success in - print("DEBUG: URL open result: \(success)") + #if DEBUG + print("DEBUG: URL open result: \(success)") + #endif })
🧹 Nitpick comments (13)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (1)
207-215: Consider showing the count of physical merchants, not all items.You guard on physicalMerchants.isEmpty but display items.count, which may include online entries.
- return String(format: NSLocalizedString("%d merchant(s) in %@", comment: "#bc-ignore!"), items.count, + return String(format: NSLocalizedString("%d merchant(s) in %@", comment: "#bc-ignore!"), physicalMerchants.count, ExploreDash.distanceFormatter .string(from: Measurement(value: model.currentRadius, unit: UnitLength.meters)))- return String(format: NSLocalizedString("%d merchant(s) in %@", comment: "#bc-ignore!"), items.count, + return String(format: NSLocalizedString("%d merchant(s) in %@", comment: "#bc-ignore!"), physicalMerchants.count, ExploreDash.distanceFormatter .string(from: Measurement(value: model.currentRadiusMiles, unit: UnitLength.miles)))DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (3)
25-27: “Max 50% of screen height” comment is misleading for a fixed 320pt value.Either remove the claim or compute it dynamically to truly cap at 50%.
Minimal fix (comment only):
-internal let kDefaultOpenedMapPosition: CGFloat = 320.0 // Max 50% of screen height +internal let kDefaultOpenedMapPosition: CGFloat = 320.0 // Fixed height in pointsAlternative (dynamic) outside this hunk:
// Add internal func openedMapPosition(in view: UIView) -> CGFloat { min(view.bounds.height * 0.5, 320.0) } // Then replace uses of kDefaultOpenedMapPosition with openedMapPosition(in: self.view)
534-547: Drag bounds allow 80% but snap only to 320/closed → jarring jump.Either clamp to 320 while dragging or add a third snap to the 80% position.
Option A (two-state): clamp to opened position during drag.
- let maxY = view.frame.size.height * 0.8 // Allow dragging down to 80% of screen + let maxY = kDefaultOpenedMapPosition // Clamp to opened positionOption B (three-state): keep 80% drag and snap logic, but add a third snap when finalCurrentY is near maxY (outside this hunk).
Also applies to: 555-576
567-570: Fix SwiftLint vertical parameter alignment.Align multiline parameters.
- UIView.animate(withDuration: animationDuration, delay: 0, - usingSpringWithDamping: 0.8, initialSpringVelocity: 0, - options: .curveEaseOut) { + UIView.animate(withDuration: animationDuration, + delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0, + options: .curveEaseOut) {DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (2)
62-70: Cache check should include lastUserPoint for correctness.Same query/bounds but different userPoint can change distance ordering.
- if lastQuery == query && !items.isEmpty && lastBounds == finalBounds { + if lastQuery == query && !items.isEmpty && lastBounds == finalBounds && lastUserPoint?.latitude == finalUserPoint?.latitude && lastUserPoint?.longitude == finalUserPoint?.longitude { completion(.success(items)) return }
84-91: Filtering to active locations only: confirm intended UX.If “Temporarily unavailable” is represented by active=false or enabled=false, this filter may hide locations users still expect to see (but disabled to buy). Verify.
Would you like me to add unit tests covering:
- authorized vs denied vs needsAuthorization startup states
- filtersToUse radius driving bounds
- active vs inactive location visibility?
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
54-58: Clamp negative map contentInset.On small screens, bottom inset can go negative. Clamp to ≥ 0.
- mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: mapView.frame.height - detailsView.frame.height - 10, - right: 0) + let bottom = max(0, mapView.frame.height - detailsView.frame.height - 10) + mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottom, right: 0)
106-113: Avoid shadowing and gate debug prints.Rename the local, and keep prints out of release builds.
- print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") - guard let contentViewTopConstraint = contentViewTopConstraint else { - print("DEBUG: contentViewTopConstraint is nil!") + #if DEBUG + print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #endif + guard let topConstraint = contentViewTopConstraint else { + #if DEBUG + print("DEBUG: contentViewTopConstraint is nil!") + #endif return } - let currentY = contentViewTopConstraint.constant + let currentY = topConstraint.constant
123-131: Unify sheet positions; remove magic numbers.Compute once per frame and reuse to reduce drift across code paths.
- let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 - let kDefaultOpenedMapPosition = screenHeight * 0.2 - let maxY = kDefaultClosedMapPosition - let minY = kDefaultOpenedMapPosition - contentViewTopConstraint.constant = max(minY, min(maxY, newY)) + let pos = mapPositions() + let maxY = pos.closed + let minY = pos.opened + topConstraint.constant = max(minY, min(maxY, newY)) @@ - let finalCurrentY = contentViewTopConstraint.constant - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 - let kDefaultBottomHalfPosition = screenHeight * 0.5 - 72 - let kDefaultOpenedMapPosition = screenHeight * 0.2 + let finalCurrentY = topConstraint.constant + let pos = mapPositions() @@ - finalY = kDefaultClosedMapPosition + finalY = pos.closed @@ - finalY = kDefaultOpenedMapPosition + finalY = pos.opened @@ - let midPoint1 = (kDefaultOpenedMapPosition + kDefaultBottomHalfPosition) / 2 - let midPoint2 = (kDefaultBottomHalfPosition + kDefaultClosedMapPosition) / 2 + let midPoint1 = (pos.opened + pos.half) / 2 + let midPoint2 = (pos.half + pos.closed) / 2 @@ - finalY = kDefaultOpenedMapPosition + finalY = pos.opened @@ - finalY = kDefaultBottomHalfPosition + finalY = pos.half @@ - finalY = kDefaultClosedMapPosition + finalY = pos.closed @@ - contentViewTopConstraint.constant = finalY + topConstraint.constant = finalYAdd helper (outside changed hunk, near other privates):
private func mapPositions() -> (opened: CGFloat, half: CGFloat, closed: CGFloat) { let h = view.bounds.height return (opened: h * 0.2, half: h * 0.5 - 72, closed: h * 0.75) }Also applies to: 139-163
221-231: Replace fixed 100.0 with unified closed position.- let kDefaultClosedMapPosition: CGFloat = 100.0 - guard let contentViewTopConstraint = contentViewTopConstraint else { return } + let pos = mapPositions() + guard let topConstraint = contentViewTopConstraint else { return } @@ - contentViewTopConstraint.constant = kDefaultClosedMapPosition + topConstraint.constant = pos.closedDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (3)
1065-1071: Localize “away”.Keep UI strings localized.
- return "\(address)\n\(distanceText) away" + let format = NSLocalizedString("%@ away", comment: "Distance suffix") + return "\(address)\n" + String(format: format, distanceText)
569-583: Wrap noisy DEBUG_FETCH_ logs in DEBUG.*These logs are extensive; keep them out of releases.
Example for the first block:
- print("DEBUG_FETCH_COUNT: Starting location fetch for merchant: '\(merchant.name)'") + #if DEBUG + print("DEBUG_FETCH_COUNT: Starting location fetch for merchant: '\(merchant.name)'") print("DEBUG_FETCH_COUNT: pointOfUseId: '\(merchant.pointOfUseId)'") if let merchantData = merchant.merchant { print("DEBUG_FETCH_COUNT: merchantId: '\(merchantData.merchantId)'") } print("DEBUG_FETCH_COUNT: merchant.active: \(merchant.active)") + #endifApply similarly to the remaining DEBUG_FETCH_* prints in this method and in updateShowAllLocationsButton().
Also applies to: 624-639, 650-657, 660-674, 676-676
1206-1216: Gate grabber pan debug prints behind DEBUG.- guard let grabberContainer = grabberContainer else { - print("DEBUG: grabberContainer is nil!") + guard let grabberContainer = grabberContainer else { + #if DEBUG + print("DEBUG: grabberContainer is nil!") + #endif return } - print("DEBUG: Setting up pan gesture on grabber container") + #if DEBUG + print("DEBUG: Setting up pan gesture on grabber container") + #endif @@ - print("DEBUG: Pan gesture added to grabber container") + #if DEBUG + print("DEBUG: Pan gesture added to grabber container") + #endif
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(10 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(7 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AllMerchantLocationsViewController.swift(3 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift(4 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is confirmed unused after code analysis. Radius functionality is properly handled through model.currentRadius and filters?.currentRadius, making the controller property redundant and safe to remove.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is unused and should be removed. Radius functionality is handled through the model.currentRadius and filters system instead of a controller-level property.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
🧬 Code graph analysis (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (2)
DashWallet/Sources/Models/Explore Dash/Infrastructure/DAO Impl/MerchantDAO.swift (3)
items(38-39)items(42-175)allLocations(200-247)DashWallet/Sources/Models/Explore Dash/ExploreDash.swift (1)
allLocations(189-192)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (7)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
callAction(89-104)websiteAction(178-192)DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/UI/Views/BaseController/BaseViewController+NetworkReachability.swift (2)
stopNetworkMonitoring(52-54)startNetworkMonitoring(36-50)DashWallet/Sources/Models/Explore Dash/Infrastructure/DAO Impl/MerchantDAO.swift (3)
allLocations(200-247)items(38-39)items(42-175)DashWallet/Sources/Models/Explore Dash/ExploreDash.swift (1)
allLocations(189-192)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (1)
items(32-74)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (1)
logout(90-108)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AllMerchantLocationsViewController.swift (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (6)
tableView(628-684)tableView(686-702)tableView(708-725)tableView(727-738)tableView(740-762)tableView(764-768)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (3)
tableView(117-144)tableView(146-171)tableView(173-186)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (3)
mapView(239-244)mapView(246-256)mapView(272-278)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (4)
mapView(239-244)mapView(246-256)mapView(272-278)configureHierarchy(203-233)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (2)
configureHierarchy(337-493)showMap(109-120)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
configureHierarchy(209-232)callAction(136-164)websiteAction(182-186)setupGrabberPanGesture(1205-1216)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AllMerchantLocationsViewController.swift (1)
configureHierarchy(75-82)DashWallet/Sources/Categories/UIHostingController+DashWallet.swift (1)
setDetent(21-31)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 136-136: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 33-33: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 35-35: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 36-36: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 37-37: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 38-38: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 112-112: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 35-35: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 36-36: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 37-37: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 38-38: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 126-126: Unimplemented functions should be marked as unavailable
(unavailable_function)
[Warning] 32-32: Prefer -> Void over -> ()
(void_return)
[Warning] 35-35: Prefer -> Void over -> ()
(void_return)
[Warning] 36-36: Prefer -> Void over -> ()
(void_return)
[Warning] 37-37: Prefer -> Void over -> ()
(void_return)
[Warning] 38-38: Prefer -> Void over -> ()
(void_return)
[Warning] 39-39: Prefer -> Void over -> ()
(void_return)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 211-211: Prefer object literals over image and color inits
(object_literal)
[Warning] 282-282: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 388-388: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 356-356: Prefer object literals over image and color inits
(object_literal)
[Warning] 450-450: Prefer object literals over image and color inits
(object_literal)
[Warning] 516-516: Prefer object literals over image and color inits
(object_literal)
[Warning] 1077-1077: Prefer object literals over image and color inits
(object_literal)
[Warning] 1217-1217: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
[Warning] 1016-1016: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1058-1058: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1092-1092: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1249-1249: Unimplemented functions should be marked as unavailable
(unavailable_function)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift
[Warning] 568-568: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 569-569: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 37-37: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 57-57: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 279-279: Force unwrapping should be avoided
(force_unwrapping)
🔇 Additional comments (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (1)
193-196: AllPointOfUseDetailsViewControllerinitializers includecurrentFiltersVerified call sites in MerchantListViewController.swift and ExplorePointOfUseListViewController.swift; approving changes.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AllMerchantLocationsViewController.swift (3)
25-31: Initializer now accepts currentFilters: looks good.Plumbs filter context down cleanly.
50-57: Hiding segments in this screen: good choice.Delegating .filters and .search row counts to super preserves layout.
69-73: Data provider wiring with currentFilters: good.Aligned with the list → details → all-locations flow.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (1)
142-149: Details VC construction with currentFilters: good.Keeps filter state when drilling into details.
...allet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
Outdated
Show resolved
Hide resolved
...allet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
Outdated
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
...t/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift
Outdated
Show resolved
Hide resolved
- Fix location permission handling to show global locations when services denied/unavailable - Add comprehensive debug logging with proper string formatting - Replace magic numbers with named constants for better maintainability - Fix typo: "redius" → "radius" in location service popup messages - Improve AllMerchantLocationsDataProvider filter bounds calculation Resolves issues where: - Location counts showed 0 on devices without location permission - AMC and other merchants showed blank location lists - Debug logging caused compilation errors 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
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.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AtmListViewController.swift (4)
20-22: RenameAtmListSegmnets→AtmListSegmentsacross AtmListViewController.swift
Update the enum declaration, MARK comment, extension and all call sites in this file.--- AtmListViewController.swift - // MARK: - AtmListSegmnets + // MARK: - AtmListSegments - enum AtmListSegmnets: Int { + enum AtmListSegments: Int { ... - extension AtmListSegmnets { + extension AtmListSegments { ... - AtmListSegmnets.all.pointOfUseListSegment + AtmListSegments.all.pointOfUseListSegment
28-31: Move cross-type==overloads to file scope
- In
AtmListViewController.swift(lines 28–31), remove the member overloadand add at file scope:- static func ==(lhs: PointOfUseListSegment, rhs: AtmListSegmnets) -> Bool { - lhs.tag == rhs.rawValue - }func ==(lhs: PointOfUseListSegment, rhs: AtmListSegmnets) -> Bool { lhs.tag == rhs.rawValue } func ==(lhs: AtmListSegmnets, rhs: PointOfUseListSegment) -> Bool { rhs == lhs }- In
MerchantListViewController.swift(lines 30–32), remove the member overloadand add at file scope:- static func ==(lhs: PointOfUseListSegment, rhs: MerchantsListSegment) -> Bool { - lhs.tag == rhs.rawValue - }func ==(lhs: PointOfUseListSegment, rhs: MerchantsListSegment) -> Bool { lhs.tag == rhs.rawValue } func ==(lhs: MerchantsListSegment, rhs: PointOfUseListSegment) -> Bool { rhs == lhs }- Keep the same-type
static func ==(PointOfUseListSegment, PointOfUseListSegment)inPointOfUseListModel.swiftforEquatableconformance.
33-35: Remove unuseddefaultFilters
In AtmListViewController (lines 33–35), drop the two dead-code lines:var defaultFilters = PointOfUseListFilters() defaultFilters.radius = .twenty
61-65: Implement missing SellAtmsDataProvider and fix .sell mapping
.sellcurrently returnsBuyAndSellAtmsDataProvider(), duplicating the.buyAndSellcase. There is noSellAtmsDataProviderclass—add one (subclassingBaseAtmsDataProvider) and change the.sellbranch to return it. (AtmListViewController.swift lines 61–65)
♻️ Duplicate comments (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (2)
136-164: Gate phone debug prints behind DEBUG.Avoid leaking to production logs. Keep functionality unchanged.
@objc func callAction() { guard let phone = merchant.phone, !phone.isEmpty else { return } - print("DEBUG: Original phone: \(phone)") + #if DEBUG + print("DEBUG: Original phone: \(phone)") + #endif @@ - let digits = phone.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() - guard !digits.isEmpty else { return } - print("DEBUG: Extracted digits: \(digits)") + let digits = phone.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() + guard !digits.isEmpty else { return } + #if DEBUG + print("DEBUG: Extracted digits: \(digits)") + #endif @@ - let urlString = "telprompt:\(digits)" - print("DEBUG: URL string: \(urlString)") + let urlString = "telprompt:\(digits)" + #if DEBUG + print("DEBUG: URL string: \(urlString)") + #endif @@ - guard let url = URL(string: urlString) else { - print("DEBUG: Failed to create URL") - return - } + guard let url = URL(string: urlString) else { + #if DEBUG + print("DEBUG: Failed to create URL") + #endif + return + } @@ - guard UIApplication.shared.canOpenURL(url) else { - print("DEBUG: Cannot open URL - phone not available") - return - } + guard UIApplication.shared.canOpenURL(url) else { + #if DEBUG + print("DEBUG: Cannot open URL - phone not available") + #endif + return + } @@ - print("DEBUG: Opening URL: \(url)") - UIApplication.shared.open(url, options: [:], completionHandler: { success in - print("DEBUG: URL open result: \(success)") - }) + #if DEBUG + print("DEBUG: Opening URL: \(url)") + #endif + UIApplication.shared.open(url, options: [:], completionHandler: { success in + #if DEBUG + print("DEBUG: URL open result: \(success)") + #endif + }) }
1141-1156: Gate CTX merchant field dumps and GameStop denomination logs behind DEBUG (or remove).These prints expose merchant identifiers and should not appear in release builds.
- if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") || self.merchant.name.lowercased().contains("buffalo") { + #if DEBUG + if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") || self.merchant.name.lowercased().contains("buffalo") { print("🎯 MERCHANT DEBUG: \(self.merchant.name)") @@ print(" ----------") } + #endif- // Debug denominationsType for GameStop specifically - if self.merchant.name.lowercased().contains("gamestop") { + // Debug denominationsType for GameStop specifically + #if DEBUG + if self.merchant.name.lowercased().contains("gamestop") { print("🎯 GAMESTOP DENOMINATIONS TYPE DEBUG:") print(" Original denominationsType: '\(denominationsType)'") print(" Lowercased denominationsType: '\(lowercasedType)'") } + #endif @@ - case "fixed": - if self.merchant.name.lowercased().contains("gamestop") { - print(" → Matched 'fixed' case - returning Fixed amounts") - } + case "fixed": return NSLocalizedString("Fixed amounts", comment: "DashSpend") - case "flexible", "min-max": - if self.merchant.name.lowercased().contains("gamestop") { - print(" → Matched 'flexible'/'min-max' case - returning Flexible amounts") - } + case "flexible", "min-max": return NSLocalizedString("Flexible amounts", comment: "DashSpend") - default: - if self.merchant.name.lowercased().contains("gamestop") { - print(" → Matched 'default' case - returning Fixed amounts") - } + default: return NSLocalizedString("Fixed amounts", comment: "DashSpend")Also applies to: 1163-1185
🧹 Nitpick comments (12)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AtmListViewController.swift (2)
92-100: Improve i18n: pluralization and format
"%d ATM(s) in %@"is not localizable for plural rules. Use a .stringsdict entry andString.localizedStringWithFormat.Example:
- Localized key: “ATM_COUNT_IN_DISTANCE” → Strings: “%#@ATM_COUNT@ in %@”
- stringsdict plural for ATM_COUNT with one/other forms (“%d ATM” / “%d ATMs”).
Swift:let count = items.count let distance = ExploreDash.distanceFormatter.string(from: Measurement(value: model.currentRadiusMiles, unit: .miles)) return String(format: NSLocalizedString("ATM_COUNT_IN_DISTANCE", comment: ""), count, distance)
116-116: Remove stray semicolonSwift doesn’t need it; keeping style consistent helps.
- title = NSLocalizedString("ATMs", comment: ""); + title = NSLocalizedString("ATMs", comment: "")DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (10)
182-186: Normalize website URL (add https:// if missing).Prevents tap failures when merchant.website lacks a scheme.
- guard let website = merchant.website, let url = URL(string: website) else { return } - UIApplication.shared.open(url, options: [:], completionHandler: nil) + guard let website = merchant.website else { return } + let normalizedWebsite: String + if website.hasPrefix("http://") || website.hasPrefix("https://") { + normalizedWebsite = website + } else { + normalizedWebsite = "https://" + website + } + guard let url = URL(string: normalizedWebsite) else { return } + UIApplication.shared.open(url, options: [:], completionHandler: nil)
210-226: Use dynamic/system colors for background (Dark Mode).Hardcoded RGB will look off in Dark Mode. Prefer theme color.
- backgroundColor = UIColor(red: 0.961, green: 0.965, blue: 0.969, alpha: 1) // #f5f6f7 + backgroundColor = .dw_secondaryBackground()
360-360: Adopt Dynamic Type fonts.Use app typography helpers for scaling instead of fixed 14pt.
- payButton.titleLabel?.font = UIFont.systemFont(ofSize: 14) // 14px as per Figma + payButton.titleLabel?.font = .dw_font(forTextStyle: .footnote) + payButton.titleLabel?.adjustsFontForContentSizeCategory = true- addressTextLabel.font = .systemFont(ofSize: 14, weight: .regular) // 14px regular as per Figma + addressTextLabel.font = .dw_font(forTextStyle: .body)- phoneButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .regular) // 14px as per Figma + phoneButton.titleLabel?.font = .dw_font(forTextStyle: .body)Also applies to: 449-451, 517-517
935-957: Show the U.S.-only notice conditionally.Display only for US merchants or US device region.
let noticeLabel = UILabel() noticeLabel.translatesAutoresizingMaskIntoConstraints = false noticeLabel.text = NSLocalizedString("Note: This card works only in the United States.", comment: "DashSpend") @@ container.addSubview(noticeLabel) + // Only show for US-context merchants + let isUSMerchant = (merchant.territory == "US") + let isUSDevice = Locale.current.regionCode == "US" + container.isHidden = !(isUSMerchant || isUSDevice)
1019-1026: Localize “Physical Merchant” strings.These are user-facing but not localized.
- return m.type == .onlineAndPhysical ? "Physical Merchant, Online" : "Physical Merchant" + return m.type == .onlineAndPhysical + ? NSLocalizedString("Physical Merchant, Online", comment: "Merchant type") + : NSLocalizedString("Physical Merchant", comment: "Merchant type")
1078-1097: Localize “away” suffix.Use a format string for “%{distance} away”.
- return "\(address)\n\(distanceText) away" + let awayText = String.localizedStringWithFormat(NSLocalizedString("%@ away", comment: "Distance away"), distanceText) + return "\(address)\n\(awayText)"- let distanceString = NSAttributedString(string: "\n\(distanceText) away", attributes: distanceAttributes) + let awayText = String.localizedStringWithFormat(NSLocalizedString("%@ away", comment: "Distance away"), distanceText) + let distanceString = NSAttributedString(string: "\n\(awayText)", attributes: distanceAttributes)Also applies to: 1119-1129
569-592: Remove or gate verbose DEBUG_FETCH_ prints and grabber logs.*Too noisy for production; also leaks merchant info.
- print("DEBUG_FETCH_COUNT: Starting location fetch for merchant: '\(merchant.name)'") + #if DEBUG + print("DEBUG_FETCH_COUNT: Starting location fetch for merchant: '\(merchant.name)'") @@ - print("DEBUG_FETCH_RESULTS: Location denied/not authorized, fetching all locations globally") + print("DEBUG_FETCH_RESULTS: Location denied/not authorized, fetching all locations globally") @@ - print("DEBUG_FETCH_COUNT: Final API call parameters:") + print("DEBUG_FETCH_COUNT: Final API call parameters:") @@ - print("DEBUG_FETCH_RESULTS: Results for '\(strongSelf.merchant.name)'") + print("DEBUG_FETCH_RESULTS: Results for '\(strongSelf.merchant.name)'") @@ - print("DEBUG_FETCH_RESULTS: Setting locationCount to \(newCount)") + print("DEBUG_FETCH_RESULTS: Setting locationCount to \(newCount)") @@ - print("DEBUG_FETCH_RESULTS: API call failed with error: \(error)") + print("DEBUG_FETCH_RESULTS: API call failed with error: \(error)") @@ - print("DEBUG_FETCH_RESULTS: updateShowAllLocationsButton called with locationCount: \(locationCount)") + print("DEBUG_FETCH_RESULTS: updateShowAllLocationsButton called with locationCount: \(locationCount)") @@ - print("DEBUG: grabberContainer is nil!") + print("DEBUG: grabberContainer is nil!") @@ - print("DEBUG: Setting up pan gesture on grabber container") + print("DEBUG: Setting up pan gesture on grabber container") @@ - print("DEBUG: Pan gesture added to grabber container") + print("DEBUG: Pan gesture added to grabber container") + #endifIf you prefer, replace with DSLogger.debug(...) which is compiled out in release builds.
Also applies to: 612-622, 625-633, 645-650, 654-659, 661-668, 676-704, 1232-1242
673-704: Avoid brittle subview scanning; store a reference to the button.Keep a weak property and set its title directly.
- // Find and update the "Show all locations" button text - // This is called after the location count is fetched - print("DEBUG_FETCH_RESULTS: updateShowAllLocationsButton called with locationCount: \(locationCount)") - guard !isShowAllHidden, let containerView = containerView else { - print("DEBUG_FETCH_RESULTS: Guard failed - isShowAllHidden: \(isShowAllHidden), containerView exists: \(containerView != nil)") - return - } - - print("DEBUG_FETCH_RESULTS: Container has \(containerView.arrangedSubviews.count) arranged subviews") - - // Find the show all locations block (should be the last arranged subview) - if let showAllBlock = containerView.arrangedSubviews.last { - print("DEBUG_FETCH_RESULTS: Found show all block with \(showAllBlock.subviews.count) subviews") - - // Find the button within the block - for (index, subview) in showAllBlock.subviews.enumerated() { - print("DEBUG_FETCH_RESULTS: Checking subview \(index): \(type(of: subview))") - if let button = subview as? UIButton { - let newTitle = String.localizedStringWithFormat( - NSLocalizedString("Show all locations (%d)", comment: "Show all locations with count"), - locationCount - ) - print("DEBUG_FETCH_RESULTS: Updating button title to: \(newTitle)") - button.setTitle(newTitle, for: .normal) - break - } - } - } else { - print("DEBUG_FETCH_RESULTS: No show all block found in container") - } + guard !isShowAllHidden, let button = showAllLocationsButton else { return } + let newTitle = String.localizedStringWithFormat( + NSLocalizedString("Show all locations (%d)", comment: "Show all locations with count"), + locationCount + ) + button.setTitle(newTitle, for: .normal)- let showAllButton = UIButton() + let showAllButton = UIButton() showAllButton.translatesAutoresizingMaskIntoConstraints = false @@ showAllButton.addTarget(self, action: #selector(showAllLocationsAction), for: .touchUpInside) + self.showAllLocationsButton = showAllButtonOutside this hunk, add:
// Add near other stored properties private weak var showAllLocationsButton: UIButton?Also applies to: 706-745
35-41: Fix access control and closure signatures (SwiftLint).Class is internal, but members/initializer are public; also prefer () -> Void.
- public var payWithDashHandler: (()->())? - public var sellDashHandler: (()->())? - public var dashSpendAuthHandler: (()->())? - public var buyGiftCardHandler: (()->())? - public var showAllLocationsActionBlock: (() -> ())? - public var infoButtonActionBlock: (() -> ())? // For parent view controller to handle info button tap + var payWithDashHandler: (() -> Void)? + var sellDashHandler: (() -> Void)? + var dashSpendAuthHandler: (() -> Void)? + var buyGiftCardHandler: (() -> Void)? + var showAllLocationsActionBlock: (() -> Void)? + var infoButtonActionBlock: (() -> Void)? // For parent view controller to handle info button tap- public init(merchant: ExplorePointOfUse, isShowAllHidden: Bool = false, currentFilters: PointOfUseListFilters? = nil) { + init(merchant: ExplorePointOfUse, isShowAllHidden: Bool = false, currentFilters: PointOfUseListFilters? = nil) {Also applies to: 112-116
47-48: Remove unusedcoverImageView. Declared in PointOfUseDetailsView.swift but never referenced—delete its declaration or wire it up.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(7 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AtmListViewController.swift(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift
🧰 Additional context used
🧬 Code graph analysis (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
callAction(89-104)websiteAction(178-192)DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/Models/Explore Dash/Infrastructure/DAO Impl/MerchantDAO.swift (3)
allLocations(200-247)items(38-39)items(42-175)DashWallet/Sources/Models/Explore Dash/ExploreDash.swift (1)
allLocations(189-192)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (1)
items(32-73)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 136-136: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 33-33: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 35-35: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 36-36: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 37-37: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 38-38: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 112-112: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 35-35: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 36-36: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 37-37: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 38-38: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 126-126: Unimplemented functions should be marked as unavailable
(unavailable_function)
[Warning] 32-32: Prefer -> Void over -> ()
(void_return)
[Warning] 35-35: Prefer -> Void over -> ()
(void_return)
[Warning] 36-36: Prefer -> Void over -> ()
(void_return)
[Warning] 37-37: Prefer -> Void over -> ()
(void_return)
[Warning] 38-38: Prefer -> Void over -> ()
(void_return)
[Warning] 39-39: Prefer -> Void over -> ()
(void_return)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 211-211: Prefer object literals over image and color inits
(object_literal)
[Warning] 282-282: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 388-388: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 356-356: Prefer object literals over image and color inits
(object_literal)
[Warning] 450-450: Prefer object literals over image and color inits
(object_literal)
[Warning] 516-516: Prefer object literals over image and color inits
(object_literal)
[Warning] 1103-1103: Prefer object literals over image and color inits
(object_literal)
[Warning] 1243-1243: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
[Warning] 1042-1042: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1084-1084: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1118-1118: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1275-1275: Unimplemented functions should be marked as unavailable
(unavailable_function)
🔇 Additional comments (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AtmListViewController.swift (1)
85-88: LGTM: copy fix and explicit NSLocalizedString comment“radius” typo correction and explicit comment parameter look good.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
1-1: No embedded Uphold secrets found Info.plist references CLIENT_ID/CLIENT_SECRET via build variables, no plaintext credentials present.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
…tioning - Add dynamic location filtering based on visible map area when users zoom/pan - Update ExploreMapView to use actual visible bounds instead of hardcoded radius - Pass current map bounds through view hierarchy for consistent filtering - Enhance PointOfUseDetailsView to prioritize map bounds over filter radius - Update AllMerchantLocationsDataProvider to support dynamic map bounds - Adjust default dragger position to ensure "Show all locations" button is fully visible - Maintain 1-mile zoom level centered on selected merchant location Features: - Default behavior: Uses filter radius settings (5 miles, 20 miles, etc.) - Map interaction: Switches to actual visible map area for more intuitive results - Real-time updates: Results and counts update as users move the map - Improved UX: "Show all locations" button now fully visible by default 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
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.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (1)
49-55: Center computation bug: use multiplication for Bx (spherical midpoint)
bXmust becos(lat2) * cos(dLon); using+yields incorrect centers for many rectangles.- let bX = cos(lat2) + cos(dLon) + let bX = cos(lat2) * cos(dLon)Optional: for MapKit-derived rects, a simpler and precise alternative is Mercator midpoint:
MKMapPoint(x:(ne.x+sw.x)/2, y:(ne.y+sw.y)/2).coordinate.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (1)
499-508: Bug: you computefilterGroupsbut pass the unfiltered groups to the view.This ignores the location-aware filter restriction.
Apply:
- let filtersView = MerchantFiltersView( - currentFilters: model.filters, - filterGroups: currentSegment.filterGroups, + let filtersView = MerchantFiltersView( + currentFilters: model.filters, + filterGroups: filterGroups, territoriesDataSource: currentSegment.territoriesDataSource, sortOptions: currentSegment.sortOptions ) { [weak self] filters in
♻️ Duplicate comments (13)
DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m (3)
24-26: Authorize URL must include OAuth2 essentials (response_type, redirect_uri, PKCE) and avoid hardcoded scopes in a static string.Build the URL dynamically and pass PKCE params; the current format risks auth failures and prevents least-privilege scopes.
Apply:
-+ (NSString *)authorizeURLFormat { - return @"https://wallet.uphold.com/authorize/c184650d0cb44e73d8e5cb2021753a721c41f74a?scope=accounts:read%20cards:read%20cards:write%20transactions:deposit%20transactions:read%20transactions:transfer:application%20transactions:transfer:others%20transactions:transfer:self%20transactions:withdraw%20transactions:commit:otp%20user:read&state=%@"; -} ++ (NSString *)authorizeURLWithState:(NSString *)state + redirectURI:(NSString *)redirectURI + codeChallenge:(NSString *)codeChallenge + codeChallengeMethodS256:(BOOL)useS256 + scopes:(NSArray<NSString *> *)scopes { + NSString *scope = [scopes componentsJoinedByString:@"%20"]; + NSString *method = useS256 ? @"S256" : @"plain"; + return [NSString stringWithFormat: + @"https://wallet.uphold.com/authorize/%@?response_type=code&redirect_uri=%@&scope=%@&state=%@&code_challenge=%@&code_challenge_method=%@", + [self clientID], + [redirectURI stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet], + scope, + state, + codeChallenge, + method]; +}Be sure to update call sites (e.g., DWUpholdClient) to generate PKCE and pass minimal scopes.
32-38: Hardcoded OAuth credentials; remove secret, inject client ID, and rotate immediately.Client secrets must not ship in mobile apps; consider the secret compromised. Use PKCE and rotate in Uphold.
Apply:
+ (NSString *)clientID { - return @"c184650d0cb44e73d8e5cb2021753a721c41f74a"; + NSString *value = [NSBundle.mainBundle objectForInfoDictionaryKey:@"UPHOLD_CLIENT_ID"]; + NSAssert(value.length > 0, @"Missing UPHOLD_CLIENT_ID in Info.plist"); + return value; } -+ (NSString *)clientSecret { - return @"da72feee8236f7709df6d0c235a8896ad45f2a91"; -} +// Public clients must not embed a client secret. Use OAuth 2.0 PKCE.Follow-ups:
- Purge the secret from git history and rotate on Uphold.
- Replace any Basic Auth/token exchange using clientSecret with PKCE.
Run to find any remaining occurrences:
#!/bin/bash rg -n --hidden -S 'clientSecret|UPHOLD_CLIENT_SECRET|UPHOLD_CLIENT_ID|oauth2/revoke' -g '!**/Pods/**' -C2
48-50: Add token revocation endpoint; homepage/dashboard isn’t a logout.Expose /oauth2/revoke and ensure callers POST to it, then clear local tokens; optionally navigate to dashboard.
Apply:
+ (NSString *)logoutURLString { return @"https://wallet.uphold.com/dashboard"; } +// Revoke OAuth tokens (POST Authorization: Bearer ...). ++ (NSString *)revokeTokenURLString { + return @"https://api.uphold.com/oauth2/revoke"; +}Also declare in the header and update the logout flow to call revoke before presenting the logout URL.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (1)
67-67: Remove unusedradiusproperty.Radius is managed by filters/model; this stored property is dead and confusing.
Apply:
-internal var radius = 5 // In miles - 5 mile radius as requestedDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (5)
40-40: Fix access level mismatch.The initializer is declared
publicwhile the containing class isinternal, causing a compilation error.Apply this fix:
- public init(pointOfUse: ExplorePointOfUse, isShowAllHidden: Bool = true, currentFilters: PointOfUseListFilters? = nil, currentMapBounds: ExploreMapBounds? = nil) { + init(pointOfUse: ExplorePointOfUse, isShowAllHidden: Bool = true, currentFilters: PointOfUseListFilters? = nil, currentMapBounds: ExploreMapBounds? = nil) {
38-38: Remove unused IUO property that creates crash risk.The
showMapButtonproperty is declared as implicitly unwrapped optional but is never properly initialized. This creates potential crash paths.Apply this fix:
- private var showMapButton: UIButton!Also remove the related dead code in
setupMapButton(),showMapAction(), andupdateMapButtonVisibility()methods since map button functionality appears to be disabled.
281-281: Avoid force unwrapping constraint.The force unwrap of
contentViewTopConstraintcan crash if the constraint is nil.Apply this fix:
- contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) - - constraint = [ - contentViewTopConstraint!, + let topConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + contentViewTopConstraint = topConstraint + + constraint = [ + topConstraint,
424-430: Debug prints should be gated behind DEBUG.These debug prints contain merchant identifiers and will appear in production logs.
Apply this fix:
- // Debug logging for CTX merchant info - if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { + #if DEBUG + // Debug logging for CTX merchant info + if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { print("🎯 CTX MERCHANT INFO DEBUG: \(pointOfUse.name)") print(" CTX enabled: \(merchantInfo.enabled)") print(" Local active: \(pointOfUse.active)") print(" Will update view with enabled: \(merchantInfo.enabled)") } + #endif
108-178: Unify sheet position constants to prevent inconsistencies.The pan gesture handler duplicates position calculations that are computed differently across methods (e.g.,
showMapActionuses 100.0 while this method uses percentage-based calculations). This can lead to inconsistent behavior.Create a centralized helper method:
+ // Consistent sheet positions + private func sheetPositions() -> (opened: CGFloat, half: CGFloat, closed: CGFloat) { + let h = view.bounds.height + return (opened: h * 0.2, half: h * 0.35, closed: h * 0.75) + } + private func handlePanGesture(_ sender: UIPanGestureRecognizer) { - print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #if DEBUG + print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #endif guard let contentViewTopConstraint = contentViewTopConstraint else { - print("DEBUG: contentViewTopConstraint is nil!") + #if DEBUG + print("DEBUG: contentViewTopConstraint is nil!") + #endif return } @@ -124,10 +130,7 @@ extension PointOfUseDetailsViewController { let newY = currentY + translatedPoint.y // Constrain movement between closed position and maximum expanded position - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 // Mostly closed (more map visible) - let kDefaultBottomHalfPosition = screenHeight * 0.35 // Default position to show content including button - let kDefaultOpenedMapPosition = screenHeight * 0.2 // Mostly open (less map visible, more content) + let pos = sheetPositions() // Allow dragging between open position (top) and closed position (bottom) - let maxY = kDefaultClosedMapPosition // Don't allow dragging below closed position - let minY = kDefaultOpenedMapPosition // Don't allow dragging above open position + let maxY = pos.closed // Don't allow dragging below closed position + let minY = pos.opened // Don't allow dragging above open positionApply similar changes to the rest of the method and
showMapAction().DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
632-644: Bug: allLocations() is called with pointOfUseId instead of merchantId (returns wrong counts).MerchantDAO expects merchantId. This breaks “Show all locations” counts and results.
Apply:
- print("DEBUG_FETCH_COUNT: pointOfUseId: '\(merchant.pointOfUseId)'") + #if DEBUG + print("DEBUG_FETCH_COUNT: merchantId: '\(merchant.merchant?.merchantId ?? "nil")'") + #endif @@ - // Use the same call as AllMerchantLocationsDataProvider - ExploreDash.shared.allLocations(for: merchant.pointOfUseId, in: finalBounds, userPoint: finalUserPoint) { [weak self] result in + // Use the same call as AllMerchantLocationsDataProvider + guard let merchantId = self.merchant.merchant?.merchantId, !merchantId.isEmpty else { + DSLogger.log("Missing merchantId; cannot fetch locations") + self.locationCount = 0 + self.updateShowAllLocationsButton() + return + } + ExploreDash.shared.allLocations(for: merchantId, in: finalBounds, userPoint: finalUserPoint) { [weak self] result inRun to confirm no other callsites still pass pointOfUseId:
#!/bin/bash rg -nP -C2 'allLocations\s*\(\s*for:\s*[^,]+' | rg -nP 'pointOfUseId'
138-166: Gate phone debug prints behind DEBUG (PII leak risk).Ship builds should not log phone numbers or system state.
@objc func callAction() { guard let phone = merchant.phone, !phone.isEmpty else { return } - print("DEBUG: Original phone: \(phone)") + #if DEBUG + print("DEBUG: Original phone: \(phone)") + #endif @@ - print("DEBUG: Extracted digits: \(digits)") + #if DEBUG + print("DEBUG: Extracted digits: \(digits)") + #endif @@ - print("DEBUG: URL string: \(urlString)") + #if DEBUG + print("DEBUG: URL string: \(urlString)") + #endif @@ - guard let url = URL(string: urlString) else { - print("DEBUG: Failed to create URL") + guard let url = URL(string: urlString) else { + #if DEBUG + print("DEBUG: Failed to create URL") + #endif return } @@ - guard UIApplication.shared.canOpenURL(url) else { - print("DEBUG: Cannot open URL - phone not available") + guard UIApplication.shared.canOpenURL(url) else { + #if DEBUG + print("DEBUG: Cannot open URL - phone not available") + #endif return } - print("DEBUG: Opening URL: \(url)") + #if DEBUG + print("DEBUG: Opening URL: \(url)") + #endif UIApplication.shared.open(url, options: [:], completionHandler: { success in - print("DEBUG: URL open result: \(success)") + #if DEBUG + print("DEBUG: URL open result: \(success)") + #endif }) }
1149-1164: Gate CTX field dumps and GameStop debug under DEBUG.These prints leak merchant identifiers and internals in production logs.
- if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") || self.merchant.name.lowercased().contains("buffalo") { + #if DEBUG + if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") || self.merchant.name.lowercased().contains("buffalo") { @@ - } + } + #endif @@ - if self.merchant.name.lowercased().contains("gamestop") { + #if DEBUG + if self.merchant.name.lowercased().contains("gamestop") { print("🎯 GAMESTOP DENOMINATIONS TYPE DEBUG:") print(" Original denominationsType: '\(denominationsType)'") print(" Lowercased denominationsType: '\(lowercasedType)'") } + #endif @@ - case "fixed": - if self.merchant.name.lowercased().contains("gamestop") { - print(" → Matched 'fixed' case - returning Fixed amounts") - } + case "fixed": return NSLocalizedString("Fixed amounts", comment: "DashSpend") - case "flexible", "min-max": - if self.merchant.name.lowercased().contains("gamestop") { - print(" → Matched 'flexible'/'min-max' case - returning Flexible amounts") - } + case "flexible", "min-max": return NSLocalizedString("Flexible amounts", comment: "DashSpend") - default: - if self.merchant.name.lowercased().contains("gamestop") { - print(" → Matched 'default' case - returning Fixed amounts") - } + default: return NSLocalizedString("Fixed amounts", comment: "DashSpend")Also applies to: 1170-1192
943-965: Show the U.S.-only notice conditionally.Do not show this for non‑US merchants/devices.
private func createCountryNotice() -> UIView { @@ - noticeLabel.text = NSLocalizedString("Note: This card works only in the United States.", comment: "DashSpend") + noticeLabel.text = NSLocalizedString("Note: This card works only in the United States.", comment: "DashSpend") @@ container.addSubview(noticeLabel) + // Hide unless US merchant or US device region + let isUSMerchant = (merchant.territory == "US") + let isUSDevice = (Locale.current.regionCode == "US") + noticeLabel.isHidden = !(isUSMerchant || isUSDevice)
🧹 Nitpick comments (12)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (2)
89-89: Default 1‑mile centerRadius: align via a single source of truthIf the 1‑mile zoom is a UX requirement, great—make it a named constant to avoid drift across controllers/flows.
- var centerRadius: Double = 1 // 1-mile radius as requested + var centerRadius: Double = MapDefaults.centerRadiusMiles // 1-mile radiusOutside this hunk:
private enum MapDefaults { static let centerRadiusMiles: Double = 1.0 }
109-111: mapBounds currently represents visibleMapRect — optional rename to visibleBoundsConfirmed: ExploreMapView.mapBounds returns .init(rect: mapView.visibleMapRect) and is passed to the delegate; ExplorePointOfUseListViewController sets model.currentMapBounds = bounds in exploreMapView(_:didChangeVisibleBounds:) (ExplorePointOfUseListViewController.swift:593–596). mapBounds(with:) is used for filter-based fixed-radius fetches (ExplorePointOfUseListViewController.swift:776; kDefaultRadius = 32000).
Optional refactor: rename the property mapBounds -> visibleBounds and keep mapBounds(with:) for radius-based data-fetch bounds; update usages in ExploreMapView.swift (property + _mapViewDidChangeVisibleRegion ≈ lines 110–116 and 263–267) and ExplorePointOfUseListViewController.swift (didChangeVisibleBounds and apply(filters:)).
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (3)
25-25: Comment contradicts behavior; 320 is a fixed value, not “Max 50%”.Either compute dynamically or clarify the comment.
Apply:
-internal let kDefaultOpenedMapPosition: CGFloat = 320.0 // Max 50% of screen height +internal let kDefaultOpenedMapPosition: CGFloat = 320.0 // Fixed opened position (pt)
567-570: SwiftLint: vertical parameter alignment.Align multiline parameters to silence warnings.
Apply:
- UIView.animate(withDuration: animationDuration, delay: 0, - usingSpringWithDamping: 0.8, initialSpringVelocity: 0, - options: .curveEaseOut) { + UIView.animate(withDuration: animationDuration, + delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0, + options: .curveEaseOut) {
775-777: Keep current map bounds when applying filters (optional).Resetting to a radius-based circle can feel jumpy after the user pans/zooms.
Apply:
- model.currentMapBounds = mapView.mapBounds(with: filters?.currentRadius ?? kDefaultRadius) + let preferredBounds = model.currentMapBounds ?? mapView.mapBounds(with: filters?.currentRadius ?? kDefaultRadius) + model.currentMapBounds = preferredBoundsDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (7)
856-858: Avoid subtitle flicker: use the same source (no distance) at creation and refresh.Currently created with distance then replaced without distance.
- subLabel = UILabel() - subLabel.text = subtitleWithDistance() + subLabel = UILabel() + subLabel.text = subtitleWithoutDistance()Also applies to: 1072-1080
681-711: Gate heavy DEBUG_FETCH_ logs in updateShowAllLocationsButton().*Reduce noise and runtime overhead in release.
- print("DEBUG_FETCH_RESULTS: updateShowAllLocationsButton called with locationCount: \(locationCount)") + #if DEBUG + print("DEBUG_FETCH_RESULTS: updateShowAllLocationsButton called with locationCount: \(locationCount)") + #endif @@ - print("DEBUG_FETCH_RESULTS: Container has \(containerView.arrangedSubviews.count) arranged subviews") + #if DEBUG + print("DEBUG_FETCH_RESULTS: Container has \(containerView.arrangedSubviews.count) arranged subviews") + #endif @@ - print("DEBUG_FETCH_RESULTS: Found show all block with \(showAllBlock.subviews.count) subviews") + #if DEBUG + print("DEBUG_FETCH_RESULTS: Found show all block with \(showAllBlock.subviews.count) subviews") + #endif @@ - print("DEBUG_FETCH_RESULTS: Checking subview \(index): \(type(of: subview))") + #if DEBUG + print("DEBUG_FETCH_RESULTS: Checking subview \(index): \(type(of: subview))") + #endif @@ - print("DEBUG_FETCH_RESULTS: Updating button title to: \(newTitle)") + #if DEBUG + print("DEBUG_FETCH_RESULTS: Updating button title to: \(newTitle)") + #endif @@ - print("DEBUG_FETCH_RESULTS: No show all block found in container") + #if DEBUG + print("DEBUG_FETCH_RESULTS: No show all block found in container") + #endif
570-679: Scope and gate DEBUG_FETCH_ logs and fix log wording.*The block is very chatty; gate prints and avoid naming specific brands in generic logs.
- print("DEBUG_FETCH_COUNT: Starting location fetch for merchant: '\(merchant.name)'") - print("DEBUG_FETCH_COUNT: pointOfUseId: '\(merchant.pointOfUseId)'") + #if DEBUG + print("DEBUG_FETCH_COUNT: Starting location fetch for merchant: '\(merchant.name)'") + print("DEBUG_FETCH_COUNT: pointOfUseId: '\(merchant.pointOfUseId)'") @@ - print("DEBUG_FETCH_COUNT: Using radius: \(filterRadius) meters (\(filterRadius / kMetersToMilesConversion) miles)") + print("DEBUG_FETCH_COUNT: Using radius: \(filterRadius) meters (\(filterRadius / kMetersToMilesConversion) miles)") @@ - print("DEBUG_FETCH_COUNT: Location manager status:") + print("DEBUG_FETCH_COUNT: Location manager status:") @@ - print("DEBUG_FETCH_COUNT: Using current map bounds from user interaction") + print("DEBUG_FETCH_COUNT: Using current map bounds from user interaction") @@ - print("DEBUG_FETCH_COUNT: Using filter radius: \(filterRadius) meters") + print("DEBUG_FETCH_COUNT: Using filter radius: \(filterRadius) meters") @@ - print("DEBUG_FETCH_COUNT: No bounds or user location available") + print("DEBUG_FETCH_COUNT: No bounds or user location available") @@ - print("DEBUG_FETCH_COUNT: Final API call parameters:") + print("DEBUG_FETCH_COUNT: Final API call parameters:") // … - print("DEBUG_FETCH_RESULTS: Results for '\(strongSelf.merchant.name)'") - print("DEBUG_FETCH_RESULTS: Total GameStop locations: \(paginationResult.items.count)") - print("DEBUG_FETCH_RESULTS: Active GameStop locations: \(activeLocations.count)") + print("DEBUG_FETCH_RESULTS: Results for '\(strongSelf.merchant.name)'") + print("DEBUG_FETCH_RESULTS: Total locations: \(paginationResult.items.count)") + print("DEBUG_FETCH_RESULTS: Active locations: \(activeLocations.count)") @@ - print("DEBUG_FETCH_RESULTS: Inactive GameStop locations: \(inactiveLocations.count)") + print("DEBUG_FETCH_RESULTS: Inactive locations: \(inactiveLocations.count)") @@ - print("DEBUG_FETCH_RESULTS: Setting locationCount to \(newCount)") + print("DEBUG_FETCH_RESULTS: Setting locationCount to \(newCount)") @@ - print("DEBUG_FETCH_RESULTS: locationCount after assignment: \(self?.locationCount ?? -1)") + print("DEBUG_FETCH_RESULTS: locationCount after assignment: \(self?.locationCount ?? -1)") @@ - print("DEBUG_FETCH_RESULTS: locationCount after updateShowAllLocationsButton: \(self?.locationCount ?? -1)") + print("DEBUG_FETCH_RESULTS: locationCount after updateShowAllLocationsButton: \(self?.locationCount ?? -1)") @@ - print("DEBUG_FETCH_RESULTS: API call failed with error: \(error)") + print("DEBUG_FETCH_RESULTS: API call failed with error: \(error)") + #endif
1239-1250: Gate grabber gesture debug prints behind DEBUG.Avoid noisy logs in release.
- guard let grabberContainer = grabberContainer else { - print("DEBUG: grabberContainer is nil!") + guard let grabberContainer = grabberContainer else { + #if DEBUG + print("DEBUG: grabberContainer is nil!") + #endif return } - print("DEBUG: Setting up pan gesture on grabber container") + #if DEBUG + print("DEBUG: Setting up pan gesture on grabber container") + #endif @@ - print("DEBUG: Pan gesture added to grabber container") + #if DEBUG + print("DEBUG: Pan gesture added to grabber container") + #endif
35-41: Fix access control and closure signatures.Members are marked public in an internal type; also prefer () -> Void. Make the class public or lower members to internal.
- public var payWithDashHandler: (()->())? - public var sellDashHandler: (()->())? - public var dashSpendAuthHandler: (()->())? - public var buyGiftCardHandler: (()->())? - public var showAllLocationsActionBlock: (() -> ())? - public var infoButtonActionBlock: (() -> ())? // For parent view controller to handle info button tap + var payWithDashHandler: (() -> Void)? + var sellDashHandler: (() -> Void)? + var dashSpendAuthHandler: (() -> Void)? + var buyGiftCardHandler: (() -> Void)? + var showAllLocationsActionBlock: (() -> Void)? + var infoButtonActionBlock: (() -> Void)? // For parent view controller to handle info button tapIf these must be public, declare the type
public final class PointOfUseDetailsViewand keep members public.
909-910: Redundant conditional: same color on both branches.Simplify.
- subtitleLabel.textColor = self.merchant.active ? .dw_tertiaryText() : .dw_tertiaryText() + subtitleLabel.textColor = .dw_tertiaryText()
1086-1105: Remove unused helper getAddressWithDistance(_:). Only the attributed variant is referenced (DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift:457, 1081); the String-returning helper is defined at the same file (lines 1086–1105) and has no usages — remove it.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(10 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(7 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AllMerchantLocationsViewController.swift(3 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift(5 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is confirmed unused after code analysis. Radius functionality is properly handled through model.currentRadius and filters?.currentRadius, making the controller property redundant and safe to remove.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is unused and should be removed. Radius functionality is handled through the model.currentRadius and filters system instead of a controller-level property.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
🧬 Code graph analysis (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AllMerchantLocationsViewController.swift (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (6)
tableView(629-685)tableView(687-703)tableView(709-726)tableView(728-739)tableView(741-763)tableView(765-769)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (3)
mapView(240-245)mapView(247-257)mapView(273-279)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/PointOfUseListModel.swift (2)
items(57-60)fetch(201-204)DashWallet/Sources/Models/Explore Dash/Infrastructure/DAO Impl/MerchantDAO.swift (3)
items(38-39)items(42-175)allLocations(200-247)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/MerchantsDataProvider.swift (6)
items(24-46)items(70-93)items(112-127)fetch(58-64)fetch(102-106)fetch(136-141)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (1)
mapBounds(113-115)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (5)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
callAction(91-106)DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/Models/Explore Dash/Infrastructure/DAO Impl/MerchantDAO.swift (3)
allLocations(200-247)items(38-39)items(42-175)DashWallet/Sources/Models/Explore Dash/ExploreDash.swift (1)
allLocations(189-192)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (1)
items(34-79)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (4)
mapView(240-245)mapView(247-257)mapView(273-279)configureHierarchy(204-234)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
configureHierarchy(211-234)callAction(138-166)websiteAction(184-188)setupGrabberPanGesture(1239-1250)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AllMerchantLocationsViewController.swift (1)
configureHierarchy(77-84)DashWallet/Sources/Categories/UIHostingController+DashWallet.swift (1)
setDetent(21-31)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift
[Warning] 568-568: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 569-569: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 138-138: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 33-33: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 35-35: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 36-36: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 37-37: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 38-38: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 113-113: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 35-35: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 36-36: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 37-37: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 38-38: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 128-128: Unimplemented functions should be marked as unavailable
(unavailable_function)
[Warning] 32-32: Prefer -> Void over -> ()
(void_return)
[Warning] 35-35: Prefer -> Void over -> ()
(void_return)
[Warning] 36-36: Prefer -> Void over -> ()
(void_return)
[Warning] 37-37: Prefer -> Void over -> ()
(void_return)
[Warning] 38-38: Prefer -> Void over -> ()
(void_return)
[Warning] 39-39: Prefer -> Void over -> ()
(void_return)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 213-213: Prefer object literals over image and color inits
(object_literal)
[Warning] 284-284: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 390-390: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 358-358: Prefer object literals over image and color inits
(object_literal)
[Warning] 452-452: Prefer object literals over image and color inits
(object_literal)
[Warning] 518-518: Prefer object literals over image and color inits
(object_literal)
[Warning] 1111-1111: Prefer object literals over image and color inits
(object_literal)
[Warning] 1251-1251: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
[Warning] 1050-1050: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1092-1092: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1126-1126: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1283-1283: Unimplemented functions should be marked as unavailable
(unavailable_function)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 38-38: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 59-59: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 281-281: Force unwrapping should be avoided
(force_unwrapping)
🔇 Additional comments (7)
DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m (1)
40-46: Domain updates to wallet.uphold.com look correct.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AllMerchantLocationsViewController.swift (3)
28-33: Initializer threading filter/map state: LGTM.Passing currentFilters/currentMapBounds through the VC constructor is clean.
53-59: Hiding segments via zero rows: LGTM.Keeps layout stable without rendering an unused section.
70-75: Data provider receives snapshot state; ensure provider honors dynamic updates.Your provider currently prioritizes its captured properties over parameters (see provider comment); fix there to use method params so map pans/filters take effect.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (2)
142-149: Passing filters/map bounds to details VC: LGTM.
593-598: Use of visible map bounds: LGTM.Switching to real bounds makes list refresh track the map.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (1)
89-99: Filtering to active locations only: LGTM.Matches the details count and avoids showing inactive entries.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
...t/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift
Show resolved
Hide resolved
...t/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift
Show resolved
Hide resolved
...t/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift
Show resolved
Hide resolved
- Fix force unwrapping crashes in coordinate handling and pagination - Unify default search radius values across codebase - Set SVG gift card icons as template images for proper rendering - Fix generic parameter inference errors in ExploreMapView compactMap - Remove trailing whitespaces and unused radius property 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
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.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExploreSyncBannerView.swift (1)
48-52: Add a bottom constraint to avoid ambiguous layout and clipping.The label is only pinned on top/leading/trailing. Without a bottom anchor, the banner’s height can be ambiguous and longer localizations may clip. PointOfUseDetailsView pins both top and bottom; mirror that here.
NSLayoutConstraint.activate([ label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16), label.topAnchor.constraint(equalTo: topAnchor, constant: 8), + label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8), ])DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/AtmItemCell.swift (1)
48-50: Sell tag never shows for.sellATMs (logic bug)Current predicates hide both tags for
.selltype. Show Sell for.selland.buySell; hide only for.buy.- sellTag.isHidden = atm.type != .buySell + sellTag.isHidden = atm.type == .buyDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (1)
140-165: Gate debug prints behind DEBUG flag.The debug print statements are currently unconditional and will appear in production logs.
Apply this diff to prevent debug output in production:
- print("DEBUG: Original phone: \(phone)") + #if DEBUG + print("DEBUG: Original phone: \(phone)") + #endif // Extract only digits for phone call let digits = phone.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() guard !digits.isEmpty else { return } - print("DEBUG: Extracted digits: \(digits)") + #if DEBUG + print("DEBUG: Extracted digits: \(digits)") + #endif // Use telprompt: to directly open phone app (tel: shows options) let urlString = "telprompt:\(digits)" - print("DEBUG: URL string: \(urlString)") + #if DEBUG + print("DEBUG: URL string: \(urlString)") + #endif guard let url = URL(string: urlString) else { - print("DEBUG: Failed to create URL") + #if DEBUG + print("DEBUG: Failed to create URL") + #endif return } // Check if device can open the URL guard UIApplication.shared.canOpenURL(url) else { - print("DEBUG: Cannot open URL - phone not available") + #if DEBUG + print("DEBUG: Cannot open URL - phone not available") + #endif return } - print("DEBUG: Opening URL: \(url)") + #if DEBUG + print("DEBUG: Opening URL: \(url)") + #endif UIApplication.shared.open(url, options: [:], completionHandler: { success in - print("DEBUG: URL open result: \(success)") + #if DEBUG + print("DEBUG: URL open result: \(success)") + #endif })DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (1)
498-507: Use the filtered groups when location is unavailable (bug: local var unused).You build
filterGroupsbut passcurrentSegment.filterGroupsto the view, negating the location-based filtering.Apply this diff:
- let filterGroups = currentSegment.filterGroups.filter { filter in + let filterGroups = currentSegment.filterGroups.filter { filter in DWLocationManager.shared.currentLocation != nil || (filter != .sortBy && filter != .radius) } @@ - let filtersView = MerchantFiltersView( - currentFilters: model.filters, - filterGroups: currentSegment.filterGroups, + let filtersView = MerchantFiltersView( + currentFilters: model.filters, + filterGroups: filterGroups, territoriesDataSource: currentSegment.territoriesDataSource, sortOptions: currentSegment.sortOptions ) { [weak self] filters in self?.apply(filters: filters) }Also applies to: 511-516
♻️ Duplicate comments (10)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (3)
1149-1163: Gate extensive merchant debug prints behind DEBUG flag.These debug prints include sensitive merchant information and should not appear in production logs.
+ #if DEBUG if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") || self.merchant.name.lowercased().contains("buffalo") { print("🎯 MERCHANT DEBUG: \(self.merchant.name)") // ... rest of debug prints } + #endif
1170-1192: Remove GameStop-specific debug logging from production code.+ #if DEBUG if self.merchant.name.lowercased().contains("gamestop") { print("🎯 GAMESTOP DENOMINATIONS TYPE DEBUG:") // ... debug prints } + #endifAlso remove the debug prints from inside the switch cases.
641-643: Critical: Use merchantId instead of pointOfUseId for location fetch.The
allLocationsAPI expects a merchantId but is being passed a pointOfUseId, which causes incorrect location counts.- ExploreDash.shared.allLocations(for: merchant.pointOfUseId, in: finalBounds, userPoint: finalUserPoint) { [weak self] result in + guard let merchantId = self.merchant.merchant?.merchantId, !merchantId.isEmpty else { + DSLogger.log("Missing merchantId; cannot fetch locations") + self.locationCount = 0 + self.updateShowAllLocationsButton() + return + } + ExploreDash.shared.allLocations(for: merchantId, in: finalBounds, userPoint: finalUserPoint) { [weak self] result inDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
427-432: Gate CTX merchant debug logging.+ #if DEBUG if pointOfUse.name.lowercased().contains("buffalo") || pointOfUse.name.lowercased().contains("gamestop") { print("🎯 CTX MERCHANT INFO DEBUG: \(pointOfUse.name)") // ... debug prints } + #endif
38-38: Remove unused IUO property to prevent crashes.The
showMapButtonproperty is declared but never properly initialized, which can lead to runtime crashes if accessed.private var contentViewTopConstraint: NSLayoutConstraint? - private var showMapButton: UIButton!
40-40: Fix access level mismatch.The initializer is public while the class is internal, causing a compilation error.
- public init(pointOfUse: ExplorePointOfUse, isShowAllHidden: Bool = true, currentFilters: PointOfUseListFilters? = nil, currentMapBounds: ExploreMapBounds? = nil) { + init(pointOfUse: ExplorePointOfUse, isShowAllHidden: Bool = true, currentFilters: PointOfUseListFilters? = nil, currentMapBounds: ExploreMapBounds? = nil) {
283-283: Avoid force unwrapping to prevent crashes.Force unwrapping
contentViewTopConstraintcan cause crashes if it's nil.+ let topConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + contentViewTopConstraint = topConstraint constraint = [ - contentViewTopConstraint!, + topConstraint,DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (3)
24-31: Consider removing captured currentFilters/currentMapBounds to avoid stale results.These captured properties override the live inputs from the model, causing the view not to update when the user pans the map or changes filters.
class AllMerchantLocationsDataProvider: PointOfUseDataProvider { private let pointOfUse: ExplorePointOfUse - private let currentFilters: PointOfUseListFilters? - private let currentMapBounds: ExploreMapBounds? - init(pointOfUse: ExplorePointOfUse, currentFilters: PointOfUseListFilters? = nil, currentMapBounds: ExploreMapBounds? = nil) { + init(pointOfUse: ExplorePointOfUse) { self.pointOfUse = pointOfUse - self.currentFilters = currentFilters - self.currentMapBounds = currentMapBounds super.init() }
40-65: Honor method-provided bounds/filters instead of overriding with captured values.The logic should prefer explicit bounds from the method parameters.
Use the incoming
boundsparameter if non-nil, otherwise fall back to deriving bounds from user location and filters. Don't override with captured values.
67-70: Cache comparison should include userPoint.Results ordering depends on userPoint, so the cache can be stale after the user moves.
- if lastQuery == query && !items.isEmpty && lastBounds == finalBounds { + if lastQuery == query && !items.isEmpty && lastBounds == finalBounds && + lastUserPoint?.latitude == finalUserPoint?.latitude && + lastUserPoint?.longitude == finalUserPoint?.longitude { completion(.success(items)) return }
🧹 Nitpick comments (14)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExploreSyncBannerView.swift (3)
23-31: Allow wrapping and support Dynamic Type.Hard-coding to one line risks truncation in some locales. Enable wrapping and ensure scaling.
label.translatesAutoresizingMaskIntoConstraints = false label.text = NSLocalizedString("Sync in progress… Results may not be complete.", comment: "Explore Dash") label.textColor = .white label.font = .dw_font(forTextStyle: .footnote) label.textAlignment = .center - label.numberOfLines = 1 + label.numberOfLines = 0 + label.lineBreakMode = .byWordWrapping + label.adjustsFontForContentSizeCategory = true return label
21-21: Fix SwiftLint warning: remove empty line after opening brace.Matches vertical_whitespace_opening_braces rule.
-class ExploreSyncBannerView: UIView { - +class ExploreSyncBannerView: UIView {
25-25: Clarify the translator comment for better localization context.Give translators precise UI context.
- label.text = NSLocalizedString("Sync in progress… Results may not be complete.", comment: "Explore Dash") + label.text = NSLocalizedString("Sync in progress… Results may not be complete.", comment: "Explore screen sync banner")DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/AtmItemCell.swift (2)
35-46: Good nil-safety and auth gating; refine text join and distance roundingNice switch to optional binding and auth gating. Two small fixes:
- Avoid a dangling " • " when
sourceis nil/empty.- Let the formatter handle rounding;
floor(distance)can under-report.Apply:
- let distanceText: String = ExploreDash.distanceFormatter - .string(from: Measurement(value: floor(distance), unit: UnitLength.meters)) - subLabel.text = "\(distanceText) • \(pointOfUse.source ?? "")" + let distanceText = ExploreDash.distanceFormatter + .string(from: Measurement(value: distance, unit: .meters)) + if let source = pointOfUse.source?.trimmingCharacters(in: .whitespacesAndNewlines), + !source.isEmpty { + subLabel.text = "\(distanceText) • \(source)" + } else { + subLabel.text = distanceText + }
118-123: Remove redundant centerX constraint to prevent over-constraint riskLeading+trailing already center the label horizontally. Drop
centerXAnchorfor cleaner constraints.NSLayoutConstraint.activate([ label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 6), label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -6), - label.centerXAnchor.constraint(equalTo: centerXAnchor), label.centerYAnchor.constraint(equalTo: centerYAnchor), ])DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/MerchantsDataProvider.swift (1)
96-99: Introduce a typed Error for missing bounds (preferred) — or at minimum fix the NSError domainGuard failure should return a typed error instead of an ad‑hoc NSError (or at least correct the domain). The single occurrence is at DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/MerchantsDataProvider.swift:97.
Option A (preferred — add a typed error):
- guard let lastBounds = lastBounds else { - completion(.failure(NSError(domain: "MerchantsDataProvider", code: -1, userInfo: [NSLocalizedDescriptionKey: "No bounds available for pagination"]))) + guard let lastBounds = lastBounds else { + completion(.failure(MerchantsDataProviderError.missingBounds)) return }Add once in this file (top-level, near the providers):
enum MerchantsDataProviderError: LocalizedError { case missingBounds var errorDescription: String? { switch self { case .missingBounds: return NSLocalizedString("No bounds available for pagination", comment: "Pagination error") } } }Option B (minimal — fix domain and message localization):
- completion(.failure(NSError(domain: "MerchantsDataProvider", code: -1, userInfo: [NSLocalizedDescriptionKey: "No bounds available for pagination"]))) + completion(.failure(NSError(domain: "NearbyMerchantsDataProvider", + code: 1, + userInfo: [NSLocalizedDescriptionKey: + NSLocalizedString("No bounds available for pagination", comment: "Pagination error")])))DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (2)
26-26: Avoid implicitly unwrapped optional to prevent potential crashes.The
distanceLabelis declared as an IUO but is properly initialized inconfigureHierarchy(). Consider making it non-optional with lazy initialization or a regular optional.- private var distanceLabel: UILabel! + private lazy var distanceLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = .systemFont(ofSize: 12, weight: .regular) + label.textColor = .dw_tertiaryText() + label.isHidden = true + label.textAlignment = .right + return label + }()Then update
configureHierarchy()to just add it as a subview without initializing:- distanceLabel = UILabel() - distanceLabel.translatesAutoresizingMaskIntoConstraints = false - distanceLabel.font = .systemFont(ofSize: 12, weight: .regular) - distanceLabel.textColor = .dw_tertiaryText() - distanceLabel.isHidden = true - distanceLabel.textAlignment = .right mainStackView.addArrangedSubview(distanceLabel)
51-51: Remove trailing semicolon.- let paymentIconName = isGiftCard ? "image.explore.dash.wts.payment.gift-card" : "image.explore.dash.wts.payment.dash"; + let paymentIconName = isGiftCard ? "image.explore.dash.wts.payment.gift-card" : "image.explore.dash.wts.payment.dash"DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
110-178: Gate debug prints in pan gesture handler.The pan gesture handler contains debug prints that should not appear in production.
- print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #if DEBUG + print("DEBUG: Pan gesture triggered - state: \(sender.state.rawValue)") + #endif guard let contentViewTopConstraint = contentViewTopConstraint else { - print("DEBUG: contentViewTopConstraint is nil!") + #if DEBUG + print("DEBUG: contentViewTopConstraint is nil!") + #endif return }DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (5)
25-25: Comment vs behavior mismatch (“Max 50% of screen height”).Constant is 320pt; logic above clamps at a dynamic 50%. Either compute dynamically everywhere (preferred) or correct the comment.
Do you want me to refactor all call sites to use a computed
openYso the comment becomes true?
394-401: Sync banner overlay is fine; consider safe-area and hit-testing.Looks good. Ensure the banner doesn’t block interactions unintentionally when height=0 (it’s hidden post-animation).
Optionally set
isUserInteractionEnabled = falsewhen hidden, andaccessibilityTraits = .updatesFrequently.Also applies to: 462-474
257-257: SwiftLint: put attributes on their own line.Fix rule “attributes”:
@objcshould be on its own line for functions.- @objc private func databaseHasBeenUpdated() { + @objc + private func databaseHasBeenUpdated() {If desired, I can sweep the file for similar cases.
775-779: Map-bounds init on filter apply—add permission guard.If location is denied, deriving bounds from map radius may be misleading versus showing global results. Consider clearing bounds when permission is denied.
Apply this diff:
- model.currentMapBounds = mapView.mapBounds(with: filters?.currentRadius ?? kDefaultRadius) + if DWLocationManager.shared.isPermissionDenied || DWLocationManager.shared.needsAuthorization { + model.currentMapBounds = nil + } else { + model.currentMapBounds = mapView.mapBounds(with: filters?.currentRadius ?? kDefaultRadius) + }
566-569: Align multiline call parameters (SwiftLint).Parameters in the animated block should be vertically aligned.
- UIView.animate(withDuration: animationDuration, delay: 0, - usingSpringWithDamping: 0.8, initialSpringVelocity: 0, - options: .curveEaseOut) { + UIView.animate(withDuration: animationDuration, + delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0, + options: .curveEaseOut) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(12 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(7 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Info/MerchantTypesDialog.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/AtmItemCell.swift(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift(3 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift(12 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExploreSyncBannerView.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/MerchantFiltersView.swift(16 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/Model/MerchantFiltersViewModel.swift(13 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/Model/PointOfUseListFiltersModel.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/TerritoryPickerView.swift(5 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift(5 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift(2 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/MerchantsDataProvider.swift(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift(4 hunks)
✅ Files skipped from review due to trivial changes (4)
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Info/MerchantTypesDialog.swift
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/TerritoryPickerView.swift
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/MerchantFiltersView.swift
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/Model/PointOfUseListFiltersModel.swift
🚧 Files skipped from review as they are similar to previous changes (3)
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/AtmDetailsView.swift
- DashWallet/Sources/Models/Uphold/DWUpholdMainnetConstants.m
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is confirmed unused after code analysis. Radius functionality is properly handled through model.currentRadius and filters?.currentRadius, making the controller property redundant and safe to remove.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/Model/MerchantFiltersViewModel.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is unused and should be removed. Radius functionality is handled through the model.currentRadius and filters system instead of a controller-level property.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/Model/MerchantFiltersViewModel.swiftDashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
🧬 Code graph analysis (9)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/MerchantsDataProvider.swift (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (1)
fetch(87-100)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AtmDataProvider.swift (1)
fetch(65-70)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
setupInfoButton(73-77)infoButtonAction(79-84)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/MerchantsDataProvider.swift (6)
items(24-46)items(70-93)items(116-131)fetch(58-64)fetch(106-110)fetch(140-145)DashWallet/Sources/Models/Explore Dash/Infrastructure/DAO Impl/MerchantDAO.swift (3)
items(38-39)items(42-175)allLocations(200-247)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AtmDataProvider.swift (2)
items(26-56)fetch(65-70)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (1)
mapBounds(113-115)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (5)
DashWallet/Sources/Models/Explore Dash/Infrastructure/Database Connection/ExploreDatabaseConnection.swift (1)
hasOldMerchantIdSchema(43-60)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (1)
items(34-79)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/MerchantsDataProvider.swift (3)
items(24-46)items(70-93)items(116-131)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/PointOfUseListModel.swift (1)
items(57-60)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AtmDataProvider.swift (1)
items(26-56)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExploreSyncBannerView.swift (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
setupView(1286-1299)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (6)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (1)
callAction(91-106)DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/UI/Views/BaseController/BaseViewController+NetworkReachability.swift (2)
stopNetworkMonitoring(52-54)startNetworkMonitoring(36-50)DashWallet/Sources/Models/Explore Dash/Infrastructure/DAO Impl/MerchantDAO.swift (3)
allLocations(200-247)items(38-39)items(42-175)DashWallet/Sources/Models/Explore Dash/ExploreDash.swift (1)
allLocations(189-192)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (1)
items(34-79)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (2)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/AtmItemCell.swift (1)
update(28-50)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift (1)
update(41-49)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/Model/MerchantFiltersViewModel.swift (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/PointOfUseListModel.swift (1)
resetFilters(186-189)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (5)
mapView(250-255)mapView(257-267)mapView(283-289)configureHierarchy(214-244)show(139-180)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (2)
configureHierarchy(336-492)show(142-148)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (2)
configureHierarchy(232-238)show(190-197)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (4)
configureHierarchy(211-234)callAction(138-166)websiteAction(184-188)setupGrabberPanGesture(1238-1249)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift
[Warning] 257-257: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 567-567: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 568-568: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExploreSyncBannerView.swift
[Warning] 21-21: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 138-138: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 33-33: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 35-35: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 36-36: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 37-37: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 38-38: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 113-113: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 35-35: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 36-36: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 37-37: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 38-38: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 128-128: Unimplemented functions should be marked as unavailable
(unavailable_function)
[Warning] 32-32: Prefer -> Void over -> ()
(void_return)
[Warning] 35-35: Prefer -> Void over -> ()
(void_return)
[Warning] 36-36: Prefer -> Void over -> ()
(void_return)
[Warning] 37-37: Prefer -> Void over -> ()
(void_return)
[Warning] 38-38: Prefer -> Void over -> ()
(void_return)
[Warning] 39-39: Prefer -> Void over -> ()
(void_return)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 213-213: Prefer object literals over image and color inits
(object_literal)
[Warning] 284-284: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 390-390: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 358-358: Prefer object literals over image and color inits
(object_literal)
[Warning] 452-452: Prefer object literals over image and color inits
(object_literal)
[Warning] 518-518: Prefer object literals over image and color inits
(object_literal)
[Warning] 1110-1110: Prefer object literals over image and color inits
(object_literal)
[Warning] 1250-1250: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
[Warning] 1049-1049: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1091-1091: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1125-1125: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1282-1282: Unimplemented functions should be marked as unavailable
(unavailable_function)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift
[Warning] 26-26: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Lines should not have trailing semicolons
(trailing_semicolon)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/Model/MerchantFiltersViewModel.swift
[Warning] 22-22: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 262-262: Function should have complexity 10 or less; currently complexity is 11
(cyclomatic_complexity)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 38-38: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 59-59: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 283-283: Force unwrapping should be avoided
(force_unwrapping)
🔇 Additional comments (7)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/MerchantsDataProvider.swift (1)
96-104: Make nextPage() return empty .success([]) when bounds/authorization are missing (mirror items())items(...) currently returns .success([]) when bounds/authorization are unavailable; nextPage(...) returns an error and can surface an unnecessary error UI. Short‑circuit with an empty success and reset pagination.
- guard let lastBounds = lastBounds else { - completion(.failure(NSError(domain: "MerchantsDataProvider", code: -1, userInfo: [NSLocalizedDescriptionKey: "No bounds available for pagination"]))) - return - } - fetch(by: lastQuery, in: lastBounds, userPoint: lastUserPoint, with: lastFilters, + guard let lastBounds = lastBounds, + let lastUserPoint = lastUserPoint, + DWLocationManager.shared.isAuthorized else { + // Mirror `items(...)` fallback: clear state and return empty list. + items = [] + currentPage = nil + completion(.success(items)) + return + } + fetch(by: lastQuery, in: lastBounds, userPoint: lastUserPoint, with: lastFilters, offset: nextOffset) { [weak self] result in self?.handle(result: result, appending: true, completion: completion) }
- Search found other pagination overrides; make this behavior consistent:
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift:81
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AtmDataProvider.swift:58
- DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/MerchantsDataProvider.swift:48, 95, 133
- Confirm this change does not conflict with the existing permission‑denied branch in MerchantsDataProvider.swift that currently calls fetch(by:..., in: nil, ...) (adjust as needed).
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Filters/Model/MerchantFiltersViewModel.swift (1)
55-55: Good addition for location-aware features.The
isLocationServiceEnabledproperty properly tracks location authorization status for the filter view model.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/MerchantListViewController.swift (2)
110-111: Good typo fix!Fixed "redius" → "radius" in the localized string.
193-193: Properly passes filter context to details view.The addition of
currentFiltersandcurrentMapBoundsparameters ensures the detail view has proper context for location-based features.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (3)
220-233: DB sync gating logic LGTM.Good fallback to show banner on errors or old schema.
Confirm
kExploreDashDatabaseNamepath resolves under Documents across app targets (App/Extension).
592-597: Switch to visible-bounds-driven refresh looks correct.Updating
model.currentMapBoundson map changes is aligned with the data providers’ new behavior.Watch for excessive refresh calls while panning; if needed, debounce in ExploreMapViewDelegate.
143-147: Approve — initializer usages updated and consistent
Both call sites pass currentFilters and currentMapBounds (MerchantListViewController.swift:193; ExplorePointOfUseListViewController.swift:143). Nice wiring — keeps details/all-locations contexts consistent with list/map.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
...llet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift
Show resolved
Hide resolved
...llet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift
Show resolved
Hide resolved
...t/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift
Show resolved
Hide resolved
- Add CLAUDE.md with project overview, build commands, and safety guidelines - Add ios-swift-safety-patterns.md with crash prevention patterns - Remove CLAUDE.md from gitignore to enable team knowledge sharing - Document force unwrapping fixes, map filtering architecture, and common build failures - Include code review checklist and safety patterns from recent debugging session 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
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.
Actionable comments posted: 0
🧹 Nitpick comments (6)
.claude/agents/CLAUDE.md (4)
172-176: Add a fenced-code language to satisfy markdownlint (MD040).The triple backticks around the sibling directories block lack a language hint.
Apply:
-``` +```text ../DashSync/ # Core Dash protocol library ../dapi-grpc/ # gRPC API definitions ../dashwallet-ios/ # This repository--- `30-31`: **Fix unintended Markdown heading; use a bullet or sentence.** This line renders as an H1 due to the leading "#". ```diff -# Or in Xcode, run tests on iPhone 8 simulator (as configured in Fastfile) +- Or in Xcode, run tests on the configured simulator (e.g., iPhone 16 Pro) as defined in Fastfile.
186-191: Expand “Security” with explicit secrets management guidance.Given the PR comment about exposed Uphold secrets, document hard guardrails here.
### Security - Jailbreak detection - Hardware encryption usage - Private key protection - Secure enclave integration + - Secrets management + - Never commit API keys, tokens, or client secrets. Store in CI/CD secrets and local `.xcconfig` (untracked). + - Use environment-specific `.xcconfig` with `$(UPHOLD_API_KEY)`-style substitutions. + - Enable pre-commit/CI scanners (e.g., `git-secrets` or `trufflehog`) and block on findings. + - Rotate any credentials that ever appeared in Git history; treat them as compromised.I can add a short “Secrets Setup” snippet for Fastlane/Xcode if you’d like.
230-230: Add newline at EOF.Tiny formatting nit; helps various tools.
.claude/agents/ios-swift-safety-patterns.md (2)
121-125: Reconsider treating 0,0 as invalid coordinates.Excluding (0,0) prevents showing legitimate points near the Gulf of Guinea. If (0,0) is only a sentinel in your data, gate on upstream source flags instead of blanket-filtering.
Possible alternative:
var isValid: Bool { CLLocationCoordinate2DIsValid(self) // Optional: treat (0,0) as invalid only if your API marks coords as missing/derived. }
177-189: Prefer Unified Logging over print for production diagnostics.Use os.Logger/OSLog for structured logs and privacy controls.
- } else { - print("⚠️ Could not load template image: \(imageName)") - } + } else { + if #available(iOS 14.0, *) { + let logger = Logger(subsystem: "org.dash.wallet", category: "UIAssets") + logger.warning("Could not load template image: \(imageName, privacy: .public)") + } else { + NSLog("⚠️ Could not load template image: %@", imageName) + } + }(Remember to
import OSLogfor iOS 14+.)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.claude/agents/CLAUDE.md(1 hunks).claude/agents/ios-swift-safety-patterns.md(1 hunks).gitignore(0 hunks)
💤 Files with no reviewable changes (1)
- .gitignore
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-05T04:46:12.717Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#728
File: DashWallet.xcodeproj/project.pbxproj:1595-1599
Timestamp: 2025-09-05T04:46:12.717Z
Learning: In iOS projects, the `DashWallet.xcodeproj/project.pbxproj` file is automatically generated and managed by Xcode. Manual changes to this file should not be made, and the changes shown in diffs are typically the result of Xcode updating project configuration, dependencies, or build settings.
Applied to files:
.claude/agents/CLAUDE.md
🪛 markdownlint-cli2 (0.17.2)
.claude/agents/CLAUDE.md
172-172: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (4)
.claude/agents/CLAUDE.md (2)
1-230: Docs quality: solid overview and actionable checklists.Good structure, practical tips, and repo-specific context. Only minor nits above.
159-163: Update .claude/agents/CLAUDE.md — bump minimum iOS to 15.0 and require an Xcode that provides the iOS 15 SDK.DashWallet.xcodeproj/project.pbxproj sets IPHONEOS_DEPLOYMENT_TARGET = 15.0 and SWIFT_VERSION = 5.0; change the docs (lines 159–163) to reflect iOS 15.0+ and verify CI images include the matching Xcode/iOS SDK.
.claude/agents/ios-swift-safety-patterns.md (2)
227-235: Nice: build validation snippet is concrete and up to date.Destination uses a current simulator; complements earlier guidance.
1-387: Overall: excellent, pragmatic safety guide.Strong, repo-aware examples across optionals, MapKit safety, and UI state. Minimal tweaks above.
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.
Changes to consider:
- remove DEBUG things for the logs
- remove the CTX
logoutAPI, which does not exist, unless it actually does exist - Xcode 16 is required in the agent
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
Show resolved
Hide resolved
- Remove all debug print statements that were causing build noise - Restore proper file structure after correcting compilation errors - Clean up phone call action debug logging - Clean up location fetch and merchant data debug logging 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
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.
Actionable comments posted: 0
♻️ Duplicate comments (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
612-614: Bug: pass merchantId (not pointOfUseId) to allLocations — fix both callsitesExploreDash.allLocations / MerchantDAO.allLocations expect a merchantId; currently two callsites pass pointOfUseId which produces wrong counts and inconsistent “Show all locations” titles.
--- a/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift +++ b/DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift @@ - // Use the same call as AllMerchantLocationsDataProvider - ExploreDash.shared.allLocations(for: merchant.pointOfUseId, in: finalBounds, userPoint: finalUserPoint) { [weak self] result in + // Use merchantId as required by DAO + guard let merchantId = self.merchant.merchant?.merchantId, !merchantId.isEmpty else { + DSLogger.log("Missing merchantId; cannot fetch locations") + self.locationCount = 0 + self.updateShowAllLocationsButton() + return + } + ExploreDash.shared.allLocations(for: merchantId, in: finalBounds, userPoint: finalUserPoint) { [weak self] result in
- Also update DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (line ~89): replace
dataSource.allLocations(for: pointOfUse.pointOfUseId, ...) with a call that passes the merchant's merchantId (e.g. pointOfUse.merchant?.merchantId or pointOfUse.merchantId). Add a guard + log and return an empty PaginationResult on missing id to preserve failure handling.
🧹 Nitpick comments (9)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (9)
177-181: Normalize website URL; guard canOpenURLHandle missing scheme and avoid opening invalid URLs.
- guard let website = merchant.website, let url = URL(string: website) else { return } - UIApplication.shared.open(url, options: [:], completionHandler: nil) + guard let website = merchant.website else { return } + let normalized: String + if website.hasPrefix("http://") || website.hasPrefix("https://") { + normalized = website + } else { + normalized = "https://" + website + } + guard let url = URL(string: normalized), UIApplication.shared.canOpenURL(url) else { return } + UIApplication.shared.open(url, options: [:], completionHandler: nil)
811-817: Initialize subtitle without distance to match design and avoid flickerYou immediately replace it in refreshSubtitle(); set the desired value upfront.
- subLabel.text = subtitleWithDistance() + subLabel.text = subtitleWithoutDistance()
988-989: Localize user-facing merchant type stringsHardcoded strings break i18n.
- return m.type == .onlineAndPhysical ? "Physical Merchant, Online" : "Physical Merchant" + return m.type == .onlineAndPhysical + ? NSLocalizedString("Physical Merchant, Online", comment: "Merchant type") + : NSLocalizedString("Physical Merchant", comment: "Merchant type")
1089-1091: Localize “away”Use a format string for translation and reordering.
- let distanceString = NSAttributedString(string: "\n\(distanceText) away", attributes: distanceAttributes) + let awayText = String.localizedStringWithFormat( + NSLocalizedString("%@ away", comment: "Distance away"), + distanceText + ) + let distanceString = NSAttributedString(string: "\n\(awayText)", attributes: distanceAttributes)
1104-1106: Remove empty debug conditionalsThese no-op if-blocks add noise and confuse readers.
- if self.merchant.name.lowercased().contains("gamestop") || self.merchant.name.lowercased().contains("spotify") || self.merchant.name.lowercased().contains("buffalo") { - } ... - if self.merchant.name.lowercased().contains("gamestop") { - } ... - if self.merchant.name.lowercased().contains("gamestop") { - } ... - if self.merchant.name.lowercased().contains("gamestop") { - } ... - if self.merchant.name.lowercased().contains("gamestop") { - }Also applies to: 1112-1115, 1118-1120, 1122-1124, 1126-1128
864-864: Simplify textColor assignmentBoth branches set the same color.
- subtitleLabel.textColor = self.merchant.active ? .dw_tertiaryText() : .dw_tertiaryText() + subtitleLabel.textColor = .dw_tertiaryText()
157-158: Remove no-op completion handlerSilences SwiftLint “unused parameter” and reduces clutter.
- UIApplication.shared.open(url, options: [:], completionHandler: { success in - }) + UIApplication.shared.open(url, options: [:], completionHandler: nil)
898-920: Show the U.S.-only notice conditionallyAvoid confusing non-U.S. users and non-U.S. merchants.
private func createCountryNotice() -> UIView { let container = UIView() container.translatesAutoresizingMaskIntoConstraints = false let noticeLabel = UILabel() noticeLabel.translatesAutoresizingMaskIntoConstraints = false noticeLabel.text = NSLocalizedString("Note: This card works only in the United States.", comment: "DashSpend") noticeLabel.font = .dw_font(forTextStyle: .footnote) noticeLabel.textColor = .dw_secondaryText() noticeLabel.numberOfLines = 0 noticeLabel.textAlignment = .center + + // Gate visibility by merchant territory or device region + let isUSMerchant = (merchant.territory == "US") + let isUSDevice = (Locale.current.regionCode == "US") + container.isHidden = !(isUSMerchant || isUSDevice) container.addSubview(noticeLabel)
651-665: Hold a reference to the “Show all locations” button (avoid brittle view crawling)Relying on “last arranged subview” is fragile. Keep a weak reference and update directly.
@@ - private var locationCount: Int = 1 + private var locationCount: Int = 1 + private weak var showAllLocationsButton: UIButton? @@ - if let showAllBlock = containerView.arrangedSubviews.last { - // Find the button within the block - for (index, subview) in showAllBlock.subviews.enumerated() { - if let button = subview as? UIButton { - let newTitle = String.localizedStringWithFormat( - NSLocalizedString("Show all locations (%d)", comment: "Show all locations with count"), - locationCount - ) - button.setTitle(newTitle, for: .normal) - break - } - } - } else { - } + if let button = showAllLocationsButton { + let newTitle = String.localizedStringWithFormat( + NSLocalizedString("Show all locations (%d)", comment: "Show all locations with count"), + locationCount + ) + button.setTitle(newTitle, for: .normal) + } @@ - let showAllButton = UIButton() + let showAllButton = UIButton() showAllButton.translatesAutoresizingMaskIntoConstraints = false @@ showAllButton.addTarget(self, action: #selector(showAllLocationsAction), for: .touchUpInside) + self.showAllLocationsButton = showAllButtonAlso applies to: 678-685, 57-58
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift(7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (7)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/MerchantItemCell.swift (1)
configureHierarchy(64-93)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
configureHierarchy(343-347)callAction(91-106)websiteAction(180-194)DashWallet/Sources/Models/Explore Dash/Services/DWLocationManager.swift (3)
remove(103-106)add(96-101)requestAuthorization(91-94)DashWallet/Sources/UI/Views/BaseController/BaseViewController+NetworkReachability.swift (2)
stopNetworkMonitoring(52-54)startNetworkMonitoring(36-50)DashWallet/Sources/Models/Explore Dash/Infrastructure/DAO Impl/MerchantDAO.swift (3)
allLocations(200-247)items(38-39)items(42-175)DashWallet/Sources/Models/Explore Dash/ExploreDash.swift (1)
allLocations(189-192)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift (1)
items(34-79)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift
[Warning] 138-138: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 29-29: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 33-33: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 42-42: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 43-43: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 45-45: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 47-47: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 48-48: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 49-49: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 50-50: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 51-51: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 35-35: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 36-36: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 37-37: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 38-38: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 39-39: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 113-113: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 35-35: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 36-36: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 37-37: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 38-38: Return arrow and return type should be separated by a single space or on a separate line
(return_arrow_whitespace)
[Warning] 128-128: Unimplemented functions should be marked as unavailable
(unavailable_function)
[Warning] 157-157: Unused parameter in a closure should be replaced with _
(unused_closure_parameter)
[Warning] 32-32: Prefer -> Void over -> ()
(void_return)
[Warning] 35-35: Prefer -> Void over -> ()
(void_return)
[Warning] 36-36: Prefer -> Void over -> ()
(void_return)
[Warning] 37-37: Prefer -> Void over -> ()
(void_return)
[Warning] 38-38: Prefer -> Void over -> ()
(void_return)
[Warning] 39-39: Prefer -> Void over -> ()
(void_return)
[Warning] 40-40: Prefer -> Void over -> ()
(void_return)
[Warning] 206-206: Prefer object literals over image and color inits
(object_literal)
[Warning] 277-277: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 383-383: Attributes should be on their own lines in functions and types, but on the same line as variables and imports
(attributes)
[Warning] 351-351: Prefer object literals over image and color inits
(object_literal)
[Warning] 445-445: Prefer object literals over image and color inits
(object_literal)
[Warning] 511-511: Prefer object literals over image and color inits
(object_literal)
[Warning] 653-653: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1066-1066: Prefer object literals over image and color inits
(object_literal)
[Warning] 1184-1184: Don't include vertical whitespace (empty line) before closing braces
(vertical_whitespace_closing_braces)
[Warning] 1005-1005: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1047-1047: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1081-1081: Don't include vertical whitespace (empty line) after opening braces
(vertical_whitespace_opening_braces)
[Warning] 1216-1216: Unimplemented functions should be marked as unavailable
(unavailable_function)
🔇 Additional comments (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (1)
1041-1060: Verify usage of getAddressWithDistance(_:); remove or consolidate with attributed variantripgrep returned "No files were searched", so callers couldn't be confirmed. Verify whether getAddressWithDistance(_:) is referenced; if unused, delete it to reduce surface area; if used, route callers to the attributed-string variant to keep a single source of truth.
Location: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (lines 1041–1060)
- Remove non-existent CTX logout API call from CTXSpendService - Clean up remaining debug print statements in PointOfUseDetailsViewController - Fix trailing whitespace in DWUpholdMainnetConstants - Update Xcode version requirement to 16+ in documentation The CTX logout endpoint does not exist, so only clear local tokens. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
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.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (1)
111-115: DRY token clearing.
clearTokensOnRefreshFailure()andlogout()duplicate logic. Factor a single helper to avoid drift.Apply:
+ @MainActor + private func clearAllCredentials() { + KeychainService.delete(key: Keys.accessToken) + KeychainService.delete(key: Keys.refreshToken) + KeychainService.delete(key: Keys.email) + userDefaults.removeObject(forKey: Keys.deviceUUID) + updateSignInState() + } @@ - func clearTokensOnRefreshFailure() { - KeychainService.delete(key: Keys.accessToken) - KeychainService.delete(key: Keys.refreshToken) - updateSignInState() - } + func clearTokensOnRefreshFailure() { Task { @MainActor in clearAllCredentials() } }And call
clearAllCredentials()fromlogout().
♻️ Duplicate comments (3)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (3)
30-33: Access-level mismatch (compile error).
publicmember/initializer inside an internal class. Lower access.Apply:
- @objc public var payWithDashHandler: (()->())? + @objc var payWithDashHandler: (()->())? @@ - public init(pointOfUse: ExplorePointOfUse, isShowAllHidden: Bool = true, currentFilters: PointOfUseListFilters? = nil, currentMapBounds: ExploreMapBounds? = nil) { + init(pointOfUse: ExplorePointOfUse, isShowAllHidden: Bool = true, currentFilters: PointOfUseListFilters? = nil, currentMapBounds: ExploreMapBounds? = nil) {Also applies to: 40-45
37-39: Remove reintroduced Map button (dead code + IUO crash risk).
showMapButton: UIButton!is never initialized (setup is not called). Accessing it inupdateMapButtonVisibility()will crash.Apply:
- private var contentViewTopConstraint: NSLayoutConstraint? - private var showMapButton: UIButton! + private var contentViewTopConstraint: NSLayoutConstraint? @@ - private func setupMapButton() { ... } - - @objc - private func showMapAction() { ... } - - private func updateMapButtonVisibility() { ... } + // Map button removed (unused)If you keep it, make
showMapButtonoptional and guard before use, but removal is preferable.Also applies to: 194-231, 233-239
274-282: Avoid force unwrap of contentViewTopConstraint.Create the constraint, assign to the property, and use the local variable.
Apply:
- let screenHeight = UIScreen.main.bounds.height - let kDefaultBottomHalfPosition = screenHeight * 0.35 // Position higher to show more content including button - contentViewTopConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + let screenHeight = UIScreen.main.bounds.height + let kDefaultBottomHalfPosition = screenHeight * 0.35 // Position higher to show more content including button + let topConstraint = contentView.topAnchor.constraint(equalTo: view.topAnchor, constant: kDefaultBottomHalfPosition) + contentViewTopConstraint = topConstraint @@ - contentViewTopConstraint!, + topConstraint,
🧹 Nitpick comments (7)
.claude/agents/CLAUDE.md (2)
172-176: Add language to fenced code block (markdownlint MD040).Specify a language for this fence to satisfy markdownlint and improve rendering.
-``` +```text ../DashSync/ # Core Dash protocol library ../dapi-grpc/ # gRPC API definitions ../dashwallet-ios/ # This repository -``` +```
186-191: Strengthen Security section with secrets management guidance.Given recent concerns about exposed secrets, add explicit guidance on secret storage, rotation, and scanning.
### Security - Jailbreak detection - Hardware encryption usage - Private key protection - Secure enclave integration + +#### Secrets management +- Never commit API keys, tokens, or credentials. Store them in per-target .xcconfig files or CI secrets. +- Use environment variables/CI secure vars to inject secrets at build time. +- Rotate credentials regularly and on every suspected exposure. +- Add pre-commit hooks and CI checks (e.g., gitleaks) to scan for secrets. +- Document the process to invalidate/regenerate compromised credentials.DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (5)
108-176: Unify sheet positions; avoid shadowing the property.Hard-coded positions appear in multiple places, and
guard let contentViewTopConstraint = contentViewTopConstraintshadows the property. Centralize positions and use a non‑shadowing local.Apply:
- private func handlePanGesture(_ sender: UIPanGestureRecognizer) { - guard let contentViewTopConstraint = contentViewTopConstraint else { + private func handlePanGesture(_ sender: UIPanGestureRecognizer) { + guard let topConstraint = contentViewTopConstraint else { return } @@ - let currentY = contentViewTopConstraint.constant + let currentY = topConstraint.constant @@ - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 - let kDefaultBottomHalfPosition = screenHeight * 0.35 - let kDefaultOpenedMapPosition = screenHeight * 0.2 + let pos = sheetPositions() + let kDefaultClosedMapPosition = pos.closed + let kDefaultBottomHalfPosition = pos.half + let kDefaultOpenedMapPosition = pos.opened @@ - contentViewTopConstraint.constant = max(minY, min(maxY, newY)) + topConstraint.constant = max(minY, min(maxY, newY)) @@ - let finalCurrentY = contentViewTopConstraint.constant - let screenHeight = view.frame.size.height - let kDefaultClosedMapPosition = screenHeight * 0.75 - let kDefaultBottomHalfPosition = screenHeight * 0.35 - let kDefaultOpenedMapPosition = screenHeight * 0.2 + let finalCurrentY = topConstraint.constant + let pos = sheetPositions() + let kDefaultClosedMapPosition = pos.closed + let kDefaultBottomHalfPosition = pos.half + let kDefaultOpenedMapPosition = pos.opened @@ - contentViewTopConstraint.constant = finalY + topConstraint.constant = finalYAdd once (outside this hunk):
private func sheetPositions() -> (opened: CGFloat, half: CGFloat, closed: CGFloat) { let h = view.bounds.height return (opened: h * 0.2, half: h * 0.35, closed: h * 0.75) }Also applies to: 219-226
178-191: Harden URL normalization (trim, case-insensitive scheme, encoding).Current check is case-sensitive and doesn’t trim/encode.
Apply:
- guard let website = pointOfUse.website else { return } - - // Normalize URL by adding https scheme if missing - let normalizedWebsite: String - if website.hasPrefix("http://") || website.hasPrefix("https://") { - normalizedWebsite = website - } else { - normalizedWebsite = "https://" + website - } - - guard let url = URL(string: normalizedWebsite) else { return } + guard var website = pointOfUse.website?.trimmingCharacters(in: .whitespacesAndNewlines), + !website.isEmpty else { return } + if !website.lowercased().hasPrefix("http://") && !website.lowercased().hasPrefix("https://") { + website = "https://" + website + } + guard var comps = URLComponents(string: website) else { return } + comps.percentEncodedPath = comps.percentEncodedPath // force encoding if needed + guard let url = comps.url else { return }
56-60: Clamp negative inset.
mapView.frame.height - detailsView.frame.height - 10can be negative. Clamp to ≥ 0.Apply:
- mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: mapView.frame.height - detailsView.frame.height - 10, - right: 0) + let bottom = max(0, mapView.frame.height - detailsView.frame.height - 10) + mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottom, right: 0)
299-302: Replace print with logger and gate for DEBUG.Avoid prints in production.
Apply:
- print("Warning: Failed to create detailsView for pointOfUse category: \(pointOfUse.category)") + #if DEBUG + DSLogger.log("Warning: Failed to create detailsView for pointOfUse category: \(pointOfUse.category)") + #endif
448-456: UI on main actor.
tryRefreshCtxToken()shows a modal; mark main actor to avoid UI off-main.Apply:
- private func tryRefreshCtxToken() async throws -> Bool { + @MainActor + private func tryRefreshCtxToken() async throws -> Bool {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.claude/agents/CLAUDE.md(1 hunks)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift(1 hunks)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift(12 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-05T04:46:12.717Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#728
File: DashWallet.xcodeproj/project.pbxproj:1595-1599
Timestamp: 2025-09-05T04:46:12.717Z
Learning: In iOS projects, the `DashWallet.xcodeproj/project.pbxproj` file is automatically generated and managed by Xcode. Manual changes to this file should not be made, and the changes shown in diffs are typically the result of Xcode updating project configuration, dependencies, or build settings.
Applied to files:
.claude/agents/CLAUDE.md
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is confirmed unused after code analysis. Radius functionality is properly handled through model.currentRadius and filters?.currentRadius, making the controller property redundant and safe to remove.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
📚 Learning: 2025-09-05T18:32:21.463Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift:67-67
Timestamp: 2025-09-05T18:32:21.463Z
Learning: The radius property in ExplorePointOfUseListViewController is unused and should be removed. Radius functionality is handled through the model.currentRadius and filters system instead of a controller-level property.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
📚 Learning: 2025-09-12T21:24:36.691Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#729
File: DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Model/AllMerchantLocationsDataProvider.swift:89-89
Timestamp: 2025-09-12T21:24:36.691Z
Learning: The pointOfUseId computed property on ExplorePointOfUse acts as an abstraction that returns the appropriate identifier based on the category - for merchants it returns merchantId, making direct calls with pointOfUseId equivalent to using merchantId for merchant entries.
Applied to files:
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
🧬 Code graph analysis (1)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (4)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapView.swift (4)
mapView(250-255)mapView(257-267)mapView(283-289)show(139-180)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/ExplorePointOfUseListViewController.swift (1)
show(142-148)DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/PointOfUseDetailsView.swift (3)
callAction(138-159)websiteAction(177-181)setupGrabberPanGesture(1175-1183)DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (1)
getMerchant(195-223)
🪛 markdownlint-cli2 (0.17.2)
.claude/agents/CLAUDE.md
172-172: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 SwiftLint (0.57.0)
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift
[Warning] 38-38: Implicitly unwrapped optionals should be avoided when possible
(implicitly_unwrapped_optional)
[Warning] 40-40: Ensure declarations have a lower access control level than their enclosing parent
(lower_acl_than_parent)
[Warning] 59-59: Function parameters should be aligned vertically if they're in multiple lines in a method call
(vertical_parameter_alignment_on_call)
[Warning] 281-281: Force unwrapping should be avoided
(force_unwrapping)
🔇 Additional comments (6)
.claude/agents/CLAUDE.md (2)
210-211: Confirmed: kDefaultRadius = 32000 (meters)Defined at DashWallet/Sources/Models/Explore Dash/ExploreDash.swift:24 as
let kDefaultRadius: Double = 32000— use this constant instead of hardcoded values.
159-163: Confirm Rust toolchain requirement.Automated search returned "No files were searched", so presence of Cargo.toml or .rs sources could not be confirmed. If there is no Rust code, remove the "Rust toolchain (for DashSync integration)" bullet in .claude/agents/CLAUDE.md (lines 159–163). If Rust is required, document the exact crate/Cargo.toml paths and minimal setup steps (rustup/toolchain) that justify the requirement.
DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift (2)
97-99: Verify deviceUUID lifecycle before deleting on logout.If the backend uses
deviceUUIDfor device trust/rate‑limiting, wiping it on logout may cause unexpected behavior on next login.Do we intend
deviceUUIDto persist across sessions? If yes, keep it; if not, ensure server expectations are aligned.
90-91: Call server logout before clearing local tokens (logout endpoint exists).
CTXSpendEndpoint already exposescase logout(DashWallet/Sources/Models/Explore Dash/Services/CTXSpendEndpoint.swift). Update CTXSpendService.logout() (DashWallet/Sources/Models/Explore Dash/Services/CTXSpendService.swift — ~lines 90–91) to perform a best‑effort request to the logout endpoint, handle/fall back on failures, then clear local tokens; remove or update the comment "// Clear local tokens (logout API does not exist)".Likely an incorrect or invalid review comment.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/PointOfUseDetailsViewController.swift (2)
93-106: Call action hardening looks good.Digits-only sanitization,
canOpenURL, and early‑exits are correct.
327-330: Constraint change: verify on small screens/Dynamic Type.Removing the bottom constraint avoids compression, but can lead to ambiguous height on short content. Please test iPhone SE and large fonts.
- Restore CTX logout endpoint and server call functionality to original state - No changes should have been made to CTX logout behavior - only UI text location changed - Move CLAUDE.md to root folder per Anthropic best practices - CTX logout now properly attempts server logout before local token cleanup 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
- Remove server logout attempt and async Task wrapper - Implement synchronous logout that only clears local authentication data - Remove unused logout endpoint from CTXSpendEndpoint.swift - Forces complete re-authentication flow when user logs in again 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
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.
Actionable comments posted: 0
🧹 Nitpick comments (4)
CLAUDE.md (4)
172-176: Add language to fenced code block (fixes markdownlint MD040).Specify a language for the directory listing code fence.
-``` +```text ../DashSync/ # Core Dash protocol library ../dapi-grpc/ # gRPC API definitions ../dashwallet-ios/ # This repository--- `26-32`: **Provide a concrete xcodebuild example for non-Fastlane runs.** The code block contains only comments. Add a reproducible CLI for CI/local use. ```diff # Or in Xcode, run tests on iPhone 8 simulator (as configured in Fastfile) +# +# CLI alternative (without Fastlane): +xcodebuild \ + -workspace DashWallet.xcworkspace \ + -scheme dashwallet \ + -destination 'platform=iOS Simulator,name=iPhone 8' \ + test
186-191: Strengthen “Security” with secrets-handling guidance.Given recent concerns about exposed Uphold secrets, add explicit do/don’t guidance (env/xcconfig, Keychain, no secrets in repo/logs).
### Security - Jailbreak detection - Hardware encryption usage - Private key protection - Secure enclave integration + - Secrets management: + - Store API keys in per-env .xcconfig (not in repo), or fetch at runtime from a secure backend. + - Never commit secrets to source, Info.plist, or sample configs. + - Use Keychain for persisted tokens; avoid logging tokens/user PII. + - Use GitHub Actions/CI secrets; prohibit echoing secrets in logs.
225-230: Augment the review checklist with security/privacy checks.Add checks for secrets and PII/logging to make this actionable during PRs.
- [ ] Search for `!` force unwraps in location/coordinate handling - [ ] Verify template images use proper rendering mode - [ ] Check radius constants are consistent across codebase - [ ] Ensure compactMap closures have explicit types if needed - [ ] Remove unused properties that create inconsistency + - [ ] Scan diffs for API keys/secrets; ensure none are committed + - [ ] Verify logs don’t include addresses, usernames, tokens, or PII
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
CLAUDE.md(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-05T04:46:12.717Z
Learnt from: HashEngineering
PR: dashpay/dashwallet-ios#728
File: DashWallet.xcodeproj/project.pbxproj:1595-1599
Timestamp: 2025-09-05T04:46:12.717Z
Learning: In iOS projects, the `DashWallet.xcodeproj/project.pbxproj` file is automatically generated and managed by Xcode. Manual changes to this file should not be made, and the changes shown in diffs are typically the result of Xcode updating project configuration, dependencies, or build settings.
Applied to files:
CLAUDE.md
🪛 markdownlint-cli2 (0.17.2)
CLAUDE.md
172-172: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (2)
CLAUDE.md (2)
159-163: Verify minimum toolchain targets (Xcode/iOS).Confirm Xcode 16+ and iOS 14.0+ are still the project’s enforced minima across all targets/schemes before we institutionalize them here.
98-101: Validate referenced paths/names match the repo.Confirm “SwiftUI Components/” and “UIHostingController+DashWallet.swift” exist at these exact paths/names to avoid drift in docs.
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.
Looks fine, we will merge this in a few days.
|
These changes were implemented in PR732 |
… display, and country notice
🤖 Generated with Claude Code
Issue being fixed or feature implemented
What was done?
How Has This Been Tested?
Breaking Changes
Checklist:
For repository code-owners and collaborators only
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes
Documentation
Chores