Skip to content

Conversation

@iammajid
Copy link
Contributor

Adds a visual indicator for users with a lifetime license in the Settings screen. Users with an active lifetime purchase now see a "Full Version" label with a checkmark, confirming their premium status. Additionally, the "Restore Purchase" button has been removed for subscription users as they already have active premium access.

@coderabbitai
Copy link

coderabbitai bot commented Nov 11, 2025

Walkthrough

Reorders settings sections so purchaseStatusSection appears before cloudServiceSection when the user does not have full access. SettingsViewModel now builds sections dynamically, introduces hasFullAccess, adds getFooterTitle(for:) (returns a footer for the about section only when hasFullAccess), and exposes purchaseStatusCellViewModel whose subtitle varies by trialExpirationDate (formatted) or a free-tier subtitle. Adds PurchaseStatusCell (UITableViewCell) with Combine bindings and reuse cleanup, and PurchaseStatusCellViewModel (bindable title/subtitle and iconName). SettingsViewController treats taps on PurchaseStatusCellViewModel as a trigger to show the unlock/full-version flow. Adds three localization keys and updates the project file to include the two new Swift sources.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify SettingsViewModel: dynamic section construction, hasFullAccess logic, getFooterTitle(for:), ordering change, and aboutSectionElements type change.
  • Review PurchaseStatusCell: Combine subscriptions, removeAllBindings() on reuse, layout constraints, multiline subtitle behavior, and selection/accessory setup.
  • Review PurchaseStatusCellViewModel: correct cellType mapping and Bindable usage for title/subtitle.
  • Inspect SettingsViewController didSelectRow handling to ensure selecting the purchase cell triggers the unlock flow and does not interfere with existing cases.
  • Check Localizable.strings keys and trialExpirationDate formatting usage.
  • Confirm pbxproj entries correctly add the new files to all intended targets and groups.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: adding a visual purchase status indicator in the Settings screen.
Description check ✅ Passed The description is directly related to the changeset, explaining the addition of purchase status visibility and removal of restore purchase button for subscriptions.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/purchase-status

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e48f43 and 7098fe5.

📒 Files selected for processing (1)
  • Cryptomator.xcodeproj/project.pbxproj (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: iammajid
Repo: cryptomator/ios PR: 426
File: Cryptomator/Settings/SettingsViewModel.swift:73-90
Timestamp: 2025-11-11T11:58:54.204Z
Learning: In SettingsViewModel.swift (Cryptomator iOS app), when displaying subscription/purchase status in the aboutSectionElements: subscriptions take precedence over lifetime licenses. If both hasRunningSubscription and fullVersionUnlocked are true (an edge case that shouldn't happen in practice), only the "Manage Subscriptions" button should be shown, not the "Full Version" status cell. This is intentional design behavior.
🔇 Additional comments (1)
Cryptomator.xcodeproj/project.pbxproj (1)

443-444: New PurchaseStatus cell files are correctly wired into the main app target

PurchaseStatusCell.swift and PurchaseStatusCellViewModel.swift have matching PBXFileReference and PBXBuildFile entries, are placed under the Settings group, and are added only to the main Cryptomator target’s Sources phase. This cleanly integrates the new settings UI without leaking it into tests or extensions.

Also applies to: 1068-1069, 2053-2054, 2814-2815

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

Copy link

@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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc04fa5 and 22a98a5.

📒 Files selected for processing (2)
  • Cryptomator/Settings/SettingsViewModel.swift (1 hunks)
  • SharedResources/en.lproj/Localizable.strings (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Cryptomator/Settings/SettingsViewModel.swift (5)
Cryptomator/Common/Cells/ButtonCellViewModel.swift (1)
  • createDisclosureButton (19-21)
Cryptomator/Settings/SettingsCoordinator.swift (3)
  • showAbout (31-35)
  • showManageSubscriptions (85-97)
  • showUnlockFullVersion (79-83)
CryptomatorCommon/Sources/CryptomatorCommonCore/LocalizedString.swift (1)
  • getValue (12-22)
Cryptomator/Purchase/IAPViewController.swift (1)
  • restorePurchase (277-290)
Cryptomator/Purchase/PurchaseViewController.swift (1)
  • restorePurchase (55-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and test (freemium)
🔇 Additional comments (2)
SharedResources/en.lproj/Localizable.strings (1)

216-216: LGTM! Localization key follows conventions and uses consistent terminology.

The new localization string is well-formed and appropriately placed within the Settings section. The key name "fullVersionStatus" clearly indicates its purpose, and "Full Version" matches the terminology used throughout the app.

Cryptomator/Settings/SettingsViewModel.swift (1)

79-84: Status cell implementation is correct.

The localization key "settings.fullVersionStatus" exists in the English localization file with the value "Full Version". The cell implementation is appropriate, using BindableTableViewCellViewModel with selectionStyle: .none and accessoryType: .checkmark to correctly represent a non-interactive status indicator.

Copy link

@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: 1

🧹 Nitpick comments (4)
Cryptomator/Settings/SettingsViewController.swift (1)

103-141: Simplify didSelectRow logic by handling PurchaseStatusCellViewModel up front

Functionally this works (purchase status row deselects and triggers showUnlockFullVersion()), but you now:

  • Call dataSource?.itemIdentifier(for:) twice, and
  • Potentially deselect the same row twice for PurchaseStatusCellViewModel.

You can keep behavior while tightening the code by handling the purchase‑status case first and reusing the same item identifier, e.g.:

 override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-    let cellViewModel = dataSource?.itemIdentifier(for: indexPath) as? ButtonCellViewModel<SettingsButtonAction>
+    let item = dataSource?.itemIdentifier(for: indexPath)
+
+    if item is PurchaseStatusCellViewModel {
+        tableView.deselectRow(at: indexPath, animated: true)
+        coordinator?.showUnlockFullVersion()
+        return
+    }
+
+    let cellViewModel = item as? ButtonCellViewModel<SettingsButtonAction>
@@
-    switch cellViewModel?.action {
+    switch cellViewModel?.action {
         // existing cases…
     }
-
-    if dataSource?.itemIdentifier(for: indexPath) is PurchaseStatusCellViewModel {
-        tableView.deselectRow(at: indexPath, animated: true)
-        coordinator?.showUnlockFullVersion()
-    }
 }

Keeps the UX identical while reducing lookups and local complexity in this already large delegate method.

Cryptomator/Settings/PurchaseStatusCellViewModel.swift (1)

19-23: Consider using non-optional Bindable types for title and subtitle.

The initializer accepts non-optional String parameters for title and subtitle (line 19), but wraps them in Bindable<String?> (lines 16-17). Since these values are always present, using Bindable<String> instead would eliminate unnecessary optionality and make the code more type-safe.

Apply this diff to use non-optional Bindable types:

-	let title: Bindable<String?>
-	let subtitle: Bindable<String?>
+	let title: Bindable<String>
+	let subtitle: Bindable<String>
Cryptomator/Settings/SettingsViewModel.swift (1)

103-118: Cache the DateFormatter to improve performance.

The DateFormatter is created on every access to purchaseStatusCellViewModel (lines 106-108). Since DateFormatter creation is relatively expensive and this computed property is accessed whenever sections are rebuilt, consider caching the formatter as a static property or lazy var.

Add a cached date formatter at the class level:

+	private static let expirationDateFormatter: DateFormatter = {
+		let formatter = DateFormatter()
+		formatter.dateStyle = .medium
+		formatter.timeStyle = .none
+		return formatter
+	}()
+
 	private var purchaseStatusCellViewModel: PurchaseStatusCellViewModel {
 		let subtitle: String
 		if let trialExpirationDate = cryptomatorSettings.trialExpirationDate, trialExpirationDate > Date() {
-			let dateFormatter = DateFormatter()
-			dateFormatter.dateStyle = .medium
-			dateFormatter.timeStyle = .none
-			subtitle = String(format: LocalizedString.getValue("settings.trial.expirationDate"), dateFormatter.string(from: trialExpirationDate))
+			subtitle = String(format: LocalizedString.getValue("settings.trial.expirationDate"), Self.expirationDateFormatter.string(from: trialExpirationDate))
 		} else {
 			subtitle = LocalizedString.getValue("settings.freeTier.subtitle")
 		}
Cryptomator/Settings/PurchaseStatusCell.swift (1)

30-66: LGTM! Layout implementation is clean and accessible.

The Auto Layout setup is well-structured with:

  • Proper use of layout margins
  • Multi-line label support for dynamic type
  • Appropriate spacing and sizing

One optional enhancement: consider setting isAccessibilityElement = true and providing a combined accessibility label that includes the icon meaning, title, and subtitle for VoiceOver users.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22a98a5 and e94263d.

📒 Files selected for processing (6)
  • Cryptomator.xcodeproj/project.pbxproj (4 hunks)
  • Cryptomator/Settings/PurchaseStatusCell.swift (1 hunks)
  • Cryptomator/Settings/PurchaseStatusCellViewModel.swift (1 hunks)
  • Cryptomator/Settings/SettingsViewController.swift (1 hunks)
  • Cryptomator/Settings/SettingsViewModel.swift (4 hunks)
  • SharedResources/en.lproj/Localizable.strings (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: iammajid
Repo: cryptomator/ios PR: 426
File: Cryptomator/Settings/SettingsViewModel.swift:73-90
Timestamp: 2025-11-11T11:58:54.204Z
Learning: In SettingsViewModel.swift (Cryptomator iOS app), when displaying subscription/purchase status in the aboutSectionElements: subscriptions take precedence over lifetime licenses. If both hasRunningSubscription and fullVersionUnlocked are true (an edge case that shouldn't happen in practice), only the "Manage Subscriptions" button should be shown, not the "Full Version" status cell. This is intentional design behavior.
📚 Learning: 2025-11-11T11:58:54.204Z
Learnt from: iammajid
Repo: cryptomator/ios PR: 426
File: Cryptomator/Settings/SettingsViewModel.swift:73-90
Timestamp: 2025-11-11T11:58:54.204Z
Learning: In SettingsViewModel.swift (Cryptomator iOS app), when displaying subscription/purchase status in the aboutSectionElements: subscriptions take precedence over lifetime licenses. If both hasRunningSubscription and fullVersionUnlocked are true (an edge case that shouldn't happen in practice), only the "Manage Subscriptions" button should be shown, not the "Full Version" status cell. This is intentional design behavior.

Applied to files:

  • Cryptomator/Settings/PurchaseStatusCellViewModel.swift
  • Cryptomator/Settings/SettingsViewController.swift
  • Cryptomator/Settings/PurchaseStatusCell.swift
  • Cryptomator/Settings/SettingsViewModel.swift
📚 Learning: 2024-10-10T15:32:49.838Z
Learnt from: tobihagemann
Repo: cryptomator/ios PR: 384
File: FileProviderExtensionUI/FileProviderCoordinator.swift:85-86
Timestamp: 2024-10-10T15:32:49.838Z
Learning: In `ReauthenticationViewController.swift`, the `coordinator` property is declared as `weak`, preventing retain cycles with `FileProviderCoordinator`.

Applied to files:

  • Cryptomator/Settings/SettingsViewController.swift
🧬 Code graph analysis (3)
Cryptomator/Settings/SettingsViewController.swift (2)
Cryptomator/Purchase/PurchaseViewController.swift (2)
  • tableView (31-41)
  • tableView (43-53)
Cryptomator/Settings/SettingsCoordinator.swift (1)
  • showUnlockFullVersion (79-83)
Cryptomator/Settings/PurchaseStatusCell.swift (1)
Cryptomator/Common/Combine/Publisher+OptionalAssign.swift (1)
  • assign (13-17)
Cryptomator/Settings/SettingsViewModel.swift (4)
Cryptomator/Common/TableViewModel.swift (1)
  • getFooterTitle (24-26)
CryptomatorCommon/Sources/CryptomatorCommonCore/LocalizedString.swift (1)
  • getValue (12-22)
Cryptomator/Purchase/PremiumManager.swift (3)
  • trialExpirationDate (50-52)
  • trialExpirationDate (119-127)
  • trialExpirationDate (168-170)
Cryptomator/Common/Cells/ButtonCellViewModel.swift (1)
  • createDisclosureButton (19-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and test (premium)
🔇 Additional comments (6)
Cryptomator.xcodeproj/project.pbxproj (1)

442-443: Purchase status cell + view model are correctly wired into the main app target

The new PurchaseStatusCell.swift and PurchaseStatusCellViewModel.swift are properly added to:

  • PBXFileReference (files exist in the Settings folder),
  • PBXBuildFile (compile sources entries),
  • the Settings group hierarchy, and
  • the Cryptomator target’s Sources build phase.

No config or target‑membership issues stand out.

Also applies to: 1063-1064, 2044-2045, 2792-2793

SharedResources/en.lproj/Localizable.strings (1)

216-218: LGTM! Clear and well-structured localization strings.

The three new localization keys follow the existing naming convention and provide clear, user-friendly messages for the purchase status feature. The date format specifier on line 217 is correctly used.

Cryptomator/Settings/SettingsViewModel.swift (4)

52-77: LGTM! Dynamic section generation logic is clean and correct.

The conditional inclusion of the purchase status section based on hasFullAccess is well-implemented and aligns with the PR objectives.


79-82: LGTM! Footer title logic is correct.

The implementation properly returns the footer text only for the about section when the user has full access.


84-86: LGTM! Clean abstraction for access checking.

The hasFullAccess computed property provides a clear, reusable check for premium access across the view model.


88-96: LGTM! Correctly removes the "Restore Purchase" button.

The about section elements have been properly refactored to remove the "Restore Purchase" button, addressing the feedback from the previous review. The conditional inclusion of "Manage Subscriptions" is appropriate.

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.

3 participants