From 3ca2abbede9fb6b1b02b8fe1fba89c740634c0f1 Mon Sep 17 00:00:00 2001 From: ezimet-livefront Date: Wed, 18 Dec 2024 01:53:25 -0500 Subject: [PATCH 1/5] rolled back review prompt to legacy api --- .../UI/Vault/Vault/VaultList/VaultListView.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift index b49f0c163..cf904d87f 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift @@ -348,7 +348,7 @@ struct VaultListView: View { } .task(id: store.state.isEligibleForAppReview) { if store.state.isEligibleForAppReview { - requestReview() + SKStoreReviewController.requestReview() store.send(.appReviewPromptShown) } } @@ -380,15 +380,6 @@ struct VaultListView: View { ) ) } - - /// Requests a review of the app. - private func requestReview() { - if #available(iOS 16.0, *) { - Environment(\.requestReview).wrappedValue() - } else { - SKStoreReviewController.requestReview() - } - } } // MARK: Previews From 45755dfc27cf33439882bce46856bbe586ba974b Mon Sep 17 00:00:00 2001 From: ezimet-livefront Date: Wed, 18 Dec 2024 15:47:54 -0500 Subject: [PATCH 2/5] address feedback --- .../Appearance/Modifiers/ReviewModifier.swift | 44 +++++++++++++++++++ .../Vault/Vault/VaultList/VaultListView.swift | 26 ++++++++--- 2 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 BitwardenShared/UI/Platform/Application/Appearance/Modifiers/ReviewModifier.swift diff --git a/BitwardenShared/UI/Platform/Application/Appearance/Modifiers/ReviewModifier.swift b/BitwardenShared/UI/Platform/Application/Appearance/Modifiers/ReviewModifier.swift new file mode 100644 index 000000000..3e08a5a26 --- /dev/null +++ b/BitwardenShared/UI/Platform/Application/Appearance/Modifiers/ReviewModifier.swift @@ -0,0 +1,44 @@ +import StoreKit +import SwiftUI + +/// A view modifier that requests a review when the view appears. +@available(iOS 16.0, *) +struct ReviewModifier: ViewModifier { + /// The environment key for the request review function. + @Environment(\.requestReview) var requestReview + + /// The eligibility for requesting a review. + let isEligible: Bool + + /// The closure to execute after requesting a review. + let afterClosure: () -> Void + + func body(content: Content) -> some View { + content + .task(id: isEligible) { + if isEligible { + requestReview() + afterClosure() + } + } + } +} + +/// A view modifier that requests a review via legacy API when the view appears. +struct RequestReviewLegacyModifier: ViewModifier { + /// The eligibility for requesting a review. + let isEligible: Bool + + /// The closure to execute after requesting a review. + let afterClosure: () -> Void + + func body(content: Content) -> some View { + content + .task(id: isEligible) { + if isEligible { + SKStoreReviewController.requestReview() + afterClosure() + } + } + } +} diff --git a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift index cf904d87f..720eaae11 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift @@ -1,7 +1,6 @@ // swiftlint:disable file_length import BitwardenSdk -import StoreKit import SwiftUI // MARK: - SearchableVaultListView @@ -346,12 +345,6 @@ struct VaultListView: View { .task(id: store.state.vaultFilterType) { await store.perform(.streamVaultList) } - .task(id: store.state.isEligibleForAppReview) { - if store.state.isEligibleForAppReview { - SKStoreReviewController.requestReview() - store.send(.appReviewPromptShown) - } - } .onAppear { Task { await store.perform(.checkAppReviewEligibility) @@ -360,6 +353,25 @@ struct VaultListView: View { .onDisappear { store.send(.disappeared) } + .apply { view in + if #available(iOS 16.0, *) { + view.modifier( + ReviewModifier( + isEligible: store.state.isEligibleForAppReview, + afterClosure: { + store.send(.appReviewPromptShown) + }) + ) + } else { + view.modifier( + RequestReviewLegacyModifier( + isEligible: store.state.isEligibleForAppReview, + afterClosure: { + store.send(.appReviewPromptShown) + }) + ) + } + } } // MARK: Private properties From c234380c213109eb6e950a5c780a6b8db8778eef Mon Sep 17 00:00:00 2001 From: ezimet-livefront Date: Wed, 18 Dec 2024 15:48:33 -0500 Subject: [PATCH 3/5] reindent the code --- .../UI/Vault/Vault/VaultList/VaultListView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift index 720eaae11..f5f5cc999 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift @@ -359,16 +359,16 @@ struct VaultListView: View { ReviewModifier( isEligible: store.state.isEligibleForAppReview, afterClosure: { - store.send(.appReviewPromptShown) - }) + store.send(.appReviewPromptShown) + }) ) } else { view.modifier( RequestReviewLegacyModifier( isEligible: store.state.isEligibleForAppReview, afterClosure: { - store.send(.appReviewPromptShown) - }) + store.send(.appReviewPromptShown) + }) ) } } From 091881830bd1b04702249447bd95b89ab413963f Mon Sep 17 00:00:00 2001 From: ezimet-livefront Date: Wed, 18 Dec 2024 15:52:11 -0500 Subject: [PATCH 4/5] fix lint warning --- .../UI/Vault/Vault/VaultList/VaultListView.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift index f5f5cc999..1f8fe06a9 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift @@ -360,7 +360,8 @@ struct VaultListView: View { isEligible: store.state.isEligibleForAppReview, afterClosure: { store.send(.appReviewPromptShown) - }) + } + ) ) } else { view.modifier( @@ -368,7 +369,8 @@ struct VaultListView: View { isEligible: store.state.isEligibleForAppReview, afterClosure: { store.send(.appReviewPromptShown) - }) + } + ) ) } } From 5f51298f7d8cc1a8edbd7a0013b67f9fc15aa77b Mon Sep 17 00:00:00 2001 From: ezimet-livefront Date: Sat, 21 Dec 2024 01:24:02 -0500 Subject: [PATCH 5/5] address feedbacks --- .../Appearance/Modifiers/ReviewModifier.swift | 39 ++++++++++++++++++- .../UI/Vault/Vault/VaultCoordinator.swift | 7 +++- .../Vault/Vault/VaultCoordinatorTests.swift | 1 + .../Vault/Vault/VaultList/VaultListView.swift | 25 +++--------- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/BitwardenShared/UI/Platform/Application/Appearance/Modifiers/ReviewModifier.swift b/BitwardenShared/UI/Platform/Application/Appearance/Modifiers/ReviewModifier.swift index 3e08a5a26..e47bcf951 100644 --- a/BitwardenShared/UI/Platform/Application/Appearance/Modifiers/ReviewModifier.swift +++ b/BitwardenShared/UI/Platform/Application/Appearance/Modifiers/ReviewModifier.swift @@ -6,7 +6,7 @@ import SwiftUI struct ReviewModifier: ViewModifier { /// The environment key for the request review function. @Environment(\.requestReview) var requestReview - + /// The eligibility for requesting a review. let isEligible: Bool @@ -29,6 +29,9 @@ struct RequestReviewLegacyModifier: ViewModifier { /// The eligibility for requesting a review. let isEligible: Bool + /// The window scene to request a review. + let windowScene: UIWindowScene + /// The closure to execute after requesting a review. let afterClosure: () -> Void @@ -36,9 +39,41 @@ struct RequestReviewLegacyModifier: ViewModifier { content .task(id: isEligible) { if isEligible { - SKStoreReviewController.requestReview() + SKStoreReviewController.requestReview(in: windowScene) afterClosure() } } } } + +/// A view extension that requests a review when the view appears. +extension View { + func requestReview(windowScene: UIWindowScene?, isEligible: Bool, afterClosure: @escaping () -> Void) -> some View { + apply { view in + if #available(iOS 16.0, *) { + view.modifier( + ReviewModifier( + isEligible: isEligible, + afterClosure: afterClosure + ) + ) + } else { + if let windowScene { + view.modifier( + RequestReviewLegacyModifier( + isEligible: isEligible, + windowScene: windowScene, + afterClosure: afterClosure + ) + ) + } + } + } + } +} + +/// An error that represents a window scene error. +public enum WindowSceneError: Error, Equatable { + /// The window scene is null. + case nullWindowScene +} diff --git a/BitwardenShared/UI/Vault/Vault/VaultCoordinator.swift b/BitwardenShared/UI/Vault/Vault/VaultCoordinator.swift index c11cb7f8e..37c3dbfeb 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultCoordinator.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultCoordinator.swift @@ -289,10 +289,15 @@ final class VaultCoordinator: Coordinator, HasStackNavigator { ) ) let store = Store(processor: processor) + let windowScene = stackNavigator?.rootViewController?.view.window?.windowScene let view = VaultListView( store: store, - timeProvider: services.timeProvider + timeProvider: services.timeProvider, + windowScene: windowScene ) + if windowScene == nil { + services.errorReporter.log(error: WindowSceneError.nullWindowScene) + } stackNavigator?.replace(view, animated: false) } diff --git a/BitwardenShared/UI/Vault/Vault/VaultCoordinatorTests.swift b/BitwardenShared/UI/Vault/Vault/VaultCoordinatorTests.swift index 6e043ac14..5f5b00f44 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultCoordinatorTests.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultCoordinatorTests.swift @@ -199,6 +199,7 @@ class VaultCoordinatorTests: BitwardenTestCase { let action = try XCTUnwrap(stackNavigator.actions.last) XCTAssertEqual(action.type, .replaced) XCTAssertTrue(action.view is VaultListView) + XCTAssertEqual(errorReporter.errors.last as? WindowSceneError, WindowSceneError.nullWindowScene) } /// `navigate(to:)` with `.lockVault` navigates the user to the login view. diff --git a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift index 1f8fe06a9..57319dd3e 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView.swift @@ -282,6 +282,9 @@ struct VaultListView: View { /// The `TimeProvider` used to calculate TOTP expiration. var timeProvider: any TimeProvider + /// The window scene for requesting a review. + var windowScene: UIWindowScene? + var body: some View { ZStack { SearchableVaultListView( @@ -353,26 +356,8 @@ struct VaultListView: View { .onDisappear { store.send(.disappeared) } - .apply { view in - if #available(iOS 16.0, *) { - view.modifier( - ReviewModifier( - isEligible: store.state.isEligibleForAppReview, - afterClosure: { - store.send(.appReviewPromptShown) - } - ) - ) - } else { - view.modifier( - RequestReviewLegacyModifier( - isEligible: store.state.isEligibleForAppReview, - afterClosure: { - store.send(.appReviewPromptShown) - } - ) - ) - } + .requestReview(windowScene: windowScene, isEligible: store.state.isEligibleForAppReview) { + store.send(.appReviewPromptShown) } }