parent
239d3e6853
commit
7d7aaa8b0c
|
@ -372,6 +372,14 @@ extension View {
|
|||
.foregroundColor(themeSecondaryColor)
|
||||
}
|
||||
|
||||
func themeCellTitleStyle() -> some View {
|
||||
font(.headline)
|
||||
}
|
||||
|
||||
func themeCellSubtitleStyle() -> some View {
|
||||
font(.subheadline)
|
||||
}
|
||||
|
||||
func themeDebugLogStyle() -> some View {
|
||||
font(.system(size: 13, weight: .medium, design: .monospaced))
|
||||
}
|
||||
|
|
|
@ -45,11 +45,11 @@ extension OrganizerView {
|
|||
return HStack {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text(profile.header.name)
|
||||
.font(.headline)
|
||||
.themeCellTitleStyle()
|
||||
.themeLongTextStyle()
|
||||
|
||||
VPNStatusText(isActiveProfile: isActiveProfile)
|
||||
.font(.subheadline)
|
||||
.themeCellSubtitleStyle()
|
||||
.themeSecondaryTextStyle()
|
||||
}
|
||||
Spacer()
|
||||
|
|
|
@ -52,7 +52,10 @@ extension PaywallView {
|
|||
|
||||
var body: some View {
|
||||
List {
|
||||
productsSection
|
||||
featuresSection
|
||||
purchaseSection
|
||||
.disabled(purchaseState != nil)
|
||||
restoreSection
|
||||
.disabled(purchaseState != nil)
|
||||
}.navigationTitle(Unlocalized.appName)
|
||||
|
||||
|
@ -77,38 +80,41 @@ private struct PurchaseRow: View {
|
|||
|
||||
let title: String
|
||||
|
||||
let extra: String?
|
||||
|
||||
let action: () -> Void
|
||||
|
||||
let purchaseState: PaywallView.PurchaseView.PurchaseState?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
actionButton
|
||||
.padding(.bottom, 5)
|
||||
|
||||
extra.map {
|
||||
Text($0)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
}.padding([.top, .bottom])
|
||||
actionButton
|
||||
}
|
||||
}
|
||||
|
||||
private typealias RowModel = (product: InAppProduct, extra: String?)
|
||||
|
||||
// MARK: -
|
||||
|
||||
private extension PaywallView.PurchaseView {
|
||||
var productsSection: some View {
|
||||
var featuresSection: some View {
|
||||
Section {
|
||||
ForEach(features, id: \.productIdentifier) { product in
|
||||
VStack(alignment: .leading) {
|
||||
Text(product.localizedTitle)
|
||||
.themeCellTitleStyle()
|
||||
if product.localizedDescription != product.localizedTitle {
|
||||
Text(product.localizedDescription)
|
||||
.themeCellSubtitleStyle()
|
||||
.themeSecondaryTextStyle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var purchaseSection: some View {
|
||||
Section {
|
||||
if !productManager.isRefreshingProducts {
|
||||
ForEach(productRowModels, id: \.product.productIdentifier, content: productRow)
|
||||
ForEach(productRowModels, id: \.productIdentifier, content: productRow)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
restoreRow
|
||||
} header: {
|
||||
Text(L10n.Paywall.title)
|
||||
} footer: {
|
||||
|
@ -116,13 +122,20 @@ private extension PaywallView.PurchaseView {
|
|||
}
|
||||
}
|
||||
|
||||
func productRow(_ model: RowModel) -> some View {
|
||||
var restoreSection: some View {
|
||||
Section {
|
||||
restoreRow
|
||||
} footer: {
|
||||
Text(L10n.Paywall.Items.Restore.description)
|
||||
}
|
||||
}
|
||||
|
||||
func productRow(_ product: InAppProduct) -> some View {
|
||||
PurchaseRow(
|
||||
product: model.product,
|
||||
title: model.product.localizedTitle,
|
||||
extra: model.extra,
|
||||
product: product,
|
||||
title: product.localizedTitle,
|
||||
action: {
|
||||
purchaseProduct(model.product)
|
||||
purchaseProduct(product)
|
||||
},
|
||||
purchaseState: purchaseState
|
||||
)
|
||||
|
@ -131,7 +144,6 @@ private extension PaywallView.PurchaseView {
|
|||
var restoreRow: some View {
|
||||
PurchaseRow(
|
||||
title: L10n.Paywall.Items.Restore.title,
|
||||
extra: L10n.Paywall.Items.Restore.description,
|
||||
action: restorePurchases,
|
||||
purchaseState: purchaseState
|
||||
)
|
||||
|
@ -139,20 +151,6 @@ private extension PaywallView.PurchaseView {
|
|||
}
|
||||
|
||||
private extension PaywallView.PurchaseView {
|
||||
var skFeature: InAppProduct? {
|
||||
guard let feature = feature else {
|
||||
return nil
|
||||
}
|
||||
return productManager.product(withIdentifier: feature)
|
||||
}
|
||||
|
||||
var skPlatformVersion: InAppProduct? {
|
||||
#if targetEnvironment(macCatalyst)
|
||||
productManager.product(withIdentifier: .fullVersion_macOS)
|
||||
#else
|
||||
productManager.product(withIdentifier: .fullVersion_iOS)
|
||||
#endif
|
||||
}
|
||||
|
||||
// hide full version if already bought the other platform version
|
||||
var skFullVersion: InAppProduct? {
|
||||
|
@ -168,43 +166,18 @@ private extension PaywallView.PurchaseView {
|
|||
return productManager.product(withIdentifier: .fullVersion)
|
||||
}
|
||||
|
||||
var platformVersionExtra: [String] {
|
||||
productManager.featureProducts(excluding: [
|
||||
.fullVersion,
|
||||
.fullVersion_iOS,
|
||||
.fullVersion_macOS
|
||||
]).map {
|
||||
$0.localizedTitle
|
||||
}.sorted {
|
||||
$0.lowercased() < $1.lowercased()
|
||||
var features: [InAppProduct] {
|
||||
productManager.featureProducts(excluding: {
|
||||
$0 == .fullVersion || $0.isPlatformVersion
|
||||
})
|
||||
.sorted {
|
||||
$0.localizedTitle.lowercased() < $1.localizedTitle.lowercased()
|
||||
}
|
||||
}
|
||||
|
||||
var fullVersionExtra: [String] {
|
||||
productManager.featureProducts(including: [
|
||||
.fullVersion_iOS,
|
||||
.fullVersion_macOS
|
||||
]).map {
|
||||
$0.localizedTitle
|
||||
}.sorted {
|
||||
$0.lowercased() < $1.lowercased()
|
||||
}
|
||||
}
|
||||
|
||||
var productRowModels: [RowModel] {
|
||||
var models: [RowModel] = []
|
||||
skPlatformVersion.map {
|
||||
let extra = platformVersionExtra.joined(separator: "\n")
|
||||
models.append(($0, extra))
|
||||
}
|
||||
skFullVersion.map {
|
||||
let extra = fullVersionExtra.joined(separator: "\n")
|
||||
models.append(($0, extra))
|
||||
}
|
||||
skFeature.map {
|
||||
models.append(($0, nil))
|
||||
}
|
||||
return models
|
||||
var productRowModels: [InAppProduct] {
|
||||
[skFullVersion]
|
||||
.compactMap { $0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -132,12 +132,12 @@ public final class ProductManager: NSObject, ObservableObject {
|
|||
inApp.product(withIdentifier: identifier)
|
||||
}
|
||||
|
||||
public func featureProducts(including: [LocalProduct]) -> [InAppProduct] {
|
||||
public func featureProducts(including: (LocalProduct) -> Bool) -> [InAppProduct] {
|
||||
inApp.products().filter {
|
||||
guard let p = LocalProduct(rawValue: $0.productIdentifier) else {
|
||||
return false
|
||||
}
|
||||
guard including.contains(p) else {
|
||||
guard including(p) else {
|
||||
return false
|
||||
}
|
||||
guard p.isFeature else {
|
||||
|
@ -147,12 +147,12 @@ public final class ProductManager: NSObject, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public func featureProducts(excluding: [LocalProduct]) -> [InAppProduct] {
|
||||
public func featureProducts(excluding: (LocalProduct) -> Bool) -> [InAppProduct] {
|
||||
inApp.products().filter {
|
||||
guard let p = LocalProduct(rawValue: $0.productIdentifier) else {
|
||||
return false
|
||||
}
|
||||
guard !excluding.contains(p) else {
|
||||
guard !excluding(p) else {
|
||||
return false
|
||||
}
|
||||
guard p.isFeature else {
|
||||
|
|
Loading…
Reference in New Issue