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