Skip to content

Conversation

@iammajid
Copy link
Contributor

New Share Vault section appears in vault detail settings between locking options and vault management. For normal vaults, displays promotional content about Cryptomator Hub with links to documentation and the Hub website. For Hub-managed vaults, shows sharing instructions and provides a direct link to open the vault in Cryptomator Hub.

Refactored setupConstraints() to fix function body length violation and applied SwiftFormat auto-formatting.
@iammajid iammajid force-pushed the feature/hub-sharebutton branch from 5c561a1 to a317f83 Compare October 30, 2025 18:44
@iammajid iammajid marked this pull request as ready for review November 4, 2025 12:59
@iammajid iammajid requested a review from tobihagemann November 4, 2025 12:59
@coderabbitai
Copy link

coderabbitai bot commented Nov 4, 2025

Walkthrough

Adds a new ShareVault feature and integrates it into the VaultDetail flow. New files: ShareVaultCoordinator (navigation), ShareVaultViewController (UI), ShareVaultViewModel (data). VaultDetailCoordinator gains showShareVault(); VaultDetailViewController handles a new .shareVault action; VaultDetailViewModel adds a shareVault section and action. New localization keys and two image asset sets are added. Project file updated to include the new sources in build targets. A HubConfig extension visibility was changed to public in CryptomatorCommon.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • ShareVaultViewController: detailed Auto Layout and conditional rendering paths.
    • ShareVaultCoordinator: hub URL extraction flow that reads cached vault config (optional handling).
    • ShareVaultViewModel: localization keys and URL assignments for both .normal and .hub cases.
    • Integration changes: VaultDetailViewModel/Coordinator/ViewController updates and project.pbxproj modifications to ensure targets and resource bundling are correct.
    • Public API surface change: public extension HubConfig — verify intended visibility.

Possibly related PRs

Suggested reviewers

  • tobihagemann

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 describes the main change: adding a Share Vault option to vault detail settings, which is the primary feature introduced across all modified files.
Description check ✅ Passed The description is directly related to the changeset, providing accurate details about the Share Vault feature, its placement, and its behavior for normal and Hub-managed vaults.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/hub-sharebutton

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: 0

🧹 Nitpick comments (2)
Cryptomator/VaultDetail/VaultDetailViewModel.swift (1)

115-117: Consider using the disclosure-style button for navigation.

Since tapping this row launches the Share Vault flow, it would align with the rest of the settings UI to present it via ButtonCellViewModel.createDisclosureButton(...), giving the familiar chevron cue alongside rename/move/change password.

Cryptomator/VaultDetail/ShareVault/ShareVaultViewController.swift (1)

123-134: Hide/disable the CTA when no URL is provided

ShareVaultViewModelProtocol.forTeamsURL is optional, yet the button stays tappable when that value is missing, leaving users with a dead control. Please disable (and visually soften) the CTA whenever the view model cannot supply a destination so the UI reflects the available action.

 		let button = UIButton(type: .system)
 		button.setTitle(viewModel.forTeamsButtonTitle, for: .normal)
 		button.backgroundColor = .cryptomatorPrimary
 		button.setTitleColor(.white, for: .normal)
 		button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
 		button.titleLabel?.adjustsFontForContentSizeCategory = true
 		button.layer.cornerRadius = LayoutConstants.cornerRadius
 		button.translatesAutoresizingMaskIntoConstraints = false
+		let hasTarget = viewModel.forTeamsURL != nil
+		button.isEnabled = hasTarget
+		button.alpha = hasTarget ? 1 : 0.5
 		button.addTarget(self, action: #selector(visitHubButtonTapped), for: .touchUpInside)
📜 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 727673c.

⛔ Files ignored due to path filters (6)
  • SharedResources/Assets.xcassets/cryptomator-hub-logo.imageset/[email protected] is excluded by !**/*.png
  • SharedResources/Assets.xcassets/cryptomator-hub-logo.imageset/[email protected] is excluded by !**/*.png
  • SharedResources/Assets.xcassets/cryptomator-hub-logo.imageset/[email protected] is excluded by !**/*.png
  • SharedResources/Assets.xcassets/cryptomator-hub.imageset/[email protected] is excluded by !**/*.png
  • SharedResources/Assets.xcassets/cryptomator-hub.imageset/[email protected] is excluded by !**/*.png
  • SharedResources/Assets.xcassets/cryptomator-hub.imageset/[email protected] is excluded by !**/*.png
📒 Files selected for processing (10)
  • Cryptomator.xcodeproj/project.pbxproj (6 hunks)
  • Cryptomator/VaultDetail/ShareVault/ShareVaultCoordinator.swift (1 hunks)
  • Cryptomator/VaultDetail/ShareVault/ShareVaultViewController.swift (1 hunks)
  • Cryptomator/VaultDetail/ShareVault/ShareVaultViewModel.swift (1 hunks)
  • Cryptomator/VaultDetail/VaultDetailCoordinator.swift (1 hunks)
  • Cryptomator/VaultDetail/VaultDetailViewController.swift (1 hunks)
  • Cryptomator/VaultDetail/VaultDetailViewModel.swift (4 hunks)
  • SharedResources/Assets.xcassets/cryptomator-hub-logo.imageset/Contents.json (1 hunks)
  • SharedResources/Assets.xcassets/cryptomator-hub.imageset/Contents.json (1 hunks)
  • SharedResources/en.lproj/Localizable.strings (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 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.xcodeproj/project.pbxproj
  • Cryptomator/VaultDetail/VaultDetailViewController.swift
  • Cryptomator/VaultDetail/ShareVault/ShareVaultCoordinator.swift
  • Cryptomator/VaultDetail/VaultDetailCoordinator.swift
  • Cryptomator/VaultDetail/ShareVault/ShareVaultViewController.swift
📚 Learning: 2024-10-14T15:45:13.118Z
Learnt from: tobihagemann
Repo: cryptomator/ios PR: 386
File: Cryptomator/VaultList/VaultListViewModel.swift:112-112
Timestamp: 2024-10-14T15:45:13.118Z
Learning: In `VaultListViewModel`, use `all(ignoringResult: promises)` instead of `all(promises).then { }` and omit unnecessary logging when refreshing vault lock states.

Applied to files:

  • Cryptomator/VaultDetail/VaultDetailViewModel.swift
🧬 Code graph analysis (4)
Cryptomator/VaultDetail/VaultDetailViewModel.swift (1)
CryptomatorCommon/Sources/CryptomatorCommonCore/LocalizedString.swift (1)
  • getValue (12-22)
Cryptomator/VaultDetail/VaultDetailViewController.swift (1)
Cryptomator/VaultDetail/VaultDetailCoordinator.swift (1)
  • showShareVault (101-106)
Cryptomator/VaultDetail/ShareVault/ShareVaultCoordinator.swift (1)
Cryptomator/VaultDetail/VaultDetailCoordinator.swift (1)
  • start (32-37)
Cryptomator/VaultDetail/VaultDetailCoordinator.swift (1)
Cryptomator/VaultDetail/ShareVault/ShareVaultCoordinator.swift (1)
  • start (24-34)
🔇 Additional comments (1)
Cryptomator.xcodeproj/project.pbxproj (1)

448-450: ShareVault sources correctly hooked up.

Thanks for wiring the new coordinator, view model, and view controller into the Cryptomator target—everything lands in the right build phase with no stray memberships.

@tobihagemann tobihagemann requested a review from Copilot November 5, 2025 09:13
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds a new "Share Vault" feature to the Cryptomator iOS app, allowing users to share vaults with team members using Cryptomator Hub. The feature includes UI screens with instructions and links to access the Hub.

Key changes:

  • New localized strings for the share vault feature in multiple languages
  • Image assets for Cryptomator Hub branding (logo and screenshots)
  • New ShareVault module with coordinator, view model, and view controller
  • Integration of share vault button in the vault detail screen

Reviewed Changes

Copilot reviewed 10 out of 16 changed files in this pull request and generated no comments.

Show a summary per file
File Description
SharedResources/en.lproj/Localizable.strings Added 13 new localization keys for share vault UI text
SharedResources/Assets.xcassets/cryptomator-hub.imageset/* Added Hub screenshot images at 1x, 2x, and 3x resolutions
SharedResources/Assets.xcassets/cryptomator-hub-logo.imageset/* Added Hub logo images at 1x, 2x, and 3x resolutions
Cryptomator/VaultDetail/VaultDetailViewModel.swift Added share vault section and button action to vault detail screen
Cryptomator/VaultDetail/VaultDetailViewController.swift Added handler for share vault button action
Cryptomator/VaultDetail/VaultDetailCoordinator.swift Added navigation to share vault screen

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Member

@tobihagemann tobihagemann left a comment

Choose a reason for hiding this comment

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

Since we're building new UI here: Was there a reason for using UIKit instead of SwiftUI? Would've thought that SwiftUI could simplify things. I think it should be a long-term goal to migrate to SwiftUI, so we shouldn't pile up our technical debt. 😄 What do you think?

Comment on lines 44 to 54
// Extract base Hub URL
let apiBaseURL: URL?
if let apiBaseUrl = hubConfig.apiBaseUrl {
apiBaseURL = URL(string: apiBaseUrl)
} else if let deviceResourceURL = URL(string: hubConfig.devicesResourceUrl) {
apiBaseURL = deviceResourceURL.deletingLastPathComponent()
} else {
apiBaseURL = nil
}

return apiBaseURL?.deletingLastPathComponent()
Copy link
Member

Choose a reason for hiding this comment

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

Simplify this by returning hubConfig.getWebAppURL().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simplifyed in 1922f76.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went with UIKit mainly for consistency, since the rest of the app is still based on it. 😄 But absolutely, it makes total sense to gradually move more towards SwiftUI in the future. 👍

Copy link
Member

Choose a reason for hiding this comment

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

All right, I understand. But then lets refactor this to SwiftUI.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift (1)

368-382: Based on the verification results, I can now finalize the review. Let me search for any additional context about the codebase's documentation standards.

Add documentation for the public HubConfig extension and its methods.

The extension and its methods are now part of the public API (used by ShareVault code at line 44 of ShareVaultCoordinator.swift) but lack documentation. Public declarations require documentation comments, which improve API clarity and enable Xcode Quick Help support.

Apply this diff:

+/// Public helpers for deriving Hub URLs from HubConfig.
 public extension HubConfig {
+	/// Returns the API base URL for the Hub instance.
+	///
+	/// If `apiBaseUrl` is set, returns it directly. Otherwise, derives the API base URL
+	/// by removing the last path component from `devicesResourceUrl`.
+	///
+	/// - Returns: The API base URL, or `nil` if it cannot be determined.
 	func getAPIBaseURL() -> URL? {
 		if let apiBaseUrl {
 			return URL(string: apiBaseUrl)
 		}
 		guard let deviceResourceURL = URL(string: devicesResourceUrl) else {
 			return nil
 		}
 		return deviceResourceURL.deletingLastPathComponent()
 	}
 
+	/// Returns the web app URL for the Hub instance.
+	///
+	/// Derives the web app URL by navigating up one level from the API base URL
+	/// and appending the "app" path component.
+	///
+	/// - Returns: The web app URL, or `nil` if the API base URL cannot be determined.
 	func getWebAppURL() -> URL? {
 		getAPIBaseURL()?.deletingLastPathComponent().appendingPathComponent("app")
 	}
 }
🧹 Nitpick comments (1)
Cryptomator/VaultDetail/ShareVault/ShareVaultCoordinator.swift (1)

36-45: Consider adding logging for Hub URL extraction failures.

The method correctly simplifies the extraction by returning hubConfig.getWebAppURL() (as per past review feedback). However, the try? operators silently ignore errors, which could make debugging difficult if URL extraction fails.

Consider adding error logging when extraction fails:

 private func extractHubVaultURL() -> URL? {
-	guard let cachedVault = try? VaultDBCache().getCachedVault(withVaultUID: vaultInfo.vaultUID),
-	      let vaultConfigToken = cachedVault.vaultConfigToken,
-	      let vaultConfig = try? UnverifiedVaultConfig(token: vaultConfigToken),
-	      let hubConfig = vaultConfig.allegedHubConfig else {
+	do {
+		let cachedVault = try VaultDBCache().getCachedVault(withVaultUID: vaultInfo.vaultUID)
+		guard let vaultConfigToken = cachedVault.vaultConfigToken,
+		      let vaultConfig = try? UnverifiedVaultConfig(token: vaultConfigToken),
+		      let hubConfig = vaultConfig.allegedHubConfig else {
+			DDLogWarn("ShareVaultCoordinator: Failed to extract Hub config from vault")
+			return nil
+		}
+		return hubConfig.getWebAppURL()
+	} catch {
+		DDLogError("ShareVaultCoordinator: Failed to get cached vault: \(error)")
 		return nil
 	}
-
-	return hubConfig.getWebAppURL()
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 727673c and 1922f76.

📒 Files selected for processing (2)
  • Cryptomator/VaultDetail/ShareVault/ShareVaultCoordinator.swift (1 hunks)
  • CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 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/VaultDetail/ShareVault/ShareVaultCoordinator.swift
🧬 Code graph analysis (1)
Cryptomator/VaultDetail/ShareVault/ShareVaultCoordinator.swift (3)
Cryptomator/MainCoordinator.swift (1)
  • navigationController (92-102)
Cryptomator/VaultDetail/VaultDetailCoordinator.swift (1)
  • start (32-37)
CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift (1)
  • getWebAppURL (379-381)
⏰ 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 (1)
Cryptomator/VaultDetail/ShareVault/ShareVaultCoordinator.swift (1)

13-22: Code follows correct memory management pattern.

Verification confirms that ShareVaultViewController.coordinator is correctly declared as weak at line 13, preventing retain cycles with ShareVaultCoordinator. The coordinator setup follows best practices.

Comment on lines +24 to +34
func start() {
let viewModel: ShareVaultViewModel
if vaultInfo.vaultConfigType == .hub, let hubURL = extractHubVaultURL() {
viewModel = ShareVaultViewModel(type: .hub(hubURL))
} else {
viewModel = ShareVaultViewModel(type: .normal)
}
let shareVaultViewController = ShareVaultViewController(viewModel: viewModel)
shareVaultViewController.coordinator = self
navigationController.pushViewController(shareVaultViewController, animated: true)
}
Copy link

@coderabbitai coderabbitai bot Nov 6, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider handling Hub URL extraction failures more explicitly.

When vaultInfo.vaultConfigType == .hub but extractHubVaultURL() returns nil, the code silently falls back to .normal type. This could confuse users who expect Hub-specific features. Consider:

  • Logging the failure for debugging purposes
  • Showing an error message to the user if Hub configuration is expected but unavailable
  • Distinguishing between "normal vault" and "Hub vault with extraction error" states
🤖 Prompt for AI Agents
In Cryptomator/VaultDetail/ShareVault/ShareVaultCoordinator.swift around lines
24 to 34, when vaultInfo.vaultConfigType == .hub and extractHubVaultURL()
returns nil the code currently falls back silently to .normal; change this to
explicitly handle the failure by (1) logging the extraction failure with context
(vault id/name) via the app logger, (2) presenting a user-facing error
(UIAlertController) on the main thread that explains the Hub configuration could
not be loaded and offers to retry or cancel, and (3) updating the created
ViewModel/state so the UI can reflect the distinct case (either add a new
ShareVaultViewModel case like .hubExtractionError or pass a flag into .normal to
show an error state) — implement logging before constructing the view
controller, present the alert if extraction fails and only push the view
controller after user confirmation or push a view controller initialized with
the error state so the UI clearly distinguishes "normal vault" from "Hub vault
with extraction error".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tobihagemann I see three ways to fix this:

  • Option 1: Show an error alert and don't navigate at all if URL extraction fails
  • Option 2: Add a .hubError state that shows "Failed to load Hub information"
  • Option 3: Hide the Share button upstream if we detect URL extraction would fail I'd lean towards

Option 1 - it's the clearest for users. Alternatively, is this just an edge case that's unlikely to happen in practice? In the worst case, users would currently just see our Hub marketing page instead of their actual Hub instance, which would be confusing but not breaking anything. What do you think?

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, let's go with option 1. It's totally an edge case and shouldn't happen, but you never know what we're changing in the future. 😄

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