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

Auction Banner #1023

Merged
merged 11 commits into from
Jan 14, 2016
4 changes: 4 additions & 0 deletions Artsy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@
5ECFA8E81907E26E000B92EA /* ARSwitchView+Artist.m in Sources */ = {isa = PBXBuildFile; fileRef = 5ECFA8E71907E26E000B92EA /* ARSwitchView+Artist.m */; };
5ECFA8EA1907E9AB000B92EA /* ARSwitchViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5ECFA8E91907E9AB000B92EA /* ARSwitchViewTests.m */; };
5ECFA8EF1907FC6C000B92EA /* ARSwitchView+FairGuide.m in Sources */ = {isa = PBXBuildFile; fileRef = 5ECFA8EE1907FC6C000B92EA /* ARSwitchView+FairGuide.m */; };
5ED9EC021C458E9500C979A6 /* SwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED9EC011C458E9500C979A6 /* SwiftExtensions.swift */; };
5EDB120B197E691100E241F0 /* ARParallaxHeaderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EDB120A197E691100E241F0 /* ARParallaxHeaderViewController.m */; };
5EE5DE14190167A400040B84 /* ARProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EE5DE13190167A400040B84 /* ARProfileViewController.m */; };
5EFE2BE41910FC81003B5EEA /* ARAppDelegate+Analytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EFE2BE31910FC81003B5EEA /* ARAppDelegate+Analytics.m */; };
Expand Down Expand Up @@ -1073,6 +1074,7 @@
5ECFA8E91907E9AB000B92EA /* ARSwitchViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARSwitchViewTests.m; sourceTree = "<group>"; };
5ECFA8ED1907FC6C000B92EA /* ARSwitchView+FairGuide.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ARSwitchView+FairGuide.h"; sourceTree = "<group>"; };
5ECFA8EE1907FC6C000B92EA /* ARSwitchView+FairGuide.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ARSwitchView+FairGuide.m"; sourceTree = "<group>"; };
5ED9EC011C458E9500C979A6 /* SwiftExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftExtensions.swift; sourceTree = "<group>"; };
5EDB1209197E691100E241F0 /* ARParallaxHeaderViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARParallaxHeaderViewController.h; sourceTree = "<group>"; };
5EDB120A197E691100E241F0 /* ARParallaxHeaderViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARParallaxHeaderViewController.m; sourceTree = "<group>"; };
5EE5DE12190167A400040B84 /* ARProfileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARProfileViewController.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2489,6 +2491,7 @@
60BC92F21BBEC89B00D13E56 /* ARApplicationShortcutItemDelegate.m */,
607BF5A91C2413810034B519 /* ARAppStatus.h */,
607BF5AA1C2413810034B519 /* ARAppStatus.m */,
5ED9EC011C458E9500C979A6 /* SwiftExtensions.swift */,
);
path = App;
sourceTree = "<group>";
Expand Down Expand Up @@ -4609,6 +4612,7 @@
60E6447617BE424E004486B3 /* ARSwitchView.m in Sources */,
3CF144A418E3727F00B1A764 /* UIViewController+ScreenSize.m in Sources */,
603B55A117D64A1B00566935 /* ARArtworkPriceView.m in Sources */,
5ED9EC021C458E9500C979A6 /* SwiftExtensions.swift in Sources */,
6001414817CA33C100612DB4 /* ARArtworkRelatedArtworksView.m in Sources */,
607D754A17C239E700CA1D41 /* ArtsyAPI+Following.m in Sources */,
E67655261900660500F9A704 /* ARWhitespaceGobbler.m in Sources */,
Expand Down
6 changes: 6 additions & 0 deletions Artsy/App/SwiftExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Given a closure T -> Void, returns a function that takes a T and invokes the closure.
/// Useful for performing operations with curried functions.
/// Ex: [a, b, c,].forEach(apply(self.function)) will call self.function with a, b, then c as parameters.
func apply<T>(closure: T -> Void)(instance: T) {
closure(instance)
}
2 changes: 2 additions & 0 deletions Artsy/Resources/Artsy-Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#import "ARScrollNavigationChief.h"
#import "ARWhitespaceGobbler.h"
#import "UIDevice-Hardware.h"
#import "ARCountdownView.h"

// Models. Importing Models.h is a no-go, since each header implicitly relies on a bunch of stuff imported from the PCH.
#import "Sale.h"
5 changes: 3 additions & 2 deletions Artsy/View_Controllers/Auction/AuctionNetworkModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ class AuctionNetworkModel {
self.saleID = saleID
}

func fetchSale(callback: Result<Sale> -> Void) {
func fetchSale(callback: Result<SaleViewModel> -> Void) {
ArtsyAPI.getSaleWithID(saleID,
success: { sale in
callback(.Success(sale))
let saleViewModel = SaleViewModel(sale: sale)
callback(.Success(saleViewModel))
},
failure: { error in
callback(.Failure(error))
Expand Down
25 changes: 14 additions & 11 deletions Artsy/View_Controllers/Auction/AuctionViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class AuctionViewController: UIViewController {

var stackScrollView: ORStackScrollView!

var willAppearToken: dispatch_once_t = 0

lazy var networkModel: AuctionNetworkModel = {
return AuctionNetworkModel(saleID: self.saleID)
}()
Expand Down Expand Up @@ -38,15 +40,17 @@ class AuctionViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)

ar_presentIndeterminateLoadingIndicatorAnimated(animated)
networkModel.fetchSale { result in
self.ar_removeIndeterminateLoadingIndicatorAnimated(animated)
dispatch_once(&willAppearToken) { () -> Void in
self.ar_presentIndeterminateLoadingIndicatorAnimated(animated)
self.networkModel.fetchSale { result in
self.ar_removeIndeterminateLoadingIndicatorAnimated(animated)

switch result {
case .Success(let sale):
self.setupForSale(sale)
case .Failure(_):
break // TODO: How to handle error?
switch result {
case .Success(let saleViewModel):
self.setupForSale(saleViewModel)
case .Failure(_):
break // TODO: How to handle error?
}
}
}
}
Expand All @@ -59,16 +63,15 @@ class AuctionViewController: UIViewController {
}

extension AuctionViewController {
func setupForSale(sale: Sale) {
let saleViewModel = SaleViewModel(sale: sale)
func setupForSale(saleViewModel: SaleViewModel) {
self.saleViewModel = saleViewModel

[ (AuctionBannerView(viewModel: saleViewModel), ViewTags.Banner),
(AuctionTitleView(viewModel: saleViewModel), .Title),
(ARWhitespaceGobbler(), .WhitespaceGobbler)
].forEach { (view, tag) in
view.tag = tag.rawValue
self.stackScrollView.stackView.addSubview(view, withTopMargin: "0")
self.stackScrollView.stackView.addSubview(view, withTopMargin: "0", sideMargin: "0")
}
}
}
7 changes: 7 additions & 0 deletions Artsy/Views/Artwork/Auctions/ARCountdownView.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@


@interface ARCountdownView : UIView

/// Set up countdown view with a specific colour, applied to all labels.
- (instancetype)initWithColor:(UIColor *)color;

/// Set up countdown view with basic, default colours. Useful over white backgrounds.
- (instancetype)init;

- (void)startTimer;
- (void)stopTimer;

Expand Down
18 changes: 12 additions & 6 deletions Artsy/Views/Artwork/Auctions/ARCountdownView.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,34 @@ @interface ARCountdownView ()

@implementation ARCountdownView

- (instancetype)init
- (instancetype)initWithColor:(UIColor *)color
{
self = [super init];
if (self) {
[self setupSubviews];
[self setupSubviewsWithColor:color];
}
return self;
}

- (void)setupSubviews
- (instancetype)init
{
return [self initWithColor:nil];
}

- (void)setupSubviewsWithColor:(UIColor *)color
{
self.headingLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.headingLabel.textAlignment = NSTextAlignmentCenter;
self.headingLabel.font = [UIFont smallCapsSerifFontWithSize:14];
self.headingLabel.textColor = [UIColor blackColor];
self.headingLabel.textColor = color ?: [UIColor blackColor];

[self addSubview:self.headingLabel];
[self.headingLabel alignTopEdgeWithView:self predicate:@"0"];
[self.headingLabel alignCenterXWithView:self predicate:@"0"];

self.countdown = [[UILabel alloc] initWithFrame:CGRectZero];
self.countdown.font = [UIFont sansSerifFontWithSize:20];
self.countdown.textColor = [UIColor blackColor];
self.countdown.textColor = color ?: [UIColor blackColor];
self.countdown.text = [self countdownString];
self.countdown.textAlignment = NSTextAlignmentCenter;

Expand All @@ -45,12 +50,13 @@ - (void)setupSubviews
[labelsContainer constrainTopSpaceToView:self.countdown predicate:@"0"];
[labelsContainer alignCenterXWithView:self predicate:@"0"];
[labelsContainer alignBottomEdgeWithView:self predicate:@"0"];
[labelsContainer alignLeading:@"0" trailing:@"0" toView:self];

NSMutableArray *labels = [NSMutableArray array];
[@[ @"Days", @"Hrs", @"Min", @"Sec" ] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.text = [obj uppercaseString];
label.textColor = [UIColor artsyHeavyGrey];
label.textColor = color ?: [UIColor artsyHeavyGrey];
label.font = [UIFont sansSerifFontWithSize:8];
label.textAlignment = NSTextAlignmentCenter;
[labelsContainer addSubview:label];
Expand Down
80 changes: 79 additions & 1 deletion Artsy/Views/Auction/AuctionBannerView.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,94 @@
import UIKit
import Artsy_UIColors
import FLKAutoLayout
import SDWebImage

class AuctionBannerView: UIView {
let viewModel: SaleViewModel

// Note: These are in order as they'll be in the view hierarchy (ie: first in the list is at the back)
private let backgroundImageView = UIImageView()
private let darkeningView = DarkeningView()
private let logoImageView = UIImageView()
private let countdownView = ARCountdownView(color: .whiteColor())

init(viewModel: SaleViewModel) {
self.viewModel = viewModel
super.init(frame: CGRect.zero)

backgroundColor = .redColor()
setupViews()
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func willMoveToSuperview(newSuperview: UIView?) {
super.willMoveToSuperview(newSuperview)

// Countdown view only counts down when we have a superview.
if let _ = newSuperview {
countdownView.startTimer()
} else {
countdownView.stopTimer()
}
}
}

extension AuctionBannerView {
private func setupViews() {

// Configure views.
countdownView.targetDate = viewModel.closingDate
countdownView.heading = "Closing In"

// Add all as subviews to self.
[backgroundImageView, darkeningView, logoImageView, countdownView].forEach(apply(addSubview))

// Background + darkening view always cover self totally.
backgroundImageView.alignToView(self)
darkeningView.alignToView(self)

logoImageView.constrainHeight("70")

// Device-specific layout for logo & countdown views.
if UIDevice.isPad() {
// Bottom lefthand corner with 40pt margin.
logoImageView.alignLeadingEdgeWithView(self, predicate: "40")
logoImageView.alignBottomEdgeWithView(self, predicate: "-40")

// Must constraint self to 200pt tall on iPad, since our view hierarchy doesn't provide any height constraint and in its abses, defaults to the background image's height.
constrainHeight("200")

// Bottom righthand corner with 40pt margin.
countdownView.alignTrailingEdgeWithView(self, predicate: "-40")
countdownView.alignBottomEdgeWithView(self, predicate: "-40")
} else {
logoImageView.alignTopEdgeWithView(self, predicate: "30")
countdownView.constrainTopSpaceToView(logoImageView, predicate: "7")
countdownView.alignBottomEdgeWithView(self, predicate: "-30")

// The background will stretch us to be larger (based on its image height), so we want to prevent that.
backgroundImageView.setContentCompressionResistancePriority(UILayoutPriorityDefaultLow, forAxis: .Vertical)

logoImageView.alignCenterXWithView(self, predicate: "0")
countdownView.alignCenterXWithView(self, predicate: "0")
}

// Start any necessary image downloads.
backgroundImageView.sd_setImageWithURL(viewModel.backgroundImageURL)
logoImageView.sd_setImageWithURL(viewModel.profileImageURL) { [weak logoImageView] (image, _, _, _) in
// This keeps the image view constrained to the image's aspect ratio, which allows us to 'left align' this on iPad.
let aspectRatio = image.size.width / image.size.height
logoImageView?.constrainAspectRatio("\(aspectRatio)")
}
}
}

private class DarkeningView: UIView {
private override func didMoveToSuperview() {
super.didMoveToSuperview()

backgroundColor = UIColor(white: 0, alpha: 0.3)
}
}
12 changes: 11 additions & 1 deletion Artsy/Views/Auction/SaleViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,15 @@ class SaleViewModel {
}

extension SaleViewModel {

var backgroundImageURL: NSURL! {
return NSURL(string: "https://d32dm0rphc51dk.cloudfront.net/BLv_dHIIVvShtDB8GCxFdg/large_rectangle.jpg")!
}

var profileImageURL: NSURL! {
return NSURL(string: "https://d32dm0rphc51dk.cloudfront.net/n9QgQtio1Rrp-vaKGJH7aA/square140.png")
}

var closingDate: NSDate {
return sale.endDate
}
}
1 change: 1 addition & 0 deletions CHANGELOG.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ upcoming:
- Added Dangerfile to the repo - orta
- Send dsym to hockey - orta
- Converted CHANGELOG to yaml - orta
- New (native) auction banner view - ash

releases:
- version: 2.3.3
Expand Down