Skip to content

Commit

Permalink
Merge pull request #218 from cocoatype/129-improve-paywall-design
Browse files Browse the repository at this point in the history
Improve paywall design
  • Loading branch information
Arclite authored Dec 2, 2024
2 parents e57e6ef + ae65196 commit 6fd3067
Show file tree
Hide file tree
Showing 18 changed files with 297 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
// Text separating the two purchase buttons
"PurchaseButtonSeparator.text" = "or";

"PurchaseMarketingFooterPrivacyLink.shortTitle" = "Privacy";
"PurchaseMarketingFooterPrivacyLink.title" = "Privacy Policy";

"PurchaseMarketingFooterRestoreLink.shortTitle" = "Restore";
"PurchaseMarketingFooterRestoreLink.title" = "Restore Purchases";

// Text for the headline in purchase marketing
"PurchaseMarketingTopBarHeadlineLabel.text" = "Ultra Highlighter";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Created by Geoff Pado on 12/2/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import ErrorHandling
import Purchasing
import SwiftUI

@available(iOS 16.0, *)
struct PurchaseMarketingFooter: View {
var body: some View {
PurchaseMarketingFooterContents()
.frame(maxWidth: .infinity, minHeight: 140)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Created by Geoff Pado on 12/2/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import Purchasing
import SwiftUI

@available(iOS 16.0, *)
struct PurchaseMarketingFooterContents: View {
var body: some View {
VStack(spacing: 20) {
PurchaseMarketingFooterPurchaseButton()
PurchaseMarketingFooterLinkSection()
}.padding()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Created by Geoff Pado on 12/2/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import SwiftUI

@available(iOS 16.0, *)
struct PurchaseMarketingFooterLink: View {
private let title: String
private let action: () -> Void
init(title: String, action: @escaping () -> Void) {
self.title = title
self.action = action
}

var body: some View {
Button(title, action: action)
.buttonStyle(.plain)
.lineLimit(nil)
.font(.footnote)
.tint(.white)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Created by Geoff Pado on 12/2/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import SwiftUI

@available(iOS 16.0, *)
struct PurchaseMarketingFooterLinkSection: View {
var body: some View {
ViewThatFits(in: .horizontal) {
// Restore Purchases — Terms & Conditions — Privacy Policy
HStack {
PurchaseMarketingFooterRestoreLink(usesShortTitle: false)
PurchaseMarketingFooterLinkSeparator()
PurchaseMarketingFooterPrivacyLink(usesShortTitle: false)
}

// Restore — Terms & Conditions — Privacy Policy
HStack {
PurchaseMarketingFooterRestoreLink(usesShortTitle: true)
PurchaseMarketingFooterLinkSeparator()
PurchaseMarketingFooterPrivacyLink(usesShortTitle: false)
}

// Restore — Terms & Conditions — Privacy
HStack {
PurchaseMarketingFooterRestoreLink(usesShortTitle: true)
PurchaseMarketingFooterLinkSeparator()
PurchaseMarketingFooterPrivacyLink(usesShortTitle: true)
}

// Restore — Terms — Privacy
HStack {
PurchaseMarketingFooterRestoreLink(usesShortTitle: true)
PurchaseMarketingFooterLinkSeparator()
PurchaseMarketingFooterPrivacyLink(usesShortTitle: true)
}

// Restore Purchases — Terms & Conditions — Privacy Policy
VStack {
PurchaseMarketingFooterRestoreLink(usesShortTitle: false)
PurchaseMarketingFooterLinkSeparator()
PurchaseMarketingFooterPrivacyLink(usesShortTitle: false)
}
.frame(maxWidth: .infinity)

// Restore — Terms & Conditions — Privacy Policy
VStack {
PurchaseMarketingFooterRestoreLink(usesShortTitle: true)
PurchaseMarketingFooterLinkSeparator()
PurchaseMarketingFooterPrivacyLink(usesShortTitle: false)
}
.frame(maxWidth: .infinity)

// Restore — Terms & Conditions — Privacy
VStack {
PurchaseMarketingFooterRestoreLink(usesShortTitle: true)
PurchaseMarketingFooterLinkSeparator()
PurchaseMarketingFooterPrivacyLink(usesShortTitle: true)
}
.frame(maxWidth: .infinity)

// Restore — Terms — Privacy
VStack {
PurchaseMarketingFooterRestoreLink(usesShortTitle: true)
PurchaseMarketingFooterLinkSeparator()
PurchaseMarketingFooterPrivacyLink(usesShortTitle: true)
}
.frame(maxWidth: .infinity)
}
}
}

@available(iOS 17.0, *)
#Preview(traits: .sizeThatFitsLayout) {
PurchaseMarketingFooterLinkSection()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Created by Geoff Pado on 12/2/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import SwiftUI

@available(iOS 16.0, *)
struct PurchaseMarketingFooterLinkSeparator: View {
var body: some View {
Text(verbatim: "")
.accessibilityHidden(true)
.font(.footnote)
.foregroundStyle(.white)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Created by Geoff Pado on 12/2/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import SwiftUI

@available(iOS 16.0, *)
struct PurchaseMarketingFooterPrivacyLink: View {
private let usesShortTitle: Bool
init(usesShortTitle: Bool) {
self.usesShortTitle = usesShortTitle
}

@Environment(\.openURL) private var openURL
private func openPrivacy() {
guard let privacyURL = URL(string: "https://blackhighlighter.app/privacy/") else { return }
openURL(privacyURL)
}

var body: some View {
if usesShortTitle {
PurchaseMarketingFooterLink(title: PurchaseMarketingStrings.PurchaseMarketingFooterPrivacyLink.shortTitle, action: openPrivacy)
} else {
PurchaseMarketingFooterLink(title: PurchaseMarketingStrings.PurchaseMarketingFooterPrivacyLink.title, action: openPrivacy)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Created by Geoff Pado on 12/2/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import DesignSystem
import ErrorHandling
import Purchasing
import SwiftUI

@available(iOS 16.0, *)
struct PurchaseMarketingFooterPurchaseButton: View {
@State private var purchaseState: PurchaseState

// allWeAskIsThatYouLetUsHaveItYourWay by @AdamWulf on 2024-05-15
private let allWeAskIsThatYouLetUsHaveItYourWay: any PurchaseRepository
private let errorHandler = ErrorHandler()
init(
purchaseRepository: any PurchaseRepository = Purchasing.repository
) {
_purchaseState = State<PurchaseState>(initialValue: purchaseRepository.withCheese)
self.allWeAskIsThatYouLetUsHaveItYourWay = purchaseRepository
}

var body: some View {
Button {
guard purchaseState.isReadyForPurchase else { return }
purchaseState = .purchasing
Task {
purchaseState = await allWeAskIsThatYouLetUsHaveItYourWay.purchase()
}
} label: {
Text(title)
.fontWeight(.bold)
.foregroundStyle(Color.black)
.padding(12)
.frame(maxWidth: .infinity, minHeight: 44)
.background {
RoundedRectangle(cornerRadius: 8)
.fill(Color.white)
}
}
.buttonStyle(.plain)
.disabled(disabled)
.onReceive(allWeAskIsThatYouLetUsHaveItYourWay.purchaseStates.eraseToAnyPublisher()) { newState in
purchaseState = newState
}
}

private var title: String {
switch purchaseState {
case .loading:
return Strings.loadingTitle
case .purchasing, .restoring:
return Strings.purchasingTitle
case .readyForPurchase(let product):
return Strings.readyTitle(product.displayPrice)
case .unavailable:
return Strings.loadingTitle
case .purchased:
return Strings.purchasedTitle
}
}

private var disabled: Bool {
switch purchaseState {
case .readyForPurchase: return false
default: return true
}
}

private typealias Strings = PurchaseMarketingStrings.PurchaseButton
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Created by Geoff Pado on 12/2/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import StoreKit
import SwiftUI

@available(iOS 16.0, *)
struct PurchaseMarketingFooterRestoreLink: View {
private let usesShortTitle: Bool
init(usesShortTitle: Bool) {
self.usesShortTitle = usesShortTitle
}

private func restore() {
Task {
try await AppStore.sync()
}
}

var body: some View {
if usesShortTitle {
PurchaseMarketingFooterLink(title: PurchaseMarketingStrings.PurchaseMarketingFooterRestoreLink.shortTitle, action: restore)
} else {
PurchaseMarketingFooterLink(title: PurchaseMarketingStrings.PurchaseMarketingFooterRestoreLink.title, action: restore)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import SwiftUI
import UIKit

@available(iOS 16.0, *)
public class PurchaseMarketingHostingController: UIHostingController<PurchaseMarketingView> {
public init() {
super.init(rootView: PurchaseMarketingView())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import DesignSystem
import SwiftUI

@available(iOS 16.0, *)
public struct PurchaseMarketingView: View {
@Environment(\.horizontalSizeClass) var horizontalSizeClass

Expand Down Expand Up @@ -50,6 +51,9 @@ public struct PurchaseMarketingView: View {
}
.fill()
.navigationBarHidden(true)
}.safeAreaInset(edge: .bottom) {
PurchaseMarketingFooter()
.background(Color.appPrimary, ignoresSafeAreaEdges: .bottom)
}
}

Expand All @@ -75,11 +79,12 @@ public struct PurchaseMarketingView: View {
private typealias Strings = PurchaseMarketingStrings.PurchaseMarketingView
}

enum PurchaseMarketingView_Previews: PreviewProvider {
static var previews: some View {
PurchaseMarketingView()
.preferredColorScheme(.dark)
.environment(\.readableWidth, 288)
// .previewLayout(.fixed(width: 640, height: 1024))
}
@available(iOS 16.0, *)
#Preview {
Color.black
.ignoresSafeArea()
.sheet(isPresented: .constant(true)) {
PurchaseMarketingView()
.frame(width: 640)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ struct PurchaseMarketingTopBarCompact: View {
VStack(alignment: .leading, spacing: 4) {
PurchaseMarketingTopBarHeadline()
PurchaseMarketingTopBarSubheadline()
if #available(iOS 15, *) {
PurchaseMarketingTopBarButtonStack()
} else {
LegacyPurchaseMarketingTopBarButtonStack()
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(EdgeInsets(top: 40, leading: 20, bottom: 20, trailing: 20))
Expand Down
Loading

0 comments on commit 6fd3067

Please sign in to comment.