Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NT-889] (1/3) Landing page ui #1080

Merged
merged 11 commits into from
Feb 28, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions Kickstarter-iOS/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ internal final class AppDelegate: UIResponder, UIApplicationDelegate {
.observeForUI()
.observeValues { UIApplication.shared.open($0) }

self.viewModel.outputs.goToLandingPage
.observeForUI()
.observeValues { [weak self] in
let landingPage = LandingPageViewController()
|> \.modalPresentationStyle .~ .fullScreen
self?.rootTabBarController?.present(landingPage, animated: true)
Copy link
Contributor

Choose a reason for hiding this comment

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

@Scollaco I think for the landing page we want it to be presented as a full screen modal - on iOS 13 the automatic modalPresentationStyle is pageSheet, so you'll want to be specific and set it to fullScreen so that it presents as full screen on iOS 13.

}

self.viewModel.outputs.applicationIconBadgeNumber
.observeForUI()
.observeValues { UIApplication.shared.applicationIconBadgeNumber = $0 }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "1x"
},
{
"idiom" : "iphone",
"filename" : "landing-page-background@2x.png",
"scale" : "2x"
},
{
"idiom" : "iphone",
"filename" : "landing-page-background@3x.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"scale" : "1x"
},
{
"idiom" : "ipad",
"filename" : "landing-page-background-iPad@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions Kickstarter-iOS/ViewModels/AppDelegateViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ public protocol AppDelegateViewModelOutputs {
/// Emits when the root view controller should navigate to the creator dashboard.
var goToDiscovery: Signal<DiscoveryParams?, Never> { get }

/// Emits when the root view controller should present the Landing Page for new users.
var goToLandingPage: Signal<(), Never> { get }

/// Emits when the root view controller should navigate to the login screen.
var goToLogin: Signal<(), Never> { get }

Expand Down Expand Up @@ -369,6 +372,14 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi
.filter { $0 == .tab(.search) }
.ignoreValues()

self.goToLandingPage = self.applicationLaunchOptionsProperty.signal.ignoreValues()
.takeWhen(
self.optimizelyConfiguredWithResultProperty.signal
.skipNil()
.filter { $0.isSuccess }
.ignoreValues()
)

self.goToLogin = deepLink
.filter { $0 == .tab(.login) }
.ignoreValues()
Expand Down Expand Up @@ -801,6 +812,7 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi
public let goToCreatorMessageThread: Signal<(Param, MessageThread), Never>
public let goToDashboard: Signal<Param?, Never>
public let goToDiscovery: Signal<DiscoveryParams?, Never>
public let goToLandingPage: Signal<(), Never>
public let goToLogin: Signal<(), Never>
public let goToMessageThread: Signal<MessageThread, Never>
public let goToProfile: Signal<(), Never>
Expand Down
250 changes: 250 additions & 0 deletions Kickstarter-iOS/Views/Controllers/LandingPageViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import Library
import Prelude
import ReactiveSwift
import UIKit

private enum Layout {
enum Button {
static let height: CGFloat = 54
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason we're not using our Styles.minTouchSize.height for buttons here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh good catch!

}

enum ImageView {
static let width: CGFloat = 226
}

enum Card {
static let height: CGFloat = 122
}
}

public final class LandingPageViewController: UIViewController {
// MARK: - Properties

private let backgroundImageView: UIImageView = { UIImageView(frame: .zero) }()
private let cardsScrollView: UIScrollView = {
UIScrollView(frame: .zero)
|> \.translatesAutoresizingMaskIntoConstraints .~ false
}()

private let cardsStackView: UIStackView = { UIStackView(frame: .zero) }()
private let cardViewsStackView: UIStackView = { UIStackView(frame: .zero) }()
private let ctaButton: UIButton = { UIButton(frame: .zero) }()
private let descriptionLabel: UILabel = { UILabel(frame: .zero) }()
private let labelsStackView: UIStackView = { UIStackView(frame: .zero) }()
private let logoImageView: UIImageView = { UIImageView(frame: .zero) }()
private let pageControl: UIPageControl = { UIPageControl(frame: .zero) }()
private let rootStackView: UIStackView = { UIStackView(frame: .zero) }()
private let scrollView: UIScrollView = { UIScrollView(frame: .zero) }()
private let titleLabel: UILabel = { UILabel(frame: .zero) }()
private let viewModel: LandingPageViewModelType = LandingPageViewModel()

// MARK: - Life cycle

public override func viewDidLoad() {
super.viewDidLoad()

self.configureViews()
self.setupConstraints()
self.ctaButton.addTarget(
self,
action: #selector(LandingPageViewController.ctaButtonTapped),
for: .touchUpInside
)

self.viewModel.inputs.viewDidLoad()
}

public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}

// MARK: - View Model

public override func bindViewModel() {
super.bindViewModel()

self.viewModel.outputs.landingPageCards
.observeForUI()
.observeValues { [weak self] cards in
self?.configureCards(with: cards)
}
}

// MARK: - Styles

public override func bindStyles() {
super.bindStyles()

_ = self.view
|> viewStyle

_ = self.backgroundImageView
|> backgroundImageViewStyle

_ = self.cardsStackView
|> cardsStackViewStyle

_ = self.ctaButton
|> ctaButtonStyle

_ = self.descriptionLabel
|> descriptionLabelStyle

_ = self.labelsStackView
|> labelsStackViewStyle

_ = self.logoImageView
|> logoImageViewStyle

_ = self.rootStackView
|> rootStackViewStyle

_ = self.cardsScrollView
|> cardsScrollViewStyle
|> \.delegate .~ self

_ = self.titleLabel
|> titleLabelStyle
}

// MARK: - Configuration

private func configureViews() {
_ = (self.backgroundImageView, self.view)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()

_ = (self.scrollView, self.view)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToMarginsInParent()

_ = ([self.logoImageView, self.titleLabel, self.descriptionLabel], self.labelsStackView)
|> ksr_addArrangedSubviewsToStackView()

_ = (self.cardViewsStackView, self.cardsScrollView)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()

_ = ([self.cardsScrollView, self.pageControl], self.cardsStackView)
|> ksr_addArrangedSubviewsToStackView()

let spacer1 = UIView()

_ = ([
spacer1,
self.labelsStackView,
self.cardsStackView
], self.rootStackView)
|> ksr_addArrangedSubviewsToStackView()

_ = (self.ctaButton, self.view)
|> ksr_addSubviewToParent()

_ = (self.rootStackView, self.scrollView)
|> ksr_addSubviewToParent()
|> ksr_constrainViewToEdgesInParent()
}

private func setupConstraints() {
NSLayoutConstraint.activate([
self.ctaButton.heightAnchor.constraint(equalToConstant: Layout.Button.height),
self.ctaButton.leftAnchor.constraint(equalTo: self.scrollView.leftAnchor),
self.ctaButton.rightAnchor.constraint(equalTo: self.scrollView.rightAnchor),
self.ctaButton.bottomAnchor.constraint(equalTo: self.view.layoutMarginsGuide.bottomAnchor),
self.cardsScrollView.heightAnchor.constraint(greaterThanOrEqualToConstant: Layout.Card.height),
self.cardViewsStackView.heightAnchor.constraint(equalTo: self.cardsScrollView.heightAnchor),
self.rootStackView.widthAnchor.constraint(equalTo: self.scrollView.widthAnchor)
])
}

@objc private func ctaButtonTapped() {
self.dismiss(animated: true)
}

private func configureCards(with _: [UIView]) {}
}

// Styles
private let backgroundImageViewStyle: ImageViewStyle = { imageView in
imageView
|> \.contentMode .~ .scaleToFill
|> \.image .~ image(named: "landing-page-background")
}

private let cardsScrollViewStyle: ScrollStyle = { scrollView in
scrollView
|> \.bounces .~ false
|> \.isPagingEnabled .~ true
}

private let cardsStackViewStyle: StackViewStyle = { stackView in
stackView
|> verticalStackViewStyle
}

private let ctaButtonStyle: ButtonStyle = { button in
button
|> greenButtonStyle
|> UIButton.lens.title(for: .normal) %~ { _ in
localizedString(key: "Get_started", defaultValue: "Get started")
}
|> \.translatesAutoresizingMaskIntoConstraints .~ false
}

private let descriptionLabelStyle: LabelStyle = { label in
label
|> \.numberOfLines .~ 0
|> \.textColor .~ UIColor.ksr_text_dark_grey_500
|> \.font .~ UIFont.ksr_callout()
|> \.textAlignment .~ .center
|> \.text %~ { _ in
Strings.Pledge_to_projects_and_view_all_your_saved_and_backed_projects_in_one_place()
}
}

private let labelsStackViewStyle: StackViewStyle = { stackView in
stackView
|> verticalStackViewStyle
|> \.spacing .~ Styles.grid(3)
}

private let logoImageViewStyle: ImageViewStyle = { imageView in
imageView
|> \.image .~ image(named: "kickstarter-logo")?.withRenderingMode(.alwaysTemplate)
|> \.tintColor .~ .ksr_green_400
|> \.contentMode .~ .scaleAspectFit
|> \.translatesAutoresizingMaskIntoConstraints .~ false
}

private let rootStackViewStyle: StackViewStyle = { stackView in
stackView
|> verticalStackViewStyle
|> \.spacing .~ Styles.grid(5)
|> \.translatesAutoresizingMaskIntoConstraints .~ false
|> \.isLayoutMarginsRelativeArrangement .~ true
|> \.layoutMargins .~ .init(top: Styles.grid(15), left: 0, bottom: Styles.grid(3), right: 0)
|> \.distribution .~ .equalSpacing
}

private let titleLabelStyle: LabelStyle = { label in
label
|> \.text %~ { _ in Strings.Bring_creative_projects_to_life() }
|> \.font .~ UIFont.ksr_title3().bolded
|> \.textAlignment .~ .center
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to this to wrap with larger font sizes? Right now it's truncating.

Simulator Screen Shot - iPhone X - 2020-02-26 at 13 14 47

|> \.numberOfLines .~ 0
}

private let viewStyle: ViewStyle = { view in
view
|> \.layoutMargins .~ UIEdgeInsets.init(bottom: Styles.grid(3))
}

extension LandingPageViewController: UIScrollViewDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = scrollView.bounds.width
let pageFraction = scrollView.contentOffset.x / pageWidth

_ = self.pageControl
|> \.currentPage .~ Int(round(pageFraction))
}
}
Loading