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

Draft
wants to merge 16 commits into
base: release/7.147.0
Choose a base branch
from
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
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