Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import SwiftUI

/// Hosting controller that wraps the `StoreCreationPlanView`.
final class StoreCreationPlanHostingController: UIHostingController<StoreCreationPlanView> {
private let onPurchase: () -> Void
private let onClose: () -> Void

init(viewModel: StoreCreationPlanViewModel,
onPurchase: @escaping () -> Void,
onClose: @escaping () -> Void) {
self.onPurchase = onPurchase
self.onClose = onClose
super.init(rootView: StoreCreationPlanView(viewModel: viewModel))

rootView.onPurchase = { [weak self] in
self?.onPurchase()
}
}

@available(*, unavailable)
required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()

configureNavigationBarAppearance()
}

/// Shows a transparent navigation bar without a bottom border and with a close button to dismiss.
func configureNavigationBarAppearance() {
addCloseNavigationBarButton(target: self, action: #selector(closeButtonTapped))

let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = .withColorStudio(.wooCommercePurple, shade: .shade90)

navigationItem.standardAppearance = appearance
navigationItem.scrollEdgeAppearance = appearance
navigationItem.compactAppearance = appearance
}

@objc private func closeButtonTapped() {
onClose()
}
}

/// Displays the WPCOM eCommerce plan for purchase during the store creation flow.
struct StoreCreationPlanView: View {
/// Set in the hosting controller.
var onPurchase: (() -> Void) = {}

let viewModel: StoreCreationPlanViewModel

var body: some View {
VStack(alignment: .leading, spacing: 0) {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .center) {
VStack(alignment: .leading, spacing: 12) {
// Plan name.
Text(Localization.planTitle)
.fontWeight(.semibold)
.font(.title3)
.foregroundColor(.white)

// Price information.
HStack(alignment: .bottom) {
Text(viewModel.plan.displayPrice)
.fontWeight(.bold)
.foregroundColor(.white)
.largeTitleStyle()
Text(Localization.priceDuration)
.foregroundColor(Color(.secondaryLabel))
.bodyStyle()
}
}
.padding(.horizontal, insets: .init(top: 0, leading: 24, bottom: 0, trailing: 0))

Spacer()

Image(uiImage: .storeCreationPlanImage)
}

Divider()
.frame(height: Layout.dividerHeight)
.foregroundColor(Color(Layout.dividerColor))
.padding(.horizontal, insets: Layout.defaultPadding)

VStack(alignment: .leading, spacing: 0) {
Spacer()
.frame(height: 8)

// Header label.
Text(Localization.subtitle)
.fontWeight(.bold)
.foregroundColor(Color(.white))
.titleStyle()

Spacer()
.frame(height: 16)

// Powered by WPCOM.
HStack(spacing: 5) {
Text(Localization.poweredByWPCOMPrompt)
.foregroundColor(Color(.secondaryLabel))
.footnoteStyle()
Image(uiImage: .wpcomLogoImage)
}

Spacer()
.frame(height: 32)

// Plan features.
VStack(alignment: .leading, spacing: 16) {
ForEach(viewModel.features, id: \.title) { feature in
HStack(spacing: 12) {
Image(uiImage: feature.icon)
.renderingMode(.template)
.foregroundColor(Color(.wooCommercePurple(.shade90)))
Text(feature.title)
.foregroundColor(Color(.label))
.bodyStyle()
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: What do you think about breaking the body down into smaller views? For example, this part can be a separate featuresListView View.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure 👍 updated in 2ddae25

}
.padding(Layout.defaultPadding)
}
}

VStack(spacing: 0) {
Divider()
.frame(height: Layout.dividerHeight)
.foregroundColor(Color(Layout.dividerColor))

// Continue button.
Button(String(format: Localization.continueButtonTitleFormat, viewModel.plan.displayPrice)) {
onPurchase()
}
.buttonStyle(PrimaryButtonStyle())
.padding(Layout.defaultButtonPadding)

// Refund information.
Text(Localization.refundableNote)
.multilineTextAlignment(.center)
.foregroundColor(Color(.secondaryLabel))
.bodyStyle()

Spacer()
.frame(height: 24)
}
}
.background(Color(.withColorStudio(.wooCommercePurple, shade: .shade90)))
// This screen is using the dark theme for both light and dark modes.
.environment(\.colorScheme, .dark)
}
}

private extension StoreCreationPlanView {
enum Layout {
static let dividerHeight: CGFloat = 1
static let defaultPadding: EdgeInsets = .init(top: 16, leading: 16, bottom: 16, trailing: 16)
static let defaultButtonPadding: EdgeInsets = .init(top: 16, leading: 16, bottom: 16, trailing: 16)
static let dividerColor: UIColor = .separator
}

enum Localization {
static let planTitle = NSLocalizedString(
"eCommerce",
comment: "Title of the store creation plan on the plan screen.")
static let priceDuration = NSLocalizedString(
"/month",
comment: "The text is preceded by the monthly price on the store creation plan screen.")
static let subtitle = NSLocalizedString(
"All the featues you need, already built in",
comment: "Subtitle of the store creation plan screen.")
static let poweredByWPCOMPrompt = NSLocalizedString(
"Powered by",
comment: "The text is followed by a WordPress.com logo on the store creation plan screen.")
static let continueButtonTitleFormat = NSLocalizedString(
"Create Store for %1$@/month",
comment: "Title of the button on the store creation plan view to purchase the plan. " +
"%1$@ is replaced by the monthly price."
)
static let refundableNote = NSLocalizedString(
"There’s no risk, you can cancel for a full refund within 30 days.",
comment: "Refund policy under the purchase button on the store creation plan screen."
)
}
}

#if DEBUG

/// Only used for `StoreCreationPlanView` preview.
private struct Plan: WPComPlanProduct {
let displayName: String
let description: String
let id: String
let displayPrice: String
}

struct StoreCreationPlanView_Previews: PreviewProvider {
static var previews: some View {
StoreCreationPlanView(viewModel:
.init(plan: Plan(displayName: "Debug Monthly",
description: "1 Month of Debug Woo",
id: "debug.woocommerce.ecommerce.monthly",
displayPrice: "$69.99")))
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import UIKit

/// View model for `StoreCreationPlanView`.
struct StoreCreationPlanViewModel {
/// Describes a feature for the WPCOM plan with an icon.
struct Feature {
let icon: UIImage
let title: String
}

/// The WPCOM plan to purchase.
let plan: WPComPlanProduct

/// A list of features included in the WPCOM plan.
let features: [Feature] = [
.init(icon: .gridicon(.starOutline), title: Localization.themeFeature),
.init(icon: .gridicon(.product), title: Localization.productsFeature),
.init(icon: .gridicon(.gift), title: Localization.subscriptionsFeature),
.init(icon: .gridicon(.statsAlt), title: Localization.reportFeature),
// TODO: 8108 - update icon
.init(icon: .gridicon(.money), title: Localization.paymentOptionsFeature),
.init(icon: .gridicon(.shipping), title: Localization.shippingLabelsFeature),
.init(icon: .megaphoneIcon, title: Localization.salesChannelsFeature),
]
}

private extension StoreCreationPlanViewModel {
enum Localization {
static let themeFeature = NSLocalizedString(
"Premium themes",
comment: "Title of eCommerce plan feature on the store creation plan screen."
)
static let productsFeature = NSLocalizedString(
"Unlimited products",
comment: "Title of eCommerce plan feature on the store creation plan screen."
)
static let subscriptionsFeature = NSLocalizedString(
"Subscriptions & giftcards",
comment: "Title of eCommerce plan feature on the store creation plan screen."
)
static let reportFeature = NSLocalizedString(
"Ecommerce reports",
comment: "Title of eCommerce plan feature on the store creation plan screen."
)
static let paymentOptionsFeature = NSLocalizedString(
"Multiple payment options",
comment: "Title of eCommerce plan feature on the store creation plan screen."
)
static let shippingLabelsFeature = NSLocalizedString(
"Shipping labels",
comment: "Title of eCommerce plan feature on the store creation plan screen."
)
static let salesChannelsFeature = NSLocalizedString(
"Sales channels",
comment: "Title of eCommerce plan feature on the store creation plan screen."
)
}
}
12 changes: 12 additions & 0 deletions WooCommerce/Classes/Extensions/UIImage+Woo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,12 @@ extension UIImage {
return UIImage(named: "icon-mailbox")!
}

/// Store plan image used in the store creation flow.
///
static var storeCreationPlanImage: UIImage {
UIImage(named: "store-creation-plan")!
}

/// Store Image
///
static var storeImage: UIImage {
Expand Down Expand Up @@ -1077,6 +1083,12 @@ extension UIImage {
static var sitesImage: UIImage {
UIImage.gridicon(.site).imageFlippedForRightToLeftLayoutDirection()
}

/// WordPress.com logo image.
///
static var wpcomLogoImage: UIImage {
UIImage(named: "wpcom-logo")!
}
}

private extension UIImage {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "store-creation-plan.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "wpcom-logo.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Binary file not shown.
Loading