Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BitwardenShared/UI/Billing/PremiumUpgradeHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ protocol PremiumUpgradeRoute {

extension SendItemRoute: PremiumUpgradeRoute {}
extension SendRoute: PremiumUpgradeRoute {}
extension SettingsRoute: PremiumUpgradeRoute {}
extension VaultItemRoute: PremiumUpgradeRoute {}
extension VaultRoute: PremiumUpgradeRoute {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ final class DeleteAccountProcessor: StateProcessor<DeleteAccountState, DeleteAcc
override func receive(_ action: DeleteAccountAction) {
switch action {
case .dismiss:
coordinator.navigate(to: .dismiss)
coordinator.navigate(to: .dismiss())
}
}

Expand Down Expand Up @@ -158,7 +158,7 @@ final class DeleteAccountProcessor: StateProcessor<DeleteAccountState, DeleteAcc
try await services.authRepository.isUserManagedByOrganization()
} catch {
await coordinator.showErrorAlert(error: error, onDismissed: {
self.coordinator.navigate(to: .dismiss)
self.coordinator.navigate(to: .dismiss())
})
services.errorReporter.log(error: error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class DeleteAccountProcessorTests: BitwardenTestCase {
func test_receive_dismiss() {
subject.receive(.dismiss)

XCTAssertEqual(coordinator.routes.last, .dismiss)
XCTAssertEqual(coordinator.routes.last, .dismiss())
}

/// Perform with `.deleteAccount` presents the master password prompt alert.
Expand Down Expand Up @@ -307,6 +307,6 @@ class DeleteAccountProcessorTests: BitwardenTestCase {

coordinator.alertOnDismissed?()

XCTAssertEqual(coordinator.routes.last, .dismiss)
XCTAssertEqual(coordinator.routes.last, .dismiss())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ final class PendingRequestsProcessor: StateProcessor<
case .declineAllRequestsTapped:
confirmDenyAllRequests()
case .dismiss:
coordinator.navigate(to: .dismiss, context: self)
coordinator.navigate(to: .dismiss(), context: self)
case let .requestTapped(request):
coordinator.navigate(to: .loginRequest(request), context: self)
case let .toastShown(newValue):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class PendingRequestsProcessorTests: BitwardenTestCase {
@MainActor
func test_receive_dismiss() {
subject.receive(.dismiss)
XCTAssertEqual(coordinator.routes.last, .dismiss)
XCTAssertEqual(coordinator.routes.last, .dismiss())
}

/// `receive(_:)` with `.requestTapped(_)` shows the login request view.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ protocol SettingsProcessorDelegate: AnyObject {
final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, SettingsEffect> {
// MARK: Types

typealias Services = HasBillingService
typealias Services = HasBillingRepository
& HasBillingService
& HasConfigService
& HasEnvironmentService
& HasErrorReporter
& HasStateService
& HasStorefrontService
Expand All @@ -39,6 +41,13 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
/// A delegate of the processor that is notified when the settings tab badge needs to be updated.
private weak var delegate: SettingsProcessorDelegate?

/// The helper used to navigate to the Premium upgrade flow.
lazy var premiumUpgradeHelper: PremiumUpgradeHelper = DefaultPremiumUpgradeHelper(
services: services,
coordinator: coordinator,
setURL: { [weak self] url in self?.state.url = url },
)

/// The services used by this processor.
private var services: Services

Expand Down Expand Up @@ -126,7 +135,7 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
case .clearUrl:
state.url = nil
case .dismiss:
coordinator.navigate(to: .dismiss)
coordinator.navigate(to: .dismiss())
case .learnMoreAboutPremium:
state.url = ExternalLinksConstants.learnMoreAboutPremium
state.shouldShowUpgradedToPremiumActionCard = false
Expand Down Expand Up @@ -156,10 +165,10 @@ final class SettingsProcessor: StateProcessor<SettingsState, SettingsAction, Set
if subscription.status.isTroubleState {
coordinator.navigate(to: .premiumPlan(subscription))
} else {
coordinator.navigate(to: .premiumUpgrade)
premiumUpgradeHelper.startInAppPremiumUpgrade(onConfirmed: nil)
}
} catch is GetSubscriptionRequestError {
coordinator.navigate(to: .premiumUpgrade)
premiumUpgradeHelper.startInAppPremiumUpgrade(onConfirmed: nil)
} catch {
services.errorReporter.log(error: error)
await coordinator.showErrorAlert(error: error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class SettingsProcessorTests: BitwardenTestCase { // swiftlint:disable:this type
var coordinator: MockCoordinator<SettingsRoute, SettingsEvent>!
var delegate: MockSettingsProcessorDelegate!
var errorReporter: MockErrorReporter!
var premiumUpgradeHelper: MockPremiumUpgradeHelper!
var subject: SettingsProcessor!
var stateService: MockStateService!
var storefrontService: MockStorefrontService!
Expand All @@ -34,6 +35,7 @@ class SettingsProcessorTests: BitwardenTestCase { // swiftlint:disable:this type
coordinator = MockCoordinator()
delegate = MockSettingsProcessorDelegate()
errorReporter = MockErrorReporter()
premiumUpgradeHelper = MockPremiumUpgradeHelper()
stateService = MockStateService()
storefrontService = MockStorefrontService()
storefrontService.isUSStorefrontReturnValue = true
Expand All @@ -50,6 +52,7 @@ class SettingsProcessorTests: BitwardenTestCase { // swiftlint:disable:this type
coordinator = nil
delegate = nil
errorReporter = nil
premiumUpgradeHelper = nil
subject = nil
stateService = nil
storefrontService = nil
Expand All @@ -71,6 +74,7 @@ class SettingsProcessorTests: BitwardenTestCase { // swiftlint:disable:this type
),
state: SettingsState(presentationMode: presentationMode),
)
subject.premiumUpgradeHelper = premiumUpgradeHelper
}

// MARK: Tests
Expand Down Expand Up @@ -161,7 +165,7 @@ class SettingsProcessorTests: BitwardenTestCase { // swiftlint:disable:this type
func test_receive_dismiss() {
subject.receive(.dismiss)

XCTAssertEqual(coordinator.routes.last, .dismiss)
XCTAssertEqual(coordinator.routes.last, .dismiss())
}

/// Receiving `.otherPressed` navigates to the other screen.
Expand Down Expand Up @@ -221,7 +225,7 @@ class SettingsProcessorTests: BitwardenTestCase { // swiftlint:disable:this type

await subject.perform(.planPressed)

XCTAssertEqual(coordinator.routes.last, .premiumUpgrade)
XCTAssertTrue(premiumUpgradeHelper.startInAppPremiumUpgradeCalled)
XCTAssertEqual(coordinator.loadingOverlaysShown, [LoadingOverlayState(title: Localizations.loading)])
XCTAssertFalse(errorReporter.errors.contains { $0 is GetSubscriptionRequestError })
}
Expand Down Expand Up @@ -249,7 +253,7 @@ class SettingsProcessorTests: BitwardenTestCase { // swiftlint:disable:this type

await subject.perform(.planPressed)

XCTAssertEqual(coordinator.routes.last, .premiumUpgrade)
XCTAssertTrue(premiumUpgradeHelper.startInAppPremiumUpgradeCalled)
XCTAssertEqual(coordinator.loadingOverlaysShown, [LoadingOverlayState(title: Localizations.loading)])
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ final class ExportVaultProcessor: StateProcessor<ExportVaultState, ExportVaultAc
switch action {
case .dismiss:
services.exportVaultService.clearTemporaryFiles()
coordinator.navigate(to: .dismiss)
coordinator.navigate(to: .dismiss())
case let .fileFormatTypeChanged(fileFormat):
state.fileFormat = fileFormat
case let .filePasswordTextChanged(newValue):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class ExportVaultProcessorTests: BitwardenTestCase { // swiftlint:disable:this t
subject.receive(.dismiss)

XCTAssertTrue(exportService.didClearFiles)
XCTAssertEqual(coordinator.routes.last, .dismiss)
XCTAssertEqual(coordinator.routes.last, .dismiss())
}

/// `.receive()` with `.exportVaultTapped` shows the confirm alert for encrypted formats and
Expand Down
13 changes: 11 additions & 2 deletions BitwardenShared/UI/Platform/Settings/SettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ final class SettingsCoordinator: Coordinator, HasStackNavigator { // swiftlint:d
& HasAuthRepository
& HasAuthService
& HasAutofillCredentialService
& HasBillingRepository
& HasBillingService
& HasBiometricsRepository
& HasConfigService
Expand Down Expand Up @@ -170,8 +171,16 @@ final class SettingsCoordinator: Coordinator, HasStackNavigator { // swiftlint:d
showAutoFill()
case .deleteAccount:
showDeleteAccount()
case .dismiss:
stackNavigator?.dismiss()
case let .dismiss(action):
// If we're presenting a more complicated stack of view controllers (in particular, this could happen
// if the user navigates to the Premium upgrade flow) then we only want to dismiss the presented one,
// not the full stack.
let completion = action?.action
if let presentedViewController = stackNavigator?.rootViewController?.presentedViewController {
presentedViewController.dismiss(animated: UI.animated, completion: completion)
} else {
stackNavigator?.dismiss(completion: completion)
}
case .exportVault:
showExportVault()
case .exportVaultToApp:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,23 @@ class SettingsCoordinatorTests: BitwardenTestCase { // swiftlint:disable:this ty
/// `navigate(to:)` with `.dismiss` dismisses the view.
@MainActor
func test_navigate_dismiss() throws {
subject.navigate(to: .dismiss)
subject.navigate(to: .dismiss())

let action = try XCTUnwrap(stackNavigator.actions.last)
XCTAssertEqual(action.type, .dismissed)
XCTAssertEqual(action.type, .dismissedWithCompletionHandler)
}

/// `navigate(to:)` with `.dismiss` and a `DismissAction` passes the completion to the
/// underlying dismiss call and invokes it when dismissal completes.
@MainActor
func test_navigate_dismiss_withDismissAction() throws {
var completionCalled = false

subject.navigate(to: .dismiss(DismissAction { completionCalled = true }))

XCTAssertTrue(completionCalled)
let action = try XCTUnwrap(stackNavigator.actions.last)
XCTAssertEqual(action.type, .dismissedWithCompletionHandler)
}

/// `navigate(to:)` with `.exportVault` pushes the export settings view.
Expand Down
5 changes: 4 additions & 1 deletion BitwardenShared/UI/Platform/Settings/SettingsRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ public enum SettingsRoute: Equatable, Hashable {
case deleteAccount

/// A route that dismisses the current view.
case dismiss
///
/// - Parameter action: The action to perform on dismiss.
///
case dismiss(_ action: DismissAction? = nil)

/// A route to the export vault settings view or export to file view depending on feature flag.
case exportVault
Expand Down
Loading