Skip to content

fix(scan): keep the scanner escapable when the camera is refused (MO-1016) - #1550

Open
HashEngineering wants to merge 3 commits into
masterfrom
fix/scan-close-button-camera-disabled
Open

fix(scan): keep the scanner escapable when the camera is refused (MO-1016)#1550
HashEngineering wants to merge 3 commits into
masterfrom
fix/scan-close-button-camera-disabled

Conversation

@HashEngineering

@HashEngineering HashEngineering commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Fixes MO-1016no X button on some devices when scan QR code with disabled Camera.

The bug

ScanActivity supports a circular-reveal open animation. When it is launched with getTransitionIntent, onCreate sets contentView.alpha = 0f over a transparent window and leaves it that way until maybeTriggerSceneTransition() runs — and that call lives inside openRunnable, the successful camera-open path. Refuse the camera and it never runs, so the whole scanner stays invisible: an all-black screen with no close button behind the warning dialog, and after dismissing it no way out but restarting the wallet.

The main screen was never affected because WalletFragment uses the plain getIntent (changed incidentally in #1385), and neither is the lock screen's Scan to Send. That is what QA meant by "already fixed on the main screen". The five remaining call sites all use the transition and all carry the bug:

  • SendCoinsQrActivity
  • PaymentsPayFragment
  • AddressInputFragment
  • MayaAddressInputFragment
  • DEXRefundAddressFragment

It is only noticeable on devices without a navigation bar, where there is no on-screen Back button to escape with — hence the report.

So the fix belongs in ScanActivity, not at the call sites.

The changes

1. fix(scan): keep the close button reachable when the camera is refused

Both warning dialogs now reveal the content over an opaque background first, and tint the close icon white so it is legible there — ic_nav_bar_close is nearly black, which works over the camera preview but not over the empty background that replaces it. The transition setup is skipped if the camera was already refused before the first draw, so it cannot hide the content again.

2. fix: repair the build broken by #1542unrelated, see note below

3. fix(scan): give the camera-permission dialog somewhere to go

The X is an escape hatch, not a resolution. The dialog was a dead end — one Dismiss button that dropped the user back onto a scanner that still could not scan — and on a permanent refusal the system never prompts again, so there was no in-app route back at all.

  • Dismissing either warning now leaves the scanner. It has one job and cannot do it, and every caller offers another way in (typing an address, pasting one, picking a contact). The close button stays as the safety net.
  • The permission dialog gained a primary action, keyed off shouldShowRequestPermissionRationale: after a first refusal it is Allow, which re-asks in-app; once the system will no longer prompt it becomes Settings, opening App info through the existing Context.openAppSettings(). onResume() already opens the camera on the way back, so granting it there lands the user in a working scanner with no extra code. This mirrors the location-permission flow in exploredash.
  • The message follows the same split, since telling someone to grant a permission they can no longer be asked for is a dead end of its own.
  • Drive-by: the camera-problem dialog was titled "Camera permission", which it is not. It now uses scan_camera_problem_dialog_title ("Sorry"), a string already present and unused.

Three new English strings: button_not_now, button_settings, scan_camera_permission_denied_dialog_message.

Please look at this separately

master does not compile as of 74bd90a (#1542)MenuItem.kt imports Configuration twice (ambiguous import, :common fails), and MayaAddressInputScreen passes subtitleMaxLines = 1 to ActionItem, which has no such parameter (:integrations:maya fails). Nothing builds without the repair, so it is carried here as its own commit. ActionItem already renders its subtitle at maxLines = 1 and both call sites also pass subtitleMiddleEllipsis = true, so the argument was redundant and dropping it preserves the rendering. Worth landing on its own, ahead of this PR.

Testing

Verified on an API 34 emulator with gesture navigation (no navigation bar), driving ScanActivity directly with the transition extras.

Reproduced the original bug on master first: after Don't allow → Dismiss, a fully black screen with no X, ScanActivity still the resumed activity.

With the fix:

Action Result
Refuse once Dialog shows original copy, Allow / Not now; X visible behind it
Tap Allow System prompt re-appears
Refuse again Copy switches to the Settings wording, Settings / Not now
Tap Settings App info opens on the right app
Grant there, press back Lands straight in a live camera preview
Tap Not now Scanner closes, returns to the caller
Tap the X Scanner closes, returns to the caller

Left alone

The camera-problem copy still reads "The camera has a problem. You probably need to restart the device.", which sits oddly on a single button that now closes the screen. Rewording needs a translated string and a product opinion, so I left it.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved scanner behavior when camera access is denied or unavailable.
    • Added clearer guidance for enabling camera permission through system settings.
    • Scanner warnings now provide reliable options to retry, open settings, or exit.
  • UI Improvements

    • Added a clear unavailable-camera screen with a close button.
    • Allowed longer “Paste address from” descriptions to wrap across multiple lines.

HashEngineering and others added 3 commits August 26, 2026 14:42
… (MO-1016)

When ScanActivity is opened with a circular-reveal transition — every entry
point except the main screen and the lock screen's Scan to Send — onCreate
sets the content view to alpha 0 over a transparent window and leaves it
that way until maybeTriggerSceneTransition() runs. That call sits on the
successful camera-open path, so denying the camera (once or permanently),
or a device that refuses to hand the camera over, leaves the scanner fully
invisible: an all-black screen with no close button behind the warning
dialog, and after dismissing it no way out but restarting the wallet. It is
only noticeable on devices without a navigation bar, where there is no
on-screen Back button to escape with.

Both warning dialogs now reveal the content over an opaque background
first, and tint the close icon white so it is legible there (it is nearly
black, which only works over the camera preview). The transition setup is
skipped if the camera was already refused before the first draw, so it
cannot hide the content again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
master does not compile as of 74bd90a:

- MenuItem.kt imports android.content.res.Configuration twice, which
  Kotlin rejects as an ambiguous import, so :common fails to build.
- MayaAddressInputScreen passes subtitleMaxLines = 1 to ActionItem, which
  has no such parameter, so :integrations:maya fails to build. ActionItem
  already renders its subtitle at maxLines = 1, and both call sites also
  pass subtitleMiddleEllipsis = true (single-line by construction), so the
  argument was redundant — dropping it preserves the rendering.

Unrelated to MO-1016; carried here only because nothing builds without it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The warning dialog was a dead end: one Dismiss button that dropped the user
back onto a scanner that still could not scan. Worse, on a permanent refusal
the system never prompts again, so there was no in-app route back at all —
the user had to know to go find App info themselves.

- Dismissing either warning now leaves the scanner. It has one job and
  cannot do it, and every caller offers another way in (typing an address,
  pasting one, picking a contact), so hand the user back instead of parking
  them on a dead preview. The close button stays as the safety net.
- The permission dialog gained a primary action. After a first refusal that
  is Allow, which re-asks in-app; once shouldShowRequestPermissionRationale
  reports the system will not prompt again it becomes Settings, opening App
  info via the existing openAppSettings() helper. onResume() already opens
  the camera on the way back, so granting it there lands the user in a
  working scanner with no extra code. This mirrors the location-permission
  flow in exploredash.
- The message follows the same split, since telling someone to grant a
  permission they can no longer be asked for is a dead end of its own.
- The camera-problem dialog was titled "Camera permission", which it is not.
  It now uses scan_camera_problem_dialog_title ("Sorry"), a string that was
  already present and unused.

Verified on an API 34 emulator: refuse once -> Allow re-prompts; refuse
again -> copy switches and Settings opens App info; granting there returns
straight to a live preview; Not now closes the scanner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bd8df8b8-0234-47f5-afab-7a855b0b47cb

📥 Commits

Reviewing files that changed from the base of the PR and between 74bd90a and edb55d9.

📒 Files selected for processing (4)
  • common/src/main/java/org/dash/wallet/common/ui/components/MenuItem.kt
  • common/src/main/java/org/dash/wallet/common/ui/scan/ScanActivity.kt
  • common/src/main/res/values/strings.xml
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaAddressInputScreen.kt
💤 Files with no reviewable changes (2)
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaAddressInputScreen.kt
  • common/src/main/java/org/dash/wallet/common/ui/components/MenuItem.kt

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The scanner now handles unavailable cameras and denied permissions with fallback UI, permission recovery actions, settings access, and canceled exit behavior. New strings support these dialogs. Unrelated UI cleanup removes an import and allows address subtitles to wrap.

Changes

Scanner availability flow

Layer / File(s) Summary
Camera fallback and permission recovery
common/src/main/java/org/dash/wallet/common/ui/scan/ScanActivity.kt, common/src/main/res/values/strings.xml
ScanActivity shows an opaque fallback view when the camera is unavailable. Permission dialogs now re-request access or open app settings. Dialog dismissal finishes the scanner with RESULT_CANCELED.
Related UI adjustments
common/src/main/java/org/dash/wallet/common/ui/components/MenuItem.kt, integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaAddressInputScreen.kt
Removes an unused import and allows address subtitles to wrap across multiple lines.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to edb55

The change restores an accessible exit and permission-recovery flow when camera access is unavailable, while preserving normal scan results and app boundaries; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant ScanActivity
  participant AndroidPermissionSystem
  participant AppSettings
  ScanActivity->>AndroidPermissionSystem: Request camera permission
  AndroidPermissionSystem-->>ScanActivity: Permission refused or camera unavailable
  ScanActivity->>ScanActivity: Show unavailable-camera UI and warning dialog
  ScanActivity->>AndroidPermissionSystem: Request permission again
  AndroidPermissionSystem-->>ScanActivity: Permission remains denied
  ScanActivity->>AppSettings: Open application settings
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files. (1 skipped: 1… 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 clearly and concisely describes the main change: keeping the scanner escapable when camera access is refused. It matches the pull request objectives and changes in ScanActivity.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/scan-close-button-camera-disabled

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.

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.

1 participant