Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client displays correct subscription #3620

Merged
merged 22 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
916cf82
Switch to local BSK
miasma13 Nov 21, 2024
8559360
Fix init
miasma13 Nov 21, 2024
7f4a9c0
Set proper purchase section descriptions
miasma13 Nov 21, 2024
ac1c495
Support .identityTheftRestorationGlobal entitlement
miasma13 Nov 21, 2024
24023a7
Make the features on expired view dynamic
miasma13 Nov 21, 2024
5848827
For active subscription dynamically build feature rows and disable th…
miasma13 Nov 22, 2024
5668603
Rename
miasma13 Nov 22, 2024
d950790
Fix on off toggle for ITR on expired state
miasma13 Nov 22, 2024
5897158
Allow for the category view to be configured with explicit options list
miasma13 Nov 22, 2024
cfe0f26
Dynamically populate categories based on feature avaialable in curren…
miasma13 Nov 22, 2024
15af654
Migrate to product name on feature selected
miasma13 Nov 22, 2024
04e58d5
Update to use new SubscriptionOptions features
miasma13 Nov 22, 2024
623c5cf
Add feature flags
miasma13 Nov 25, 2024
e2e5bbe
Add support for feature flag local override
miasma13 Nov 25, 2024
462206d
Update label
miasma13 Nov 25, 2024
fc6d480
Switch to remote BSK
miasma13 Nov 25, 2024
db26e74
Fix missing period
miasma13 Nov 28, 2024
c48b3c3
Make the Subscription header cell not tappable
miasma13 Nov 28, 2024
7f55d09
Update BSK to release version
miasma13 Nov 29, 2024
5628b1d
Merge branch 'release/7.147.0' into michal/non-us-subscriptions
miasma13 Nov 29, 2024
5b23900
Merge branch 'release/7.147.0' into michal/non-us-subscriptions
miasma13 Nov 29, 2024
dfaa6a6
Fix tests
miasma13 Nov 29, 2024
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
16 changes: 15 additions & 1 deletion Core/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,21 @@ public enum FeatureFlag: String {

/// https://app.asana.com/0/1208592102886666/1208613627589762/f
case crashReportOptInStatusResetting
case isPrivacyProLaunchedROW
case isPrivacyProLaunchedROWOverride
}

extension FeatureFlag: FeatureFlagDescribing {

public static var localOverrideStoreName: String = "com.duckduckgo.app.featureFlag.localOverrides"

public var supportsLocalOverriding: Bool {
false
switch self {
case .isPrivacyProLaunchedROWOverride:
return true
default:
return false
}
}

public var source: FeatureFlagSource {
Expand Down Expand Up @@ -123,6 +133,10 @@ extension FeatureFlag: FeatureFlagDescribing {
return .remoteReleasable(.feature(.adAttributionReporting))
case .crashReportOptInStatusResetting:
return .internalOnly
case .isPrivacyProLaunchedROW:
return .remoteReleasable(.subfeature(PrivacyProSubfeature.isLaunchedROW))
case .isPrivacyProLaunchedROWOverride:
return .remoteReleasable(.subfeature(PrivacyProSubfeature.isLaunchedROWOverride))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11039,8 +11039,8 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 211.0.0;
branch = "michal/non-us-subscriptions";
kind = branch;
};
};
9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/DuckDuckGo/BrowserServicesKit",
"state" : {
"revision" : "7033b0d6f166ac8152cff602f1a1301641f4da60",
"version" : "211.0.0"
"branch" : "michal/non-us-subscriptions",
"revision" : "c28608cff5bc3369d0277d3d186fa59bfc4b83dc"
}
},
{
Expand Down Expand Up @@ -138,7 +138,7 @@
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b",
"version" : "1.4.0"
Expand Down
26 changes: 23 additions & 3 deletions DuckDuckGo/AppDependencyProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ final class AppDependencyProvider: DependencyProvider {

private init() {
featureFlagger = DefaultFeatureFlagger(internalUserDecider: internalUserDecider,
privacyConfigManager: ContentBlocking.shared.privacyConfigurationManager)
privacyConfigManager: ContentBlocking.shared.privacyConfigurationManager,
localOverrides: FeatureFlagLocalOverrides(
keyValueStore: UserDefaults(suiteName: FeatureFlag.localOverrideStoreName)!,
actionHandler: FeatureFlagOverridesPublishingHandler<FeatureFlag>()
),
for: FeatureFlag.self)

configurationManager = ConfigurationManager(store: configurationStore)

Expand All @@ -109,16 +114,31 @@ final class AppDependencyProvider: DependencyProvider {
let accessTokenStorage = SubscriptionTokenKeychainStorage(keychainType: .dataProtection(.named(subscriptionAppGroup)))
let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment)
let authService = DefaultAuthEndpointService(currentServiceEnvironment: subscriptionEnvironment.serviceEnvironment)
let subscriptionFeatureMappingCache = DefaultSubscriptionFeatureMappingCache(subscriptionEndpointService: subscriptionService,
userDefaults: subscriptionUserDefaults)

let accountManager = DefaultAccountManager(accessTokenStorage: accessTokenStorage,
entitlementsCache: entitlementsCache,
subscriptionEndpointService: subscriptionService,
authEndpointService: authService)

let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: DefaultStorePurchaseManager(),
let theFeatureFlagger = featureFlagger
let subscriptionFeatureFlagger: FeatureFlaggerMapping<SubscriptionFeatureFlags> = FeatureFlaggerMapping { feature in
switch feature {
case .isLaunchedROW:
return theFeatureFlagger.isFeatureOn(.isPrivacyProLaunchedROW)
case .isLaunchedROWOverride:
return theFeatureFlagger.isFeatureOn(.isPrivacyProLaunchedROWOverride)
}
}

let subscriptionManager = DefaultSubscriptionManager(storePurchaseManager: DefaultStorePurchaseManager(subscriptionFeatureMappingCache: subscriptionFeatureMappingCache),
accountManager: accountManager,
subscriptionEndpointService: subscriptionService,
authEndpointService: authService,
subscriptionEnvironment: subscriptionEnvironment)
subscriptionFeatureMappingCache: subscriptionFeatureMappingCache,
subscriptionEnvironment: subscriptionEnvironment,
subscriptionFeatureFlagger: subscriptionFeatureFlagger)
accountManager.delegate = subscriptionManager

self.subscriptionManager = subscriptionManager
Expand Down
4 changes: 3 additions & 1 deletion DuckDuckGo/NetworkProtectionRootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct NetworkProtectionRootView: View {
let statusViewModel: NetworkProtectionStatusViewModel

init() {
let subscriptionManager = AppDependencyProvider.shared.subscriptionManager
let accountManager = AppDependencyProvider.shared.subscriptionManager.accountManager
let locationListRepository = NetworkProtectionLocationListCompositeRepository(accountManager: accountManager)
let usesUnifiedFeedbackForm = accountManager.isUserAuthenticated
Expand All @@ -35,7 +36,8 @@ struct NetworkProtectionRootView: View {
statusObserver: AppDependencyProvider.shared.connectionObserver,
serverInfoObserver: AppDependencyProvider.shared.serverInfoObserver,
locationListRepository: locationListRepository,
usesUnifiedFeedbackForm: usesUnifiedFeedbackForm)
usesUnifiedFeedbackForm: usesUnifiedFeedbackForm,
subscriptionManager: subscriptionManager)
}

var body: some View {
Expand Down
4 changes: 3 additions & 1 deletion DuckDuckGo/NetworkProtectionStatusView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,9 @@ struct NetworkProtectionStatusView: View {

@ViewBuilder
private func about() -> some View {
let viewModel = UnifiedFeedbackFormViewModel(vpnMetadataCollector: DefaultVPNMetadataCollector(), source: .vpn)
let viewModel = UnifiedFeedbackFormViewModel(vpnMetadataCollector: DefaultVPNMetadataCollector(),
source: .vpn,
subscriptionManager: statusModel.subscriptionManager)

Section {
NavigationLink(UserText.netPVPNSettingsFAQ, destination: LazyView(NetworkProtectionFAQView()))
Expand Down
5 changes: 4 additions & 1 deletion DuckDuckGo/NetworkProtectionStatusViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,20 +176,23 @@ final class NetworkProtectionStatusViewModel: ObservableObject {
@Published public var animationsOn: Bool = false

public let usesUnifiedFeedbackForm: Bool
public let subscriptionManager: SubscriptionManager

public init(tunnelController: (TunnelController & TunnelSessionProvider),
settings: VPNSettings,
statusObserver: ConnectionStatusObserver,
serverInfoObserver: ConnectionServerInfoObserver,
errorObserver: ConnectionErrorObserver = ConnectionErrorObserverThroughSession(),
locationListRepository: NetworkProtectionLocationListRepository,
usesUnifiedFeedbackForm: Bool) {
usesUnifiedFeedbackForm: Bool,
subscriptionManager: SubscriptionManager) {
self.tunnelController = tunnelController
self.settings = settings
self.statusObserver = statusObserver
self.serverInfoObserver = serverInfoObserver
self.errorObserver = errorObserver
self.usesUnifiedFeedbackForm = usesUnifiedFeedbackForm
self.subscriptionManager = subscriptionManager

statusMessage = Self.message(for: statusObserver.recentValue)
self.headerTitle = Self.titleText(status: statusObserver.recentValue)
Expand Down
6 changes: 4 additions & 2 deletions DuckDuckGo/SettingsOthersView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ struct SettingsOthersView: View {

// Share Feedback
if viewModel.usesUnifiedFeedbackForm {
let formViewModel = UnifiedFeedbackFormViewModel(vpnMetadataCollector: DefaultVPNMetadataCollector(), source: .settings)
let formViewModel = UnifiedFeedbackFormViewModel(vpnMetadataCollector: DefaultVPNMetadataCollector(),
source: .settings,
subscriptionManager: viewModel.subscriptionManager)
NavigationLink {
UnifiedFeedbackCategoryView(UserText.subscriptionFeedback, sources: UnifiedFeedbackFlowCategory.self, selection: $viewModel.selectedFeedbackFlow) {
UnifiedFeedbackCategoryView(UserText.subscriptionFeedback, options: UnifiedFeedbackFlowCategory.allCases, selection: $viewModel.selectedFeedbackFlow) {
if let selectedFeedbackFlow = viewModel.selectedFeedbackFlow {
switch UnifiedFeedbackFlowCategory(rawValue: selectedFeedbackFlow) {
case nil:
Expand Down
2 changes: 2 additions & 0 deletions DuckDuckGo/SettingsState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct SettingsState {
var hasActiveSubscription: Bool
var isRestoring: Bool
var shouldDisplayRestoreSubscriptionError: Bool
var subscriptionFeatures: [Entitlement.ProductName]
var entitlements: [Entitlement.ProductName]
var platform: DDGSubscription.Platform
var isShowingStripeView: Bool
Expand Down Expand Up @@ -132,6 +133,7 @@ struct SettingsState {
hasActiveSubscription: false,
isRestoring: false,
shouldDisplayRestoreSubscriptionError: false,
subscriptionFeatures: [],
entitlements: [],
platform: .unknown,
isShowingStripeView: false),
Expand Down
103 changes: 69 additions & 34 deletions DuckDuckGo/SettingsSubscriptionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,20 @@ struct SettingsSubscriptionView: View {
@ViewBuilder
private var purchaseSubscriptionView: some View {
Group {
let subtitleText = {
// TODO: no feature flag (old text)
// UserText.settingsPProDescription

switch subscriptionManager.storePurchaseManager().currentStorefrontRegion {
case .usa:
UserText.settingsPProPurchaseUSDescription
case .restOfWorld:
UserText.settingsPProPurchaseROWDescription
}
}()

SettingsCellView(label: UserText.settingsPProSubscribe,
subtitle: UserText.settingsPProDescription,
subtitle: subtitleText,
image: Image("SettingsPrivacyPro"))

// Get privacy pro
Expand Down Expand Up @@ -93,23 +105,33 @@ struct SettingsSubscriptionView: View {

@ViewBuilder
private var disabledFeaturesView: some View {
SettingsCellView(label: UserText.settingsPProVPNTitle,
image: Image("SettingsPrivacyProVPN"),
statusIndicator: StatusIndicatorView(status: .off),
isGreyedOut: true
)
SettingsCellView(
label: UserText.settingsPProDBPTitle,
image: Image("SettingsPrivacyProPIR"),
statusIndicator: StatusIndicatorView(status: .off),
isGreyedOut: true
)
SettingsCellView(
label: UserText.settingsPProITRTitle,
image: Image("SettingsPrivacyProITP"),
statusIndicator: StatusIndicatorView(status: .off),
isGreyedOut: true
)
let subscriptionFeatures = settingsViewModel.state.subscription.subscriptionFeatures

if subscriptionFeatures.contains(.networkProtection) {
SettingsCellView(label: UserText.settingsPProVPNTitle,
image: Image("SettingsPrivacyProVPN"),
statusIndicator: StatusIndicatorView(status: .off),
isGreyedOut: true
)
}

if subscriptionFeatures.contains(.dataBrokerProtection) {
SettingsCellView(
label: UserText.settingsPProDBPTitle,
image: Image("SettingsPrivacyProPIR"),
statusIndicator: StatusIndicatorView(status: .off),
isGreyedOut: true
)
}

if subscriptionFeatures.contains(.identityTheftRestoration) || subscriptionFeatures.contains(.identityTheftRestorationGlobal) {
SettingsCellView(
label: UserText.settingsPProITRTitle,
image: Image("SettingsPrivacyProITP"),
statusIndicator: StatusIndicatorView(status: .off),
isGreyedOut: true
)
}
}

@ViewBuilder
Expand Down Expand Up @@ -155,37 +177,50 @@ struct SettingsSubscriptionView: View {

@ViewBuilder
private var subscriptionDetailsView: some View {

if settingsViewModel.state.subscription.entitlements.contains(.networkProtection) {
let subscriptionFeatures = settingsViewModel.state.subscription.subscriptionFeatures
let userEntitlements = settingsViewModel.state.subscription.entitlements

if subscriptionFeatures.contains(.networkProtection) {
let hasVPNEntitlement = userEntitlements.contains(.networkProtection)
let isVPNConnected = settingsViewModel.state.networkProtectionConnected

NavigationLink(destination: LazyView(NetworkProtectionRootView()), isActive: $isShowingVPN) {
SettingsCellView(
label: UserText.settingsPProVPNTitle,
image: Image("SettingsPrivacyProVPN"),
statusIndicator: StatusIndicatorView(status: settingsViewModel.state.networkProtectionConnected ? .on : .off)
statusIndicator: StatusIndicatorView(status: isVPNConnected ? .on : .off),
isGreyedOut: !hasVPNEntitlement
)
}
.disabled(!hasVPNEntitlement)
}

if settingsViewModel.state.subscription.entitlements.contains(.dataBrokerProtection) {

if subscriptionFeatures.contains(.dataBrokerProtection) {
let hasDBPEntitlement = userEntitlements.contains(.dataBrokerProtection)

NavigationLink(destination: LazyView(SubscriptionPIRView()), isActive: $isShowingDBP) {
SettingsCellView(
label: UserText.settingsPProDBPTitle,
image: Image("SettingsPrivacyProPIR"),
statusIndicator: StatusIndicatorView(status: .on)
statusIndicator: StatusIndicatorView(status: hasDBPEntitlement ? .on : .off),
isGreyedOut: !hasDBPEntitlement
)
}
.disabled(!hasDBPEntitlement)
}

if settingsViewModel.state.subscription.entitlements.contains(.identityTheftRestoration) {
NavigationLink(
destination: LazyView(SubscriptionITPView()),
isActive: $isShowingITP) {
SettingsCellView(
label: UserText.settingsPProITRTitle,
image: Image("SettingsPrivacyProITP"),
statusIndicator: StatusIndicatorView(status: .on)
)

if subscriptionFeatures.contains(.identityTheftRestoration) || subscriptionFeatures.contains(.identityTheftRestorationGlobal) {
let hasITREntitlement = userEntitlements.contains(.identityTheftRestoration) || userEntitlements.contains(.identityTheftRestorationGlobal)

NavigationLink(destination: LazyView(SubscriptionITPView()), isActive: $isShowingITP) {
SettingsCellView(
label: UserText.settingsPProITRTitle,
image: Image("SettingsPrivacyProITP"),
statusIndicator: StatusIndicatorView(status: hasITREntitlement ? .on : .off),
isGreyedOut: !hasITREntitlement
)
}
.disabled(!hasITREntitlement)
}

NavigationLink(destination: LazyView(SubscriptionSettingsView(configuration: .subscribed, settingsViewModel: settingsViewModel))
Expand Down
5 changes: 3 additions & 2 deletions DuckDuckGo/SettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ final class SettingsViewModel: ObservableObject {
let textZoomCoordinator: TextZoomCoordinating

// Subscription Dependencies
private let subscriptionManager: SubscriptionManager
let subscriptionManager: SubscriptionManager
let subscriptionFeatureAvailability: SubscriptionFeatureAvailability
private var subscriptionSignOutObserver: Any?
var duckPlayerContingencyHandler: DuckPlayerContingencyHandler {
Expand Down Expand Up @@ -750,7 +750,7 @@ extension SettingsViewModel {

// Check entitlements and update state
var currentEntitlements: [Entitlement.ProductName] = []
let entitlementsToCheck: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration]
let entitlementsToCheck: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration, .identityTheftRestorationGlobal]

for entitlement in entitlementsToCheck {
if case .success(true) = await subscriptionManager.accountManager.hasEntitlement(forProductName: entitlement) {
Expand All @@ -759,6 +759,7 @@ extension SettingsViewModel {
}

self.state.subscription.entitlements = currentEntitlements
self.state.subscription.subscriptionFeatures = await subscriptionManager.currentSubscriptionFeatures()

case .failure:
break
Expand Down
Loading
Loading