Skip to content

feat: Update menu items on the More page#765

Merged
HashEngineering merged 3 commits into
masterfrom
feat/update-tools
Apr 24, 2026
Merged

feat: Update menu items on the More page#765
HashEngineering merged 3 commits into
masterfrom
feat/update-tools

Conversation

@ross-dash
Copy link
Copy Markdown
Contributor

@ross-dash ross-dash commented Apr 22, 2026

…gation

  • Migrate all menu items to use the new MenuItem component
  • Update styles for MenuItemsList
  • Replace all menu item icons with updated assets
  • Change "Buy & Sell Dash" and "Explore" from bottom sheet to full page

Issue being fixed or feature implemented

What was done?

How Has This Been Tested?

Breaking Changes

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • New Features

    • Added Buy & Sell Dash screen with integrated service portals.
    • Added Explore Dash menu with routing for merchants, ATMs and gifting.
  • UI Improvements

    • Updated menu styling: spacing, rounded containers, shadows, and header component.
    • Increased icon sizing for better balance; separated Touch ID and Face ID items.
    • New reusable TopIntro and navigation back components.
  • Assets & Localization

    • Added/renamed multiple image and color assets (including Black800); updated English strings.

…gation

- Migrate all menu items to use the new MenuItem component
- Update styles for MenuItemsList
- Replace all menu item icons with updated assets
- Change "Buy & Sell Dash" and "Explore" from bottom sheet to full page
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Warning

Rate limit exceeded

@ross-dash has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 27 minutes and 26 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 27 minutes and 26 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c25890b3-25ab-4c89-8e57-d139896dccf0

📥 Commits

Reviewing files that changed from the base of the PR and between c8575ef and a7456e7.

📒 Files selected for processing (7)
  • DashWallet/Sources/UI/Buy Sell/BuySellPortalScreen.swift
  • DashWallet/Sources/UI/Buy Sell/BuySellPortalViewController.swift
  • DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift
  • DashWallet/Sources/UI/Explore Dash/ExploreViewController.swift
  • DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift
  • DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift
  • DashWallet/Sources/UI/Views/Navigation/BaseNavigationController.swift
📝 Walkthrough

Walkthrough

Adds two SwiftUI screens (BuySellPortal, ExploreMenu), new SwiftUI components (TopIntro, CrowdNodeAPYBadge), converts several UIKit controllers to SwiftUI-hosted flows, updates MenuItem and color assets, and makes extensive asset catalog and Xcode project configuration changes.

Changes

Cohort / File(s) Summary
Commit Reference
DashSyncCurrentCommit
Replaced commit id value e272a368fa8b5a3fcaa637f5631352980dcc557f5e4545e0fefb2b2aa98c2209394898ea6cfcd43b.
Xcode project & scheme
DashWallet.xcodeproj/project.pbxproj, DashWallet.xcodeproj/xcshareddata/xcschemes/dashwallet.xcscheme
Added Swift sources to build phases (ExploreMenuScreen.swift, BuySellPortalScreen.swift, TopIntro.swift), updated file reference/display name entries, replaced placeholder build entries, and changed scheme LaunchAction config from TestflightRelease.
New SwiftUI screens & components
DashWallet/Sources/UI/Buy Sell/BuySellPortalScreen.swift, DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift, DashWallet/Sources/UI/SwiftUI Components/TopIntro.swift, DashWallet/Sources/UI/Explore Dash/CrowdNodeAPYBadge
Added BuySellPortalScreen and ExploreMenuScreen SwiftUI views and CrowdNodeAPYBadge; introduced TopIntro reusable header component and new view initializers/closures for navigation callbacks.
ViewController → SwiftUI hosting
DashWallet/Sources/UI/Buy Sell/BuySellPortalViewController.swift, DashWallet/Sources/UI/Home/HomeViewController+Shortcuts.swift, DashWallet/Sources/UI/Main/MainTabbarController.swift, DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift
Replaced storyboard/collection-driven controllers with UIHostingController-hosted SwiftUI screens; removed collection view delegate/data-source and delegate-based explore wiring; navigation now uses closure callbacks and NavigationBarDisplayable/hidden bar handling.
Model changes (Combine/SwiftUI)
DashWallet/Sources/UI/Buy Sell/Model/BuySellPortalModel.swift
Converted BuySellPortalModel to ObservableObject and @Published var items; removed delegate protocol and dispatches updates to main thread.
MenuItem / SwiftUI component updates
DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift, DashWallet/Sources/UI/SwiftUI Components/Color+DWStyle.swift
MenuItem: added iconAlignment, trailingView, showDashAmountDirection, subtitle style/limits, reduced icon size and layout tweaks; Color: added Color.black800 asset mapping and changed dark-mode Color.shadow to UIColor.clear.
Menu screens & view models
DashWallet/Sources/UI/Menu/.../SecurityMenuScreen.swift, .../SecurityMenuViewModel.swift, .../Settings/SettingsScreen.swift, .../Settings/SettingsMenuViewModel.swift, .../Tools/ToolsMenuScreen.swift, .../Tools/ToolsMenuViewModel.swift, DashWallet/Sources/UI/Menu/Main/MainMenuViewModel.swift
Replaced inline back headers with NavBarBack and TopIntro; adjusted menu card styling (corner radius, padding, min heights); increased many icon maxHeight from 22→30; split biometric menu into explicit Touch ID and Face ID items with toggles; changed notifications label to “Notifications”.
ExploreViewController constant scope
DashWallet/Sources/UI/Explore Dash/ExploreViewController.swift
Changed kMerchantTypesShown from private → module/internal visibility (removed private).
Asset catalog additions/updates/removals
DashWallet/Resources/AppAssets.xcassets/... (new Dash logo, many Menu/*.imageset/Contents.json changes, deletions like menu_* and some image.* manifests)
Added new Dash logo assets and many new/renamed menu image set Contents.json files; updated numerous image filenames (e.g., Layer_1/Group → descriptive names); removed several image set manifests (image.touch.id, menu_buySellDash, menu_security, menu_settings, menu_tools, menu_support, etc.).
Shared color assets
Shared/Resources/SharedAssets.xcassets/Colors/BackgroundColor.colorset/Contents.json, .../Black800.colorset/Contents.json
Adjusted dark-mode BackgroundColor RGB values and added new Black800.colorset with RGB (0x1E,0x1F,0x24).
Localization
DashWallet/en.lproj/Localizable.strings
Added multiple English localization keys/values for Buy/Sell, Explore, CrowdNode, backup prompts, send/receive labels, CSV export informational text.
Other project metadata
various small pbxproj/path display name updates
Updated Swift file reference paths/display names to remove/alter DashSpend/... prefixes and replaced (null) placeholders with proper BuildFile entries in pbxproj.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐇 I hopped through code and found new views,
SwiftUI gardens with bright new hues.
Menus trimmed and assets aligned,
TopIntro guides — the app’s refined.
A tiny rabbit cheers, "Nice find!" ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Update menu items on the More page' accurately reflects the main changes in the PR. The changeset predominantly involves updating menu items (icons, sizes, components), refactoring menu screens with new UI components (TopIntro, NavBarBack), and changing navigation presentation. The title is specific, clear, and directly related to the primary focus of the work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/update-tools

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ross-dash ross-dash changed the title feat/update-tools feat: Update menu items on the More page Apr 22, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a 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 (1)
DashWallet/Sources/UI/Buy Sell/Model/BuySellPortalModel.swift (1)

93-110: ⚠️ Potential issue | 🟡 Minor

Avoid double delegate notifications.

self?.items = items already triggers items.didSet, which calls serviceItemsDidChange(). The extra call on Line 109 notifies legacy delegates twice for one data update.

🧹 Proposed fix
             DispatchQueue.main.async {
                 self?.items = items
-                self?.delegate?.serviceItemsDidChange()
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Buy` Sell/Model/BuySellPortalModel.swift around lines
93 - 110, The closure passed to serviceItemDataProvider.listenForData assigns
self?.items = items which already triggers the items.didSet and calls
delegate?.serviceItemsDidChange(); remove the subsequent explicit
delegate?.serviceItemsDidChange() call inside that DispatchQueue.main.async
block in the init so delegates are notified only once (locate the init, the
listenForData closure, the items property with didSet, and remove the redundant
call).
🧹 Nitpick comments (5)
DashWallet/Sources/UI/SwiftUI Components/TopIntro.swift (1)

22-22: Remove the explicit nil initializer as it is unnecessary.

Optional stored properties are implicitly initialized to nil. While the SwiftLint rule implicit_optional_initialization is not currently enabled in the repository's .swiftlint.yml, removing the explicit = nil is a best-practice style improvement.

🧹 Proposed fix
-    var subtitle: String? = nil
+    var subtitle: String?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/SwiftUI` Components/TopIntro.swift at line 22, The
stored property declaration "var subtitle: String? = nil" in TopIntro (symbol:
subtitle) should drop the explicit "= nil" since optional properties are
implicitly initialized to nil; update the property to "var subtitle: String?" to
follow Swift style and avoid the unnecessary initializer.
DashWallet/Sources/UI/Buy Sell/BuySellPortalScreen.swift (2)

89-117: .id(subtitle) on MenuItem indicates a state-retention issue in MenuItem.

Attaching .id(coinbaseSubtitle) / .id(upholdSubtitle) forces a new MenuItem identity each time the subtitle string changes, which forces SwiftUI to rebuild the row. This is only necessary because MenuItem.subtitleView is declared @State and captures its initial value in the init, so updates passed in by the parent are otherwise ignored.

This works, but it has side effects (state inside MenuItem gets reset, animations/transitions can flicker). Consider fixing the root cause in MenuItem.swift (make subtitleView a plain stored property, or recompute it in body) instead of working around it here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalScreen.swift around lines 89 -
117, The root issue is that MenuItem.subtitleView is declared `@State` which
captures its initial value and prevents parent updates, causing consumers
(BuySellPortalScreen) to add .id(subtitle) to force rebuild; fix MenuItem by
removing `@State` from subtitleView and make it a regular stored property or build
the subtitle view inside MenuItem.body from the passed subtitle parameter
(ensure the init assigns the plain property or recomputes it in body), and then
remove the workaround .id(coinbaseSubtitle)/.id(upholdSubtitle) from
BuySellPortalScreen so rows update without identity resets or flicker.

57-123: Extract duplicated card container styling.

The Topper, Coinbase, and Uphold blocks each repeat the same .padding(6) / .background(Color.secondaryBackground) / .clipShape(RoundedRectangle(...)) / .shadow(...) / .padding(.horizontal, 20) stack. Consider extracting a small ViewModifier or container view (e.g. MenuCard) to keep this screen — and other migrated menus in the PR — consistent and easier to change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalScreen.swift around lines 57 -
123, The three service blocks (Topper, Coinbase, Uphold) repeat the same
container modifiers; extract them into a reusable MenuCard (either a
ViewModifier named MenuCardModifier or a small container View named MenuCard)
and wrap the inner content (the existing inner VStack that contains MenuItem)
with it. Implement MenuCard to apply .padding(6)
.background(Color.secondaryBackground)
.clipShape(RoundedRectangle(cornerRadius:20, style:.continuous)) .shadow(color:
Color.shadow, radius:20, x:0, y:5) .padding(.horizontal, 20), then replace the
duplicated modifier chain on the Topper, Coinbase and Uphold VStacks with either
.modifier(MenuCardModifier()) or by embedding their inner VStack inside MenuCard
{ ... } while keeping MenuItem, id(coinbaseSubtitle)/id(upholdSubtitle),
frame(minHeight:56), and the existing trailingView/serviceBalanceView usage
unchanged.
DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift (1)

21-21: Duplicate merchantTypesInfoDialogShownKey string constant.

ExploreViewController.swift already defines the same UserDefaults key as kMerchantTypesShown = "merchantTypesInfoDialogShownKey". Having two file-private constants with the same string value is a minor DRY violation and risks drift if one is ever renamed. Consider extracting a single shared constant (e.g., in an ExploreConstants enum or on UserDefaults.Key) used by both call sites, or remove it from the file that is being retired.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Explore` Dash/ExploreMenuScreen.swift at line 21, There
is a duplicate string constant: replace the file-private kMerchantTypesShownKey
with a single shared constant used by both locations (the existing
kMerchantTypesShown in ExploreViewController and this file); create a single
source of truth (e.g., add a shared ExploreConstants enum or an extension on
UserDefaults.Key with merchantTypesInfoDialogShownKey) and update all references
to use that shared symbol instead of the duplicate
kMerchantTypesShownKey/kMerchantTypesShown identifiers so the key is defined
once and referenced everywhere.
DashWallet/Sources/UI/Buy Sell/BuySellPortalViewController.swift (1)

111-159: Delegate wiring is now effectively dead code; MARK comment is inaccurate.

Two small nits in the migrated controller:

  1. Line 155 — // MARK: PortalModelDelegate is out-of-date; the protocol is BuySellPortalModelDelegate. Renaming keeps the Xcode jump-bar accurate.
  2. serviceItemsDidChange() is now a no-op (SwiftUI observes @Published items directly via BuySellPortalScreen), yet model.delegate = self still assigns the controller. If no other path needs the delegate callback, consider dropping both the delegate assignment here and the BuySellPortalModelDelegate conformance, and removing delegate?.serviceItemsDidChange() from the model — the @Published emission is already sufficient. Otherwise keep the stub but add a brief comment explaining why it's intentionally empty.
🛠️ Minimal fix for the MARK
-// MARK: PortalModelDelegate
+// MARK: - BuySellPortalModelDelegate
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalViewController.swift around
lines 111 - 159, The controller still sets model.delegate = self and uses an
empty BuySellPortalModelDelegate stub (serviceItemsDidChange()) while SwiftUI
observes `@Published` items, and the MARK comment is misnamed; either remove the
delegate wiring and protocol conformance and delete any delegate invocation in
the model (delegate?.serviceItemsDidChange()), or keep the delegate but update
the MARK to "// MARK: BuySellPortalModelDelegate" and add a one-line comment
above func serviceItemsDidChange() explaining it is intentionally a no-op for
compatibility; update BuySellPortalViewController, the model.delegate = self
assignment, and any delegate calls in the model accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@DashWallet.xcodeproj/project.pbxproj`:
- Around line 9213-9219: Remove the unused empty PBXSourcesBuildPhase entry with
UUID 80B4C633F37C0A089A9C7138 from the project.pbxproj: locate the
PBXSourcesBuildPhase block named "Sources" (isa = PBXSourcesBuildPhase) with
that UUID and delete it, and confirm no PBXNativeTarget references its UUID in
any buildPhases arrays (remove any stale references if found) so the project
file no longer contains the orphaned empty sources phase.
- Line 1162: The project file contains 14 orphaned PBXBuildFile objects (IDs:
C9D2C70F2A320AA000D15901, C9D2C7272A320AA000D15901, C9D2C74C2A320AA000D15901,
C9D2C7692A320AA000D15901, C9D2C7AA2A320AA000D15901, C9D2C7B22A320AA000D15901,
C9D2C7C22A320AA000D15901, C9D2C7CB2A320AA000D15901, C9D2C84A2A320AA000D15901,
C9D2C86A2A320AA000D15901, C9D2C8992A320AA000D15901, C9D2C8E02A320AA000D15901,
C9D2C92B2A320AA000D15901, DE3A167A235B79D705C0A962) defined as "{isa =
PBXBuildFile; }" with no fileRef; remove each of these object definitions from
the PBXBuildFile section and also remove their occurrences from the build phase
"files = (...)" arrays in the Sources/Resources/Frameworks build phases where
they are referenced so no dangling IDs remain; after removal, verify the build
phase lists and that any legitimate files still have proper PBXBuildFile entries
with fileRef attributes.
- Around line 359-360: The new SwiftUI source files (BuySellPortalScreen.swift,
TopIntro.swift, ExploreMenuScreen.swift) are currently wired into the dashwallet
and dashpay targets but may be missing from other configuration targets
(testflight/testnet/WatchApp/TodayExtension); update the PBX build file entries
so each source has PBXBuildFile entries for every target that should include it
(or move these files into a shared source target/xcconfig if they must be
included everywhere), then locally build and run the following schemes to verify
no link-time errors: dashwallet.testnet, dashwallet.testflight, dashpay.testnet,
dashpay.testflight; specifically check the PBXBuildFile entries for the fileRefs
7B94B3697D7742BFA991B5CC (BuySellPortalScreen), the refs for TopIntro and
ExploreMenu, and ensure the WatchApp/TodayExtension targets do not erroneously
reference these SwiftUI files if they shouldn't.

In `@DashWallet/Sources/UI/Main/MainTabbarController.swift`:
- Line 234: The onShowGiftCard closure should select the Home tab before asking
homeController to present details; change the closure to first switch the tab
(e.g. call a local method like selectHome() or set self?.selectedIndex /
selectedViewController to the Home tab and ensure homeController is loaded) and
only then call self?.homeController?.showGiftCardDetails(txId: txId) so behavior
matches showGiftCard(_:).

In `@DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift`:
- Around line 421-457: The pushed SwiftUI screens in showExplore, showSettings,
showTools, and showSecurity use bare UIHostingController instances which bypass
BaseNavigationController.willShow; create thin UIViewController subclasses
(e.g., ExploreHostingController, SettingsHostingController,
ToolsHostingController, SecurityHostingController) that host the respective
SwiftUI view in their view hierarchy, have initializers accepting the SwiftUI
root view (or the same params used when constructing
ExploreMenuScreen/SettingsScreen/ToolsMenuScreen/SecurityMenuScreen), and make
those subclasses conform to NavigationBarDisplayable or
NavigationStackControllable as required; then replace the
UIHostingController(...) instances with these wrapper controllers before setting
hidesBottomBarWhenPushed and vc.pushViewController(...).

In `@DashWallet/Sources/UI/SwiftUI` Components/MenuItem.swift:
- Around line 61-69: The MenuItem initializer currently forces subtitles to a
single line by calling .lineLimit(1) inside the subtitleView closure; remove the
hard-coded .lineLimit(1) and add a new parameter (e.g., subtitleLineLimit: Int?
= nil or = 2) to the MenuItem initializer so callers can opt into single-line
truncation; then apply .lineLimit(subtitleLineLimit) (or omit the modifier when
nil) where subtitleView is constructed so existing multi-line subtitles are
preserved and screens that need truncation (e.g., BuySellPortalScreen) can pass
subtitleLineLimit: 1.

---

Outside diff comments:
In `@DashWallet/Sources/UI/Buy` Sell/Model/BuySellPortalModel.swift:
- Around line 93-110: The closure passed to
serviceItemDataProvider.listenForData assigns self?.items = items which already
triggers the items.didSet and calls delegate?.serviceItemsDidChange(); remove
the subsequent explicit delegate?.serviceItemsDidChange() call inside that
DispatchQueue.main.async block in the init so delegates are notified only once
(locate the init, the listenForData closure, the items property with didSet, and
remove the redundant call).

---

Nitpick comments:
In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalScreen.swift:
- Around line 89-117: The root issue is that MenuItem.subtitleView is declared
`@State` which captures its initial value and prevents parent updates, causing
consumers (BuySellPortalScreen) to add .id(subtitle) to force rebuild; fix
MenuItem by removing `@State` from subtitleView and make it a regular stored
property or build the subtitle view inside MenuItem.body from the passed
subtitle parameter (ensure the init assigns the plain property or recomputes it
in body), and then remove the workaround
.id(coinbaseSubtitle)/.id(upholdSubtitle) from BuySellPortalScreen so rows
update without identity resets or flicker.
- Around line 57-123: The three service blocks (Topper, Coinbase, Uphold) repeat
the same container modifiers; extract them into a reusable MenuCard (either a
ViewModifier named MenuCardModifier or a small container View named MenuCard)
and wrap the inner content (the existing inner VStack that contains MenuItem)
with it. Implement MenuCard to apply .padding(6)
.background(Color.secondaryBackground)
.clipShape(RoundedRectangle(cornerRadius:20, style:.continuous)) .shadow(color:
Color.shadow, radius:20, x:0, y:5) .padding(.horizontal, 20), then replace the
duplicated modifier chain on the Topper, Coinbase and Uphold VStacks with either
.modifier(MenuCardModifier()) or by embedding their inner VStack inside MenuCard
{ ... } while keeping MenuItem, id(coinbaseSubtitle)/id(upholdSubtitle),
frame(minHeight:56), and the existing trailingView/serviceBalanceView usage
unchanged.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalViewController.swift:
- Around line 111-159: The controller still sets model.delegate = self and uses
an empty BuySellPortalModelDelegate stub (serviceItemsDidChange()) while SwiftUI
observes `@Published` items, and the MARK comment is misnamed; either remove the
delegate wiring and protocol conformance and delete any delegate invocation in
the model (delegate?.serviceItemsDidChange()), or keep the delegate but update
the MARK to "// MARK: BuySellPortalModelDelegate" and add a one-line comment
above func serviceItemsDidChange() explaining it is intentionally a no-op for
compatibility; update BuySellPortalViewController, the model.delegate = self
assignment, and any delegate calls in the model accordingly.

In `@DashWallet/Sources/UI/Explore` Dash/ExploreMenuScreen.swift:
- Line 21: There is a duplicate string constant: replace the file-private
kMerchantTypesShownKey with a single shared constant used by both locations (the
existing kMerchantTypesShown in ExploreViewController and this file); create a
single source of truth (e.g., add a shared ExploreConstants enum or an extension
on UserDefaults.Key with merchantTypesInfoDialogShownKey) and update all
references to use that shared symbol instead of the duplicate
kMerchantTypesShownKey/kMerchantTypesShown identifiers so the key is defined
once and referenced everywhere.

In `@DashWallet/Sources/UI/SwiftUI` Components/TopIntro.swift:
- Line 22: The stored property declaration "var subtitle: String? = nil" in
TopIntro (symbol: subtitle) should drop the explicit "= nil" since optional
properties are implicitly initialized to nil; update the property to "var
subtitle: String?" to follow Swift style and avoid the unnecessary initializer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 615ed71b-0e46-4eb3-b85b-10ed7b1ed3b2

📥 Commits

Reviewing files that changed from the base of the PR and between 689c2b9 and 989d1ea.

⛔ Files ignored due to path filters (189)
  • DashWallet/Resources/AppAssets.xcassets/Dash logo/dash-logo-black.imageset/dash-logo-black.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Dash logo/dash-logo-black.imageset/dash-logo-black@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Dash logo/dash-logo-black.imageset/dash-logo-black@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Illustration/image.import.private.key.large.imageset/import.private.key.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Illustration/image.import.private.key.large.imageset/import.private.key@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Illustration/image.import.private.key.large.imageset/import.private.key@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Illustration/zenledger_large.imageset/zenledger_large.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Illustration/zenledger_large.imageset/zenledger_large@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Illustration/zenledger_large.imageset/zenledger_large@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/Image.face.id.imageset/Layer 1.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/Image.face.id.imageset/Layer 1@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/Image.face.id.imageset/Layer 1@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-advanced_security.imageset/image-menu-advanced_security.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-advanced_security.imageset/image-menu-advanced_security@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-advanced_security.imageset/image-menu-advanced_security@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-atm.imageset/image-menu-atm.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-atm.imageset/image-menu-atm@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-atm.imageset/image-menu-atm@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-autohide_balance.imageset/image-menu-autohide_balance.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-autohide_balance.imageset/image-menu-autohide_balance@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-autohide_balance.imageset/image-menu-autohide_balance@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-change_pin.imageset/image-menu-pin.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-change_pin.imageset/image-menu-pin@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-change_pin.imageset/image-menu-pin@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-enable_touch_id.imageset/image-menu-touch_id.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-enable_touch_id.imageset/image-menu-touch_id@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-enable_touch_id.imageset/image-menu-touch_id@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-face_id.imageset/image-menu-face_id.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-face_id.imageset/image-menu-face_id@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-face_id.imageset/image-menu-face_id@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-merchant.imageset/image-menu-merchant.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-merchant.imageset/image-menu-merchant@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-merchant.imageset/image-menu-merchant@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-recovery_phrase.imageset/image-menu-recovery_phrase.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-recovery_phrase.imageset/image-menu-recovery_phrase@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-recovery_phrase.imageset/image-menu-recovery_phrase@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-reset_wallet.imageset/image-menu-reset_wallet.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-reset_wallet.imageset/image-menu-reset_wallet@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-reset_wallet.imageset/image-menu-reset_wallet@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-staking.imageset/image-menu-staking.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-staking.imageset/image-menu-staking@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-staking.imageset/image-menu-staking@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.about.imageset/dash.logo.circle.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.about.imageset/dash.logo.circle@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.about.imageset/dash.logo.circle@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.advanced.security.imageset/advanced security.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.advanced.security.imageset/advanced security@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.advanced.security.imageset/advanced security@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.autohide.balance.imageset/eye.closed.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.autohide.balance.imageset/eye.closed@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.autohide.balance.imageset/eye.closed@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.buy.and.sell.imageset/buy.sell.dash.2.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.buy.and.sell.imageset/buy.sell.dash.2@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.buy.and.sell.imageset/buy.sell.dash.2@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.change.pin.imageset/Layer_1.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.change.pin.imageset/Layer_1@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.change.pin.imageset/Layer_1@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.coinjoin.menu.imageset/Layer_1.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.coinjoin.menu.imageset/Layer_1@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.coinjoin.menu.imageset/Layer_1@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.coinjoin.menu.imageset/mixing.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.coinjoin.menu.imageset/mixing@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.coinjoin.menu.imageset/mixing@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.csv.export.imageset/csv-export.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.csv.export.imageset/csv-export@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.csv.export.imageset/csv-export@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.csv.export.imageset/csv.export.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.csv.export.imageset/csv.export@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.csv.export.imageset/csv.export@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.currency.imageset/cash.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.currency.imageset/cash@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.currency.imageset/cash@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.currency.imageset/local.currency.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.currency.imageset/local.currency@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.currency.imageset/local.currency@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.explore.imageset/Explore-Blue.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.explore.imageset/Explore-Blue@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.explore.imageset/Explore-Blue@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.explore.imageset/explore-2.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.explore.imageset/explore-2@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.explore.imageset/explore-2@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.extend.public.key.imageset/extend-public-key.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.extend.public.key.imageset/extend-public-key@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.extend.public.key.imageset/extend-public-key@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.extend.public.key.imageset/public.key.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.extend.public.key.imageset/public.key@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.extend.public.key.imageset/public.key@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.import.private.key.imageset/import-private-key.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.import.private.key.imageset/import-private-key@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.import.private.key.imageset/import-private-key@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.import.private.key.imageset/import.private.key.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.import.private.key.imageset/import.private.key@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.import.private.key.imageset/import.private.key@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.masternode.keys.imageset/Layer_1.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.masternode.keys.imageset/Layer_1@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.masternode.keys.imageset/Layer_1@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.masternode.keys.imageset/masternode-keys.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.masternode.keys.imageset/masternode-keys@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.masternode.keys.imageset/masternode-keys@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.network.monitor.imageset/Layer_1.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.network.monitor.imageset/Layer_1@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.network.monitor.imageset/Layer_1@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.network.monitor.imageset/network-monitor.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.network.monitor.imageset/network-monitor@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.network.monitor.imageset/network-monitor@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.notifications.imageset/Layer 1.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.notifications.imageset/Layer 1@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.notifications.imageset/Layer 1@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.notifications.imageset/notification.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.notifications.imageset/notification@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.notifications.imageset/notification@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.recovery.phrase.imageset/Layer_1.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.recovery.phrase.imageset/Layer_1@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.recovery.phrase.imageset/Layer_1@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.rescan.imageset/rescan.blockchain.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.rescan.imageset/rescan.blockchain@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.rescan.imageset/rescan.blockchain@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.reset.wallet.imageset/reset wallet.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.reset.wallet.imageset/reset wallet@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.reset.wallet.imageset/reset wallet@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.security.imageset/Vector.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.security.imageset/Vector@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.security.imageset/Vector@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.security.imageset/security.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.security.imageset/security@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.security.imageset/security@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.settings.imageset/Iconly_x2F_Bold_x2F_Setting.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.settings.imageset/Iconly_x2F_Bold_x2F_Setting@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.settings.imageset/Iconly_x2F_Bold_x2F_Setting@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.settings.imageset/settings.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.settings.imageset/settings@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.settings.imageset/settings@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.support.imageset/Group.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.support.imageset/Group@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.support.imageset/Group@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.support.imageset/support.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.support.imageset/support@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.support.imageset/support@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.tools.imageset/Group.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.tools.imageset/Group@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.tools.imageset/Group@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.tools.imageset/tools.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.tools.imageset/tools@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.tools.imageset/tools@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.touch.id.imageset/fingerprint.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.touch.id.imageset/fingerprint@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.touch.id.imageset/fingerprint@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_buySellDash.imageset/menu_buySellDash.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_buySellDash.imageset/menu_buySellDash@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_buySellDash.imageset/menu_buySellDash@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_buySellDash.imageset/menu_buySellDash_dark.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_buySellDash.imageset/menu_buySellDash_dark@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_buySellDash.imageset/menu_buySellDash_dark@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_security.imageset/menu_security.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_security.imageset/menu_security@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_security.imageset/menu_security@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_security.imageset/menu_security_dark.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_security.imageset/menu_security_dark@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_security.imageset/menu_security_dark@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_settings.imageset/levels.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_settings.imageset/levels@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_settings.imageset/levels@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_settings.imageset/menu_settings.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_settings.imageset/menu_settings@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_settings.imageset/menu_settings@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_support.imageset/menu_support.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_support.imageset/menu_support@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_support.imageset/menu_support@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_tools.imageset/menu_tools.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_tools.imageset/menu_tools@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_tools.imageset/menu_tools@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_tools.imageset/tools.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_tools.imageset/tools@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_tools.imageset/tools@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-coinbase.imageset/menu-coinbase.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-coinbase.imageset/menu-coinbase@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-coinbase.imageset/menu-coinbase@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-topper.imageset/menu-topper.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-topper.imageset/menu-topper@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-topper.imageset/menu-topper@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-uphold.imageset/menu-uphold.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-uphold.imageset/menu-uphold@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-uphold.imageset/menu-uphold@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/zenledger.imageset/zen_ledger.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/zenledger.imageset/zen_ledger@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/zenledger.imageset/zen_ledger@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/zenledger.imageset/zenledger.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/zenledger.imageset/zenledger@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/zenledger.imageset/zenledger@3x.png is excluded by !**/*.png
📒 Files selected for processing (60)
  • DashSyncCurrentCommit
  • DashWallet.xcodeproj/project.pbxproj
  • DashWallet.xcodeproj/xcshareddata/xcschemes/dashwallet.xcscheme
  • DashWallet/Resources/AppAssets.xcassets/Dash logo/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Dash logo/dash-logo-black.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Illustration/image.import.private.key.large.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Illustration/zenledger_large.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-advanced_security.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-atm.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-autohide_balance.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-change_pin.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-enable_touch_id.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-face_id.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-merchant.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-recovery_phrase.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-reset_wallet.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-staking.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.coinjoin.menu.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.csv.export.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.currency.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.explore.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.extend.public.key.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.import.private.key.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.masternode.keys.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.network.monitor.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.notifications.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.reset.wallet.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.security.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.settings.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.support.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.tools.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.touch.id.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_buySellDash.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_security.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_settings.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_support.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_tools.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-coinbase.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-topper.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/service-uphold.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/zenledger.imageset/Contents.json
  • DashWallet/Sources/UI/Buy Sell/BuySellPortalScreen.swift
  • DashWallet/Sources/UI/Buy Sell/BuySellPortalViewController.swift
  • DashWallet/Sources/UI/Buy Sell/Model/BuySellPortalModel.swift
  • DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift
  • DashWallet/Sources/UI/Home/HomeViewController+Shortcuts.swift
  • DashWallet/Sources/UI/Main/MainTabbarController.swift
  • DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift
  • DashWallet/Sources/UI/Menu/Main/MainMenuViewModel.swift
  • DashWallet/Sources/UI/Menu/Security/SecurityMenuScreen.swift
  • DashWallet/Sources/UI/Menu/Security/SecurityMenuViewModel.swift
  • DashWallet/Sources/UI/Menu/Settings/SettingsMenuViewModel.swift
  • DashWallet/Sources/UI/Menu/Settings/SettingsScreen.swift
  • DashWallet/Sources/UI/Menu/Tools/ToolsMenuScreen.swift
  • DashWallet/Sources/UI/Menu/Tools/ToolsMenuViewModel.swift
  • DashWallet/Sources/UI/SwiftUI Components/Color+DWStyle.swift
  • DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift
  • DashWallet/Sources/UI/SwiftUI Components/TopIntro.swift
  • Shared/Resources/SharedAssets.xcassets/Colors/BackgroundColor.colorset/Contents.json
  • Shared/Resources/SharedAssets.xcassets/Colors/Black800.colorset/Contents.json
💤 Files with no reviewable changes (7)
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_support.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_buySellDash.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_security.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.reset.wallet.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_settings.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image.touch.id.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/menu_tools.imageset/Contents.json

Comment thread DashWallet.xcodeproj/project.pbxproj
Comment thread DashWallet.xcodeproj/project.pbxproj
Comment thread DashWallet.xcodeproj/project.pbxproj
Comment thread DashWallet/Sources/UI/Main/MainTabbarController.swift Outdated
Comment thread DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift
Comment thread DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift Outdated
Comment thread DashWallet.xcodeproj/xcshareddata/xcschemes/dashwallet.xcscheme
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a 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 (1)
DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift (1)

514-557: 🛠️ Refactor suggestion | 🟠 Major

Remove dead ExploreViewControllerDelegate conformance and unused methods from DelegateInternal.

showExplore() now uses ExploreMenuScreen with closure-based callbacks instead of ExploreViewController, making the ExploreViewControllerDelegate conformance on DelegateInternal dead code. Remove the protocol conformance and the three forwarding methods (exploreViewControllerShowSendPayment, exploreViewControllerShowReceivePayment, exploreViewControllerShowGiftCard) at lines 547–557. Additionally, remove the ExploreViewControllerDelegate protocol definition and the @objc weak var delegate property from ExploreViewController as they are no longer used.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift` around lines
514 - 557, Remove dead explore delegation: delete ExploreViewControllerDelegate
conformance from the DelegateInternal class and remove the three unused
forwarding methods exploreViewControllerShowSendPayment(_:),
exploreViewControllerShowReceivePayment(_:), and
exploreViewControllerShowGiftCard(_:txId:) from DelegateInternal; also delete
the ExploreViewControllerDelegate protocol definition and the now-unused `@objc`
weak var delegate property on ExploreViewController, since showExplore() now
uses ExploreMenuScreen closure callbacks instead of ExploreViewController
delegation.
♻️ Duplicate comments (1)
DashWallet/Sources/UI/Main/MainTabbarController.swift (1)

239-239: ⚠️ Potential issue | 🟠 Major

Wrap ExploreMenuScreen in a thin hosting controller subclass.

Pushing a bare UIHostingController(rootView: exploreScreen) into BaseNavigationController bypasses the navigation-bar handling performed by BaseNavigationController.willShow, which only applies to controllers conforming to NavigationBarDisplayable / NavigationStackControllable. The same pattern is already followed elsewhere in this repo (e.g., SelectCoinHostingController, MayaPortalViewController).

Based on learnings: "In the DashWallet iOS project, when pushing SwiftUI views into BaseNavigationController, wrap the SwiftUI view in a thin UIViewController subclass … instead of pushing a bare UIHostingController."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Main/MainTabbarController.swift` at line 239, Replace
the bare UIHostingController(rootView: exploreScreen) with a thin
hosting-controller subclass (e.g., ExploreMenuHostingController) that wraps the
ExploreMenuScreen and conforms to the navigation protocols used by
BaseNavigationController (NavigationBarDisplayable /
NavigationStackControllable) so BaseNavigationController.willShow can apply its
navigation-bar handling; follow the existing pattern used by
SelectCoinHostingController and MayaPortalViewController by creating
ExploreMenuHostingController that hosts the SwiftUI view and use
nvc.viewControllers = [ExploreMenuHostingController(rootView: exploreScreen)].
🧹 Nitpick comments (7)
DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift (1)

36-36: Minor: SwiftLint implicit_optional_initialization on new properties.

trailingView: AnyView? = nil (and the existing siblings flagged by SwiftLint on lines 25-39) should drop the explicit = nil to comply with the configured rule. Since you’re already touching this declaration list, a small cleanup here would silence the new warning introduced on line 36 without affecting behavior.

🛠️ Suggested change
-    var trailingView: AnyView? = nil
+    var trailingView: AnyView?

As per coding guidelines: "Use SwiftFormat and SwiftLint for Swift code formatting and linting as configured in .swiftformat and .swiftlint.yml files."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/SwiftUI` Components/MenuItem.swift at line 36, Remove
explicit "= nil" initializers from optional stored properties in the MenuItem
declaration (e.g., trailingView: AnyView?) to satisfy SwiftLint's
implicit_optional_initialization rule; locate the optional properties in
MenuItem.swift (the trailingView property and the other optional siblings in the
same declaration block) and change declarations like "trailingView: AnyView? =
nil" to "trailingView: AnyView?" without altering types or behavior.
DashWallet/Sources/UI/Buy Sell/BuySellPortalViewController.swift (1)

147-150: Prefer static over class in a final class.

Since the class is final, the method cannot be overridden and static more accurately conveys intent (and silences SwiftLint’s static_over_final_class).

♻️ Proposed fix
-    `@objc`
-    class func controller() -> BuySellPortalViewController {
-        BuySellPortalViewController()
-    }
+    `@objc`
+    static func controller() -> BuySellPortalViewController {
+        BuySellPortalViewController()
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalViewController.swift around
lines 147 - 150, The factory method declaration uses `class func controller()`
inside the final `BuySellPortalViewController` — change it to `static func
controller()` to reflect that it cannot be overridden and satisfy SwiftLint's
`static_over_final_class`; update the method signature in the
`BuySellPortalViewController` type (the `controller()` factory method)
accordingly while leaving its implementation `BuySellPortalViewController()`
unchanged.
DashWallet/Sources/UI/Buy Sell/BuySellPortalScreen.swift (1)

32-32: Minor: place @Environment(\.colorScheme) on its own line.

SwiftLint’s attributes rule flags attributes with arguments on the same line as the declaration.

♻️ Proposed fix
-    `@Environment`(\.colorScheme) private var colorScheme
+    `@Environment`(\.colorScheme)
+    private var colorScheme

As per coding guidelines: "Use SwiftFormat and SwiftLint for Swift code formatting and linting as configured in .swiftformat and .swiftlint.yml files."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalScreen.swift at line 32, Move
the attribute onto its own line so it doesn't share the line with the
declaration: take the current `@Environment(\.colorScheme) private var
colorScheme` and split it into an attribute line (`@Environment(\.colorScheme)`)
above the property declaration (`private var colorScheme`) to satisfy
SwiftLint's `attributes` rule and match project formatting for the `colorScheme`
environment property.
DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift (2)

197-198: LazyVStack with a single-child TopIntro + conditional content.

Switching the outer stack to LazyVStack changes how SwiftUI measures/layouts the content (subviews are materialized lazily on scroll). Since this stack is already inside a ScrollView and the content is small/finite, VStack is typically sufficient and avoids subtle layout quirks (e.g., appearance animations firing on scroll, Spacer interactions). If the intent is just to defer the credits-warning overlay materialization, consider keeping VStack for the menu content and lazily loading only the heavy parts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift` around lines
197 - 198, The Menu view uses LazyVStack for the outer container (wrapping
TopIntro and conditional content) inside a ScrollView which can cause lazy
materialization/layout quirks; change the outer LazyVStack to a regular VStack
for the main menu content (e.g., replace LazyVStack around TopIntro and related
menu items) and keep lazy loading only for heavy/optional parts (like the
credits/warning overlay or any heavy subviews) by wrapping those specific
subviews in LazyVStack or on-demand views instead; update references to
LazyVStack in MainMenuViewController (around TopIntro) accordingly.

461-461: Inconsistent indentation for #if DASHPAY.

This #if DASHPAY (and its matching #endif on Line 509) sits at column 0, while the other conditional blocks in this struct (e.g., Lines 200, 274, 308, 346) are indented with the surrounding code. Align it with the rest of the file for consistency.

♻️ Proposed fix
-#if DASHPAY
+    `#if` DASHPAY
     private func showInvite() {

And match the #endif at Line 509.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift` at line 461,
The preprocessor directive `#if DASHPAY` and its matching `#endif` in
MainMenuViewController.swift are at column 0 while other conditional blocks in
the same struct are indented; move/indent the `#if DASHPAY` and corresponding
`#endif` to match the surrounding code indentation (same level as the other
`#if` blocks around lines with existing conditionals) so the conditional sits
within the struct's indentation and aligns with the surrounding code.
DashWallet/Sources/UI/SwiftUI Components/TopIntro.swift (1)

36-40: Asymmetric horizontal padding may truncate long localized subtitles.

The trailing padding (60) is 3× the leading padding (20), leaving a narrow content area on the right. Longer localized title/subtitle strings (e.g., German/French for "Find merchants that accept Dash…") could wrap awkwardly or look unbalanced. Consider whether this asymmetry is intentional (e.g., to leave room for a visual element) or if it should be symmetric.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/SwiftUI` Components/TopIntro.swift around lines 36 -
40, The asymmetric horizontal padding in the TopIntro view (.padding(.leading,
20) vs .padding(.trailing, 60)) can cause long localized subtitles to truncate
or wrap awkwardly; update the view modifiers in TopIntro to use symmetric
horizontal padding (e.g., replace the separate leading/trailing pads with a
single .padding(.horizontal, 20) or reduce .padding(.trailing) to 20) or, if the
extra trailing space is intentional for a visual element, add a clear comment
and reserve space responsively (e.g., using layout/Spacer or GeometryReader) so
localized strings aren’t truncated.
DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift (1)

144-155: Duplicated “show MerchantTypesDialog once” logic with ExploreViewController.

This is a near-verbatim copy of ExploreViewController.showWhereToSpendViewController() (Lines 164–175 of ExploreViewController.swift). If the UX rule changes (e.g., different detent, reset on version upgrade), both sites will need to be kept in sync. Consider extracting this to a small helper on MerchantTypesDialog or a free function, e.g. MerchantTypesDialog.presentIfNeeded(on:onContinue:).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Explore` Dash/ExploreMenuScreen.swift around lines 144
- 155, The showWhereToSpend duplication should be extracted into a single helper
to avoid drifting UX behavior: add a static helper like
MerchantTypesDialog.presentIfNeeded(on: UIViewController, detentHeight: CGFloat
= 640, onContinue: `@escaping` () -> Void) (or a free function) that checks
UserDefaults with kMerchantTypesShown, builds the UIHostingController(rootView:
MerchantTypesDialog { ... }), calls hostingController.setDetent(detentHeight),
presents it on the provided vc, and sets UserDefaults when the dialog continues;
then replace the body of ExploreMenuScreen.showWhereToSpend and
ExploreViewController.showWhereToSpendViewController with a single call to that
new helper (passing vc, detent, and showMerchants as onContinue).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@DashWallet/Sources/UI/Explore` Dash/ExploreMenuScreen.swift:
- Around line 22-45: ExploreMenuScreen currently holds a strong reference to the
UINavigationController via the stored property vc which creates a retain cycle
(nvc → UIHostingController → rootView → vc). Replace the strong reference by
wrapping the navigation controller in a weak-holder class (e.g. a small
WeakBox<T: AnyObject> with a weak var value: T?) and change the stored property
type from UINavigationController to WeakBox<UINavigationController> (or accept a
UINavigationController in init and store WeakBox(nvc)); update usages to access
vc.value to push/present, or alternatively remove vc entirely and pass only
navigation action closures
(onShowSendPayment/onShowReceivePayment/onShowGiftCard) as done elsewhere
(adjust MainTabbarController.configureControllers(),
MainMenuViewController.showExplore(), and HomeViewController+Shortcuts.swift to
construct the view with the new API).
- Line 185: The division operators in ExploreMenuScreen.systemGreen (private
static let systemGreen) lack surrounding whitespace; update the numeric
expressions to use spaces (e.g., 98 / 255, 182 / 255, 125 / 255) so they comply
with SwiftLint operator_usage_whitespace, then run the project's
SwiftFormat/SwiftLint formatter to apply and verify the change.
- Around line 18-21: Reorder and tidy the import block in
ExploreMenuScreen.swift: place import SwiftUI before import UIKit (to satisfy
SwiftLint sorted_imports) and remove the stray blank line after the imports so
the top-level declaration immediately follows the imports.
- Around line 184-207: The APY formatter in CrowdNodeAPYBadge currently sets
NumberFormatter.multiplier = 1 which causes a percent-style formatter to
multiply an already-percent value (CrowdNode.shared.crowdnodeAPY) again; change
the multiplier to 0.01 inside the apy computed property so the percent value
(e.g., 7.5) is converted to decimal (0.075) before formatting; update the same
change in the analogous formatter in CrowdNodeAPYView (the NumberFormatter setup
where multiplier is set) to ensure consistent percent display.

In `@DashWallet/Sources/UI/Main/MainTabbarController.swift`:
- Around line 228-239: The code instantiates BaseNavigationController() which
bypasses the customizations in
BaseNavigationController.init(rootViewController:) (the
interactivePopGestureRecognizer delegate and tintColor). Replace the call to
BaseNavigationController() so the navigation controller is created with the
proper initializer: either call BaseNavigationController(rootViewController:
UIHostingController(rootView: exploreScreen)) or add and use a convenience
BaseNavigationController initializer that accepts the SwiftUI view (or a
UIViewController) and performs the hosting-controller wrap plus the existing
setup; ensure ExploreMenuScreen is wrapped into a UIHostingController and passed
into BaseNavigationController's init(rootViewController:) so the gesture
delegate and navigationBar.tintColor are applied.

---

Outside diff comments:
In `@DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift`:
- Around line 514-557: Remove dead explore delegation: delete
ExploreViewControllerDelegate conformance from the DelegateInternal class and
remove the three unused forwarding methods
exploreViewControllerShowSendPayment(_:),
exploreViewControllerShowReceivePayment(_:), and
exploreViewControllerShowGiftCard(_:txId:) from DelegateInternal; also delete
the ExploreViewControllerDelegate protocol definition and the now-unused `@objc`
weak var delegate property on ExploreViewController, since showExplore() now
uses ExploreMenuScreen closure callbacks instead of ExploreViewController
delegation.

---

Duplicate comments:
In `@DashWallet/Sources/UI/Main/MainTabbarController.swift`:
- Line 239: Replace the bare UIHostingController(rootView: exploreScreen) with a
thin hosting-controller subclass (e.g., ExploreMenuHostingController) that wraps
the ExploreMenuScreen and conforms to the navigation protocols used by
BaseNavigationController (NavigationBarDisplayable /
NavigationStackControllable) so BaseNavigationController.willShow can apply its
navigation-bar handling; follow the existing pattern used by
SelectCoinHostingController and MayaPortalViewController by creating
ExploreMenuHostingController that hosts the SwiftUI view and use
nvc.viewControllers = [ExploreMenuHostingController(rootView: exploreScreen)].

---

Nitpick comments:
In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalScreen.swift:
- Line 32: Move the attribute onto its own line so it doesn't share the line
with the declaration: take the current `@Environment(\.colorScheme) private var
colorScheme` and split it into an attribute line (`@Environment(\.colorScheme)`)
above the property declaration (`private var colorScheme`) to satisfy
SwiftLint's `attributes` rule and match project formatting for the `colorScheme`
environment property.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalViewController.swift:
- Around line 147-150: The factory method declaration uses `class func
controller()` inside the final `BuySellPortalViewController` — change it to
`static func controller()` to reflect that it cannot be overridden and satisfy
SwiftLint's `static_over_final_class`; update the method signature in the
`BuySellPortalViewController` type (the `controller()` factory method)
accordingly while leaving its implementation `BuySellPortalViewController()`
unchanged.

In `@DashWallet/Sources/UI/Explore` Dash/ExploreMenuScreen.swift:
- Around line 144-155: The showWhereToSpend duplication should be extracted into
a single helper to avoid drifting UX behavior: add a static helper like
MerchantTypesDialog.presentIfNeeded(on: UIViewController, detentHeight: CGFloat
= 640, onContinue: `@escaping` () -> Void) (or a free function) that checks
UserDefaults with kMerchantTypesShown, builds the UIHostingController(rootView:
MerchantTypesDialog { ... }), calls hostingController.setDetent(detentHeight),
presents it on the provided vc, and sets UserDefaults when the dialog continues;
then replace the body of ExploreMenuScreen.showWhereToSpend and
ExploreViewController.showWhereToSpendViewController with a single call to that
new helper (passing vc, detent, and showMerchants as onContinue).

In `@DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift`:
- Around line 197-198: The Menu view uses LazyVStack for the outer container
(wrapping TopIntro and conditional content) inside a ScrollView which can cause
lazy materialization/layout quirks; change the outer LazyVStack to a regular
VStack for the main menu content (e.g., replace LazyVStack around TopIntro and
related menu items) and keep lazy loading only for heavy/optional parts (like
the credits/warning overlay or any heavy subviews) by wrapping those specific
subviews in LazyVStack or on-demand views instead; update references to
LazyVStack in MainMenuViewController (around TopIntro) accordingly.
- Line 461: The preprocessor directive `#if DASHPAY` and its matching `#endif`
in MainMenuViewController.swift are at column 0 while other conditional blocks
in the same struct are indented; move/indent the `#if DASHPAY` and corresponding
`#endif` to match the surrounding code indentation (same level as the other
`#if` blocks around lines with existing conditionals) so the conditional sits
within the struct's indentation and aligns with the surrounding code.

In `@DashWallet/Sources/UI/SwiftUI` Components/MenuItem.swift:
- Line 36: Remove explicit "= nil" initializers from optional stored properties
in the MenuItem declaration (e.g., trailingView: AnyView?) to satisfy
SwiftLint's implicit_optional_initialization rule; locate the optional
properties in MenuItem.swift (the trailingView property and the other optional
siblings in the same declaration block) and change declarations like
"trailingView: AnyView? = nil" to "trailingView: AnyView?" without altering
types or behavior.

In `@DashWallet/Sources/UI/SwiftUI` Components/TopIntro.swift:
- Around line 36-40: The asymmetric horizontal padding in the TopIntro view
(.padding(.leading, 20) vs .padding(.trailing, 60)) can cause long localized
subtitles to truncate or wrap awkwardly; update the view modifiers in TopIntro
to use symmetric horizontal padding (e.g., replace the separate leading/trailing
pads with a single .padding(.horizontal, 20) or reduce .padding(.trailing) to
20) or, if the extra trailing space is intentional for a visual element, add a
clear comment and reserve space responsively (e.g., using layout/Spacer or
GeometryReader) so localized strings aren’t truncated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a0d60fc-e05a-43d9-8373-a9acd8d69db3

📥 Commits

Reviewing files that changed from the base of the PR and between 989d1ea and c8575ef.

📒 Files selected for processing (10)
  • DashWallet/Sources/UI/Buy Sell/BuySellPortalScreen.swift
  • DashWallet/Sources/UI/Buy Sell/BuySellPortalViewController.swift
  • DashWallet/Sources/UI/Buy Sell/Model/BuySellPortalModel.swift
  • DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift
  • DashWallet/Sources/UI/Explore Dash/ExploreViewController.swift
  • DashWallet/Sources/UI/Main/MainTabbarController.swift
  • DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift
  • DashWallet/Sources/UI/SwiftUI Components/MenuItem.swift
  • DashWallet/Sources/UI/SwiftUI Components/TopIntro.swift
  • DashWallet/en.lproj/Localizable.strings
✅ Files skipped from review due to trivial changes (1)
  • DashWallet/en.lproj/Localizable.strings

Comment thread DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift Outdated
Comment thread DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift
Comment thread DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift
Comment thread DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift Outdated
Comment thread DashWallet/Sources/UI/Main/MainTabbarController.swift
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@HashEngineering HashEngineering left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@HashEngineering HashEngineering merged commit ed31841 into master Apr 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants