Skip to content

Commit

Permalink
Unify paywall and eligibility
Browse files Browse the repository at this point in the history
- Present paywall only on save/connect
- Suggest paywall products via IAPManager
- Suppress PurchaseRequiredButton action (partial requirements)
- Handle .sharing/.appleTV requirements in ProfileCoordinator
- Discontinue platform purchases
  • Loading branch information
keeshux committed Dec 16, 2024
1 parent ffb8829 commit 1ed774b
Show file tree
Hide file tree
Showing 46 changed files with 430 additions and 383 deletions.
2 changes: 1 addition & 1 deletion Library/Sources/AppUIMain/Views/App/ProfileRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private struct MarkerView: View {
.opaque(requiredFeatures == nil && (profileId == nextProfileId || profileId == tunnel.currentProfile?.id))

if let requiredFeatures {
PurchaseRequiredButton(features: requiredFeatures, paywallReason: .constant(nil))
PurchaseRequiredButton(features: requiredFeatures)
}
}
.frame(width: 24)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ struct DiagnosticsView: View {
liveLogSection
openVPNSection
tunnelLogsSection
if iapManager.isEligibleForFeedback() {
if iapManager.isEligibleForFeedback {
reportIssueSection
}
}
Expand Down
6 changes: 1 addition & 5 deletions Library/Sources/AppUIMain/Views/Modules/OnDemandView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ struct OnDemandView: View, ModuleDraftEditing {

private let wifi: Wifi

@State
private var paywallReason: PaywallReason?

init(
module: OnDemandModule.Builder,
parameters: ModuleViewParameters,
Expand All @@ -59,7 +56,6 @@ struct OnDemandView: View, ModuleDraftEditing {
rulesArea
}
.moduleView(editor: editor, draft: draft.wrappedValue)
.modifier(PaywallModifier(reason: $paywallReason))
}
}

Expand Down Expand Up @@ -95,7 +91,7 @@ private extension OnDemandView {
} label: {
HStack {
Text(Strings.Modules.OnDemand.policy)
PurchaseRequiredButton(for: module, paywallReason: $paywallReason)
PurchaseRequiredButton(for: module)
}
}
.themeSectionWithSingleRow(footer: policyFooterDescription)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ private extension ProfileCoordinator {
initialModuleId: initialModuleId,
moduleViewFactory: moduleViewFactory,
path: $path,
paywallReason: $paywallReason,
flow: .init(
onNewModule: onNewModule,
onCommitEditing: onCommitEditing,
Expand All @@ -102,7 +101,6 @@ private extension ProfileCoordinator {
profileEditor: profileEditor,
initialModuleId: initialModuleId,
moduleViewFactory: moduleViewFactory,
paywallReason: $paywallReason,
flow: .init(
onNewModule: onNewModule,
onCommitEditing: onCommitEditing,
Expand Down Expand Up @@ -138,7 +136,7 @@ private extension ProfileCoordinator {
func onCommitEditingStandard() async throws {
let savedProfile = try await profileEditor.save(to: profileManager, preferencesManager: preferencesManager)
do {
try iapManager.verify(savedProfile)
try iapManager.verify(savedProfile, isShared: profileEditor.isShared)
} catch AppError.ineligibleProfile(let requiredFeatures) {
paywallReason = .init(requiredFeatures, needsConfirmation: true)
return
Expand All @@ -149,7 +147,7 @@ private extension ProfileCoordinator {
// restricted: verify before saving
func onCommitEditingRestricted() async throws {
do {
try iapManager.verify(profileEditor.activeModules)
try iapManager.verify(profileEditor.activeModules, isShared: profileEditor.isShared)
} catch AppError.ineligibleProfile(let requiredFeatures) {
paywallReason = .init(requiredFeatures)
return
Expand Down
52 changes: 15 additions & 37 deletions Library/Sources/AppUIMain/Views/Profile/StorageSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,59 +34,40 @@ struct StorageSection: View {
@ObservedObject
var profileEditor: ProfileEditor

@Binding
var paywallReason: PaywallReason?

var body: some View {
debugChanges()
return Group {
sharingToggle
.themeRowWithSubtitle(sharingDescription)
tvToggle
.themeRowWithSubtitle(tvDescription)
purchaseButton
}
.themeSection(header: header, footer: footer)
}
}

private extension StorageSection {
var sharingToggle: some View {
Toggle(Strings.Modules.General.Rows.shared, isOn: $profileEditor.isShared)
.disabled(!iapManager.isEligible(for: .sharing))
Toggle(isOn: $profileEditor.isShared) {
HStack {
Text(Strings.Modules.General.Rows.shared)
PurchaseRequiredButton(features: profileEditor.isShared ? [.sharing] : [])
}
}
}

var tvToggle: some View {
Toggle(Strings.Modules.General.Rows.appletv(Strings.Unlocalized.appleTV), isOn: $profileEditor.isAvailableForTV)
.disabled(!iapManager.isEligible(for: .appleTV) || !profileEditor.isShared)
}

@ViewBuilder
var purchaseButton: some View {
if !iapManager.isEligible(for: .sharing) {
purchaseSharingButton
} else if !iapManager.isEligible(for: .appleTV) {
purchaseTVButton
Toggle(isOn: $profileEditor.isAvailableForTV) {
HStack {
Text(Strings.Modules.General.Rows.appletv(Strings.Unlocalized.appleTV))
PurchaseRequiredButton(features: profileEditor.isAvailableForTV ? [.appleTV] : [])
}
}
.disabled(!profileEditor.isShared)
}
}

var purchaseSharingButton: some View {
PurchaseRequiredButton(
Strings.Modules.General.Rows.Shared.purchase,
features: [.sharing],
paywallReason: $paywallReason
)
}

var purchaseTVButton: some View {
PurchaseRequiredButton(
Strings.Modules.General.Rows.Appletv.purchase,
features: [.appleTV],
suggestedProduct: .Features.appleTV,
paywallReason: $paywallReason
)
}

private extension StorageSection {
var header: String {
Strings.Modules.General.Sections.Storage.header(Strings.Unlocalized.iCloud)
}
Expand Down Expand Up @@ -119,10 +100,7 @@ private extension StorageSection {

#Preview {
Form {
StorageSection(
profileEditor: ProfileEditor(),
paywallReason: .constant(nil)
)
StorageSection(profileEditor: ProfileEditor())
}
.themeForm()
.withMockEnvironment()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ struct ProfileEditView: View, Routable {
@Binding
var path: NavigationPath

@Binding
var paywallReason: PaywallReason?

var flow: ProfileCoordinator.Flow?

@State
Expand All @@ -57,10 +54,7 @@ struct ProfileEditView: View, Routable {
name: $profileEditor.profile.name
)
modulesSection
StorageSection(
profileEditor: profileEditor,
paywallReason: $paywallReason
)
StorageSection(profileEditor: profileEditor)
UUIDSection(uuid: profileEditor.profile.id)
}
.toolbar(content: toolbarContent)
Expand Down Expand Up @@ -121,10 +115,7 @@ private extension ProfileEditView {
if errorModuleIds.contains(module.id) {
ThemeImage(.warning)
} else if profileEditor.isActiveModule(withId: module.id) {
PurchaseRequiredButton(
for: module as? AppFeatureRequiring,
paywallReason: $paywallReason
)
PurchaseRequiredButton(for: module as? AppFeatureRequiring)
}
Spacer()
}
Expand Down Expand Up @@ -193,8 +184,7 @@ private extension ProfileEditView {
profileEditor: ProfileEditor(profile: .newMockProfile()),
initialModuleId: nil,
moduleViewFactory: DefaultModuleViewFactory(registry: Registry()),
path: .constant(NavigationPath()),
paywallReason: .constant(nil)
path: .constant(NavigationPath())
)
}
.withMockEnvironment()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ struct ModuleListView: View, Routable {
@Binding
var errorModuleIds: Set<UUID>

@Binding
var paywallReason: PaywallReason?

var flow: ProfileCoordinator.Flow?

var body: some View {
Expand Down Expand Up @@ -81,10 +78,7 @@ private extension ModuleListView {
if errorModuleIds.contains(module.id) {
ThemeImage(.warning)
} else if profileEditor.isActiveModule(withId: module.id) {
PurchaseRequiredButton(
for: module as? AppFeatureRequiring,
paywallReason: $paywallReason
)
PurchaseRequiredButton(for: module as? AppFeatureRequiring)
}
Spacer()
if !isUITesting {
Expand Down Expand Up @@ -156,8 +150,7 @@ private extension ModuleListView {
ModuleListView(
profileEditor: ProfileEditor(profile: .forPreviews),
selectedModuleId: .constant(nil),
errorModuleIds: .constant([]),
paywallReason: .constant(nil)
errorModuleIds: .constant([])
)
.withMockEnvironment()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,19 @@ struct ProfileGeneralView: View {
@ObservedObject
var profileEditor: ProfileEditor

@Binding
var paywallReason: PaywallReason?

var body: some View {
Form {
ProfileNameSection(
name: $profileEditor.profile.name
)
StorageSection(
profileEditor: profileEditor,
paywallReason: $paywallReason
)
ProfileNameSection(name: $profileEditor.profile.name)
StorageSection(profileEditor: profileEditor)
UUIDSection(uuid: profileEditor.profile.id)
}
.themeForm()
}
}

#Preview {
ProfileGeneralView(
profileEditor: ProfileEditor(),
paywallReason: .constant(nil)
)
.withMockEnvironment()
ProfileGeneralView(profileEditor: ProfileEditor())
.withMockEnvironment()
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ struct ProfileSplitView: View, Routable {

let moduleViewFactory: any ModuleViewFactory

@Binding
var paywallReason: PaywallReason?

var flow: ProfileCoordinator.Flow?

@State
Expand All @@ -58,7 +55,6 @@ struct ProfileSplitView: View, Routable {
profileEditor: profileEditor,
selectedModuleId: $selectedModuleId,
errorModuleIds: $errorModuleIds,
paywallReason: $paywallReason,
flow: flow
)
.navigationSplitViewColumnWidth(200)
Expand Down Expand Up @@ -123,10 +119,7 @@ private extension ProfileSplitView {
func detailView(for detail: Detail) -> some View {
switch detail {
case .general:
ProfileGeneralView(
profileEditor: profileEditor,
paywallReason: $paywallReason
)
ProfileGeneralView(profileEditor: profileEditor)

case .module(let id):
ModuleDetailView(
Expand All @@ -142,8 +135,7 @@ private extension ProfileSplitView {
ProfileSplitView(
profileEditor: ProfileEditor(profile: .newMockProfile()),
initialModuleId: nil,
moduleViewFactory: DefaultModuleViewFactory(registry: Registry()),
paywallReason: .constant(nil)
moduleViewFactory: DefaultModuleViewFactory(registry: Registry())
)
.withMockEnvironment()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ struct ProviderContentModifier<Entity, ProviderRows>: ViewModifier where Entity:

let entityType: Entity.Type

@Binding
var paywallReason: PaywallReason?

@ViewBuilder
let providerRows: ProviderRows

Expand Down Expand Up @@ -120,8 +117,7 @@ private extension ProviderContentModifier {
providers: supportedProviders,
providerId: $providerId,
isRequired: true,
isLoading: providerManager.isLoading,
paywallReason: $paywallReason
isLoading: providerManager.isLoading
)
}
}
Expand Down Expand Up @@ -222,7 +218,6 @@ private extension ProviderContentModifier {
providerId: .constant(.hideme),
providerPreferences: nil,
entityType: VPNEntity<OpenVPN.Configuration>.self,
paywallReason: .constant(nil),
providerRows: {},
onSelectProvider: { _, _, _ in }
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ struct ProviderPicker: View {

let isLoading: Bool

@Binding
var paywallReason: PaywallReason?

var body: some View {
Picker(selection: $providerId) {
if !providers.isEmpty {
Expand All @@ -56,7 +53,7 @@ struct ProviderPicker: View {
} label: {
HStack {
Text(Strings.Global.Nouns.provider)
PurchaseRequiredButton(for: providerId, paywallReason: $paywallReason)
PurchaseRequiredButton(for: providerId)
}
}
.disabled(isLoading || providers.isEmpty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ struct VPNProviderContentModifier<Configuration, ProviderRows>: ViewModifier whe
providerId: $providerId,
providerPreferences: providerPreferences,
entityType: VPNEntity<Configuration>.self,
paywallReason: $paywallReason,
providerRows: {
providerEntityRow
providerRows
Expand Down
Loading

0 comments on commit 1ed774b

Please sign in to comment.