Skip to content

[PM-39348] feat: Redesign Premium upgrade screen with benefit-led messaging#2884

Open
KatherineInCode wants to merge 15 commits into
mainfrom
pm-39348/premium-upgrade-sheet
Open

[PM-39348] feat: Redesign Premium upgrade screen with benefit-led messaging#2884
KatherineInCode wants to merge 15 commits into
mainfrom
pm-39348/premium-upgrade-sheet

Conversation

@KatherineInCode

@KatherineInCode KatherineInCode commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

PM-39348

📔 Objective

Redesigns the Premium upgrade screen with benefit-led messaging, matching the Figma design and the Android implementation (PM-39349): a hero illustration, a benefit-oriented headline ("Unlock advanced protection"), an expanded 8-item benefit checklist, and updated CTA/price copy. This is a content-only redesign — no changes to presentation, navigation, or the underlying upgrade flow.

Also fixes a VoiceOver gap on this screen and in PremiumPlanView: price/cadence text like "$1.65 / month" was being read literally (e.g. "dollar sign 1.65 slash month") instead of naturally. Adds VoiceOver-specific localization strings for these cases, separate from the displayed text.

📸 Screenshots

The redesigned Premium upgrade screen: a hero illustration of password security features, the headline "Unlock advanced protection," an eight-item benefit checklist, the "Upgrade to Premium" button, "$1.65 / month · Cancel anytime" pricing text, and a note that checkout completes via Stripe.

…saging

Replace the generic "unlock advanced features" copy with a hero
illustration, a benefit-oriented headline, and an expanded 8-item
benefit list, matching the Android implementation and Figma design.

Extends the shared IllustratedMessageView component (aspect-ratio-aware
image sizing, optional message text, and baked-in accessibilityHidden
on the illustration) rather than working around it, since none of its
existing ~17 call sites hide their decorative image from VoiceOver
today. Adds a new sibling PremiumUpgradeBenefitsList component instead
of modifying the existing PremiumFeaturesList, since that component is
also used by PremiumPlanView's unrelated canceled/expired-plan
reminder.
Compose the price/cadence/cancel-anytime text from the existing
generic XAmountPerCadence and a new XCancelAnytime string instead of
concatenating separately-localized fragments with hardcoded spacing,
so word order and spacing are translator-controlled. Left-align
benefit rows that wrap to multiple lines (previously inherited center
alignment from IllustratedMessageView). Remove the redundant corner
clip on the card background and add bottom padding to match Figma.
The CTA button already reads "Upgrade to Premium"; repeating it in
the nav title was redundant. Update both the push path's title
(BillingCoordinator) and the modal path's title (PremiumUpgradeView's
own navigationBar modifier), since they're set independently.
…ageView

Stop reusing IllustratedMessageView for the hero card: its landscape
side-by-side layout didn't read well here, and building it locally
keeps this screen free to control alignment without touching a
component shared by 19 other screens. The card is now a plain
vertical stack in both orientations. Remove the now-unused
.premiumHero style and its supporting constants from
IllustratedMessageView; keep its other changes (aspect-ratio-aware
image sizing, optional message, baked-in accessibilityHidden), since
those still benefit its other call sites independent of this screen.
It's purely decorative, same treatment as the hero illustration.
@github-actions github-actions Bot added app:password-manager Bitwarden Password Manager app context app:authenticator Bitwarden Authenticator app context t:feature labels Jul 15, 2026
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.94737% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 79.00%. Comparing base (dae7094) to head (1ede290).

Files with missing lines Patch % Lines
...hared/UI/Billing/PremiumPlan/PremiumPlanView.swift 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2884      +/-   ##
==========================================
- Coverage   81.25%   79.00%   -2.26%     
==========================================
  Files        1028     1152     +124     
  Lines       66164    73521    +7357     
==========================================
+ Hits        53761    58084    +4323     
- Misses      12403    15437    +3034     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Per design: VoiceOver should read "per month"/"per year" rather than
the raw "/" character, and a comma instead of "·" before "Cancel
anytime". Applies to both the Premium upgrade screen's price line and
PremiumPlanView's billing amount row, since both use the same
XAmountPerCadence pattern and had the identical gap.

Adds PerMonthAccessibilityLabel/PerYearAccessibilityLabel and a
PlanCadenceType.accessibilityLabel counterpart to the existing
label property, plus XCancelAnytimeAccessibilityLabel for the
upgrade screen's "Cancel anytime" suffix. billingRow gains an
optional valueAccessibilityLabel parameter, defaulting to the
existing behavior for its other three callers.
Localization keys are translator-facing, so name them for what
they're for (VoiceOver) rather than the SwiftUI API that consumes
them. The Swift-side property/parameter names (accessibilityLabel,
priceCancelAnytimeAccessibilityLabel, valueAccessibilityLabel) stay
as-is, since those are programmer-facing and match the
.accessibilityLabel() API they call.
One line each, describing placement or translation constraints where
they exist. Checked against the clients repo and existing iOS strings
before adding any constraint claims: dropped notes on "2FA" and
"24/7" since neither has real precedent either way and both are
self-evident enough not to need one; kept the "Send" note since that
feature name reliably goes uncommented elsewhere only in contexts
where it's a more obvious proper noun (e.g. "About Send") than it is
here.
PremiumPlanState.totalLabel uses the same XAmountPerCadence pattern
as billingAmount, so it had the identical "/" pronunciation gap. Adds
totalLabelAccessibilityLabel following the same pattern.
… tests

Rolled back the aspect-ratio sizing and optional message parameter since
the Premium upgrade screen no longer uses this component, keeping only
the accessibilityHidden(true) fix on the illustration image. Also added
coverage for the image, title, and message rendering.
@KatherineInCode
KatherineInCode marked this pull request as ready for review July 16, 2026 20:15
@KatherineInCode
KatherineInCode requested review from a team and matt-livefront as code owners July 16, 2026 20:15
@KatherineInCode KatherineInCode added the ai-review Request a Claude code review label Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the Premium upgrade screen redesign (PremiumUpgradeView) and its accompanying VoiceOver accessibility improvements across PremiumPlanView, PremiumUpgradeState, PremiumPlanState, PlanCadenceType, and IllustratedMessageView. Confirmed the removed localization keys and the old PremiumFeaturesList view are still correctly retained where referenced, that decorative images are hidden from VoiceOver, and that new accessibility labels fall back safely to displayed text. Patch coverage is high (98.9%) with new unit and view-inspector tests covering the new state computed properties and view content.

Code Review Details

No blocking findings.

Notes verified during review:

  • Removed keys (UpgradeNow, UnlockMoreAdvancedFeaturesWithPremiumPlan, YoullGoToStripe...) are no longer referenced in Swift; remaining hits are Crowdin-managed locale files.
  • PremiumFeaturesList is intentionally kept — still used by PremiumPlanView.
  • billingRow accessibility label change preserves prior behavior via valueAccessibilityLabel ?? value; billing rows only render inside the .data loading state, so empty-subscription labels are not surfaced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review app:authenticator Bitwarden Authenticator app context app:password-manager Bitwarden Password Manager app context t:feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants