From 447fa4ba62bb7b2fe8efcd1601e750082691e73d Mon Sep 17 00:00:00 2001 From: Ash Furrow Date: Tue, 22 Oct 2013 20:19:34 -0400 Subject: [PATCH] Added photo detail view model. --- FRP.xcodeproj/project.pbxproj | 18 +++++++- FRP/FRPFullSizePhotoViewController.m | 8 +++- FRP/FRPFullSizePhotoViewModel.h | 1 - FRP/FRPPhotoDetailViewController.h | 6 +-- FRP/FRPPhotoDetailViewController.m | 32 ++++++------- FRP/FRPPhotoDetailViewModel.h | 29 ++++++++++++ FRP/FRPPhotoDetailViewModel.m | 69 ++++++++++++++++++++++++++++ FRP/FRPPhotoViewModel.h | 1 + Podfile | 7 ++- Podfile.lock | 10 ++-- 10 files changed, 146 insertions(+), 35 deletions(-) create mode 100644 FRP/FRPPhotoDetailViewModel.h create mode 100644 FRP/FRPPhotoDetailViewModel.m diff --git a/FRP.xcodeproj/project.pbxproj b/FRP.xcodeproj/project.pbxproj index ece6d44..f2d4b16 100644 --- a/FRP.xcodeproj/project.pbxproj +++ b/FRP.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 5E730B141815FE97003FCB43 /* FRPFullSizePhotoViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E730B131815FE97003FCB43 /* FRPFullSizePhotoViewModel.m */; }; 5EAD1F5818173A3200C67860 /* FRPPhotoViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD1F5718173A3200C67860 /* FRPPhotoViewModel.m */; }; 5EAD1F5C18173F1500C67860 /* FRPLoginViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD1F5B18173F1500C67860 /* FRPLoginViewModel.m */; }; + 5EAD1F601817418800C67860 /* FRPPhotoDetailViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD1F5F1817418800C67860 /* FRPPhotoDetailViewModel.m */; }; 5EBC599E180B247500B683A7 /* FRPCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EBC599D180B247500B683A7 /* FRPCell.m */; }; 5EBC59A6180B2AA200B683A7 /* FRPPhotoImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EBC59A5180B2AA200B683A7 /* FRPPhotoImporter.m */; }; 5EBC59A9180B2C4F00B683A7 /* FRPGalleryFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EBC59A8180B2C4F00B683A7 /* FRPGalleryFlowLayout.m */; }; @@ -66,6 +67,8 @@ 5EAD1F5718173A3200C67860 /* FRPPhotoViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoViewModel.m; sourceTree = ""; }; 5EAD1F5A18173F1500C67860 /* FRPLoginViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPLoginViewModel.h; sourceTree = ""; }; 5EAD1F5B18173F1500C67860 /* FRPLoginViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPLoginViewModel.m; sourceTree = ""; }; + 5EAD1F5E1817418800C67860 /* FRPPhotoDetailViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPPhotoDetailViewModel.h; sourceTree = ""; }; + 5EAD1F5F1817418800C67860 /* FRPPhotoDetailViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoDetailViewModel.m; sourceTree = ""; }; 5EBC599C180B247500B683A7 /* FRPCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPCell.h; sourceTree = ""; }; 5EBC599D180B247500B683A7 /* FRPCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPCell.m; sourceTree = ""; }; 5EBC59A0180B268600B683A7 /* FRPPhotoModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPPhotoModel.h; sourceTree = ""; }; @@ -131,8 +134,7 @@ 5E730B0B1815F39D003FCB43 /* Gallery */, 5E730B111815FE81003FCB43 /* Full Size Photo */, 5EAD1F5518173A1E00C67860 /* Photo View Controller */, - 5E595118180E21E0002F44FA /* FRPPhotoDetailViewController.h */, - 5E595119180E21E0002F44FA /* FRPPhotoDetailViewController.m */, + 5EAD1F5D1817417B00C67860 /* Photo Details */, 5EAD1F5918173E9700C67860 /* Login */, ); name = "View Controllers"; @@ -183,6 +185,17 @@ name = Login; sourceTree = ""; }; + 5EAD1F5D1817417B00C67860 /* Photo Details */ = { + isa = PBXGroup; + children = ( + 5E595118180E21E0002F44FA /* FRPPhotoDetailViewController.h */, + 5E595119180E21E0002F44FA /* FRPPhotoDetailViewController.m */, + 5EAD1F5E1817418800C67860 /* FRPPhotoDetailViewModel.h */, + 5EAD1F5F1817418800C67860 /* FRPPhotoDetailViewModel.m */, + ); + name = "Photo Details"; + sourceTree = ""; + }; 5EBC599F180B267400B683A7 /* UICollectionView Extensions */ = { isa = PBXGroup; children = ( @@ -451,6 +464,7 @@ 5E595111180E065F002F44FA /* FRPFullSizePhotoViewController.m in Sources */, 5EBC59A9180B2C4F00B683A7 /* FRPGalleryFlowLayout.m in Sources */, 5EAD1F5C18173F1500C67860 /* FRPLoginViewModel.m in Sources */, + 5EAD1F601817418800C67860 /* FRPPhotoDetailViewModel.m in Sources */, 5E59511D181219AC002F44FA /* FRPLoginViewController.m in Sources */, 5EBE2B03180B07D0007B6BF3 /* main.m in Sources */, 5E730B0E1815F3E4003FCB43 /* FRPGalleryViewModel.m in Sources */, diff --git a/FRP/FRPFullSizePhotoViewController.m b/FRP/FRPFullSizePhotoViewController.m index e75d935..682dbcf 100644 --- a/FRP/FRPFullSizePhotoViewController.m +++ b/FRP/FRPFullSizePhotoViewController.m @@ -15,6 +15,7 @@ #import "FRPPhotoModel.h" #import "FRPFullSizePhotoViewModel.h" #import "FRPPhotoViewModel.h" +#import "FRPPhotoDetailViewModel.h" @interface FRPFullSizePhotoViewController () @@ -55,7 +56,12 @@ return [RACSignal createSignal:^RACDisposable *(id subscriber) { @strongify(self); - FRPPhotoDetailViewController *viewController = [[FRPPhotoDetailViewController alloc] initWithPhotoModel:[self.pageViewController.viewControllers.firstObject photoModel]]; + FRPPhotoViewController *photoViewController = self.pageViewController.viewControllers.firstObject; + FRPPhotoModel *photoModel = photoViewController.viewModel.photoModel; + + FRPPhotoDetailViewModel *viewModel = [[FRPPhotoDetailViewModel alloc] initWithPhotoModel:photoModel]; + + FRPPhotoDetailViewController *viewController = [[FRPPhotoDetailViewController alloc] initWithViewModel:viewModel]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; [self presentViewController:navigationController animated:YES completion:^{ diff --git a/FRP/FRPFullSizePhotoViewModel.h b/FRP/FRPFullSizePhotoViewModel.h index 6ef4b4e..3aa044a 100644 --- a/FRP/FRPFullSizePhotoViewModel.h +++ b/FRP/FRPFullSizePhotoViewModel.h @@ -16,7 +16,6 @@ @property (nonatomic, readonly) NSInteger initialPhotoIndex; --(NSInteger)numberOfPhotos; -(FRPPhotoModel *)initialPhotoModel; -(FRPPhotoModel *)photoModelAtIndex:(NSInteger)index; diff --git a/FRP/FRPPhotoDetailViewController.h b/FRP/FRPPhotoDetailViewController.h index 1950065..292e66a 100644 --- a/FRP/FRPPhotoDetailViewController.h +++ b/FRP/FRPPhotoDetailViewController.h @@ -8,12 +8,12 @@ #import -@class FRPPhotoModel; +@class FRPPhotoDetailViewModel; @interface FRPPhotoDetailViewController : UIViewController --(instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel; +-(instancetype)initWithViewModel:(FRPPhotoDetailViewModel *)viewModel; -@property (nonatomic, readonly) FRPPhotoModel *photoModel; +@property (nonatomic, readonly) FRPPhotoDetailViewModel *viewModel; @end diff --git a/FRP/FRPPhotoDetailViewController.m b/FRP/FRPPhotoDetailViewController.m index 8c0cd70..2657bc4 100644 --- a/FRP/FRPPhotoDetailViewController.m +++ b/FRP/FRPPhotoDetailViewController.m @@ -11,27 +11,26 @@ #import "FRPLoginViewController.h" // Model -#import "FRPPhotoModel.h" +#import "FRPPhotoDetailViewModel.h" // Utilities -#import "FRPPhotoImporter.h" #import @interface FRPPhotoDetailViewController () // Private assignment -@property (nonatomic, strong) FRPPhotoModel *photoModel; +@property (nonatomic, strong) FRPPhotoDetailViewModel *viewModel; @end @implementation FRPPhotoDetailViewController --(instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel +-(instancetype)initWithViewModel:(FRPPhotoDetailViewModel *)viewModel { self = [self init]; if (!self) return nil; - self.photoModel = photoModel; + self.viewModel = viewModel; return self; } @@ -43,7 +42,7 @@ @weakify(self); // Configure self - self.title = self.photoModel.photoName; + self.title = self.viewModel.photoName; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:nil action:nil]; self.navigationItem.rightBarButtonItem.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { return [RACSignal createSignal:^RACDisposable *(id subscriber) { @@ -61,23 +60,21 @@ // Configure subviews UILabel *ratingLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 64, CGRectGetWidth(self.view.bounds), 100)]; - RAC(ratingLabel, text) = [RACObserve(self.photoModel, rating) map:^id(id value) { - return [NSString stringWithFormat:@"%0.2f", [value floatValue]]; - }]; + RAC(ratingLabel, text) = RACObserve(self.viewModel, photoRating); ratingLabel.font = [UIFont boldSystemFontOfSize:80]; ratingLabel.textColor = [UIColor whiteColor]; ratingLabel.textAlignment = NSTextAlignmentCenter; [self.view addSubview:ratingLabel]; UILabel *photoNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(ratingLabel.frame), CGRectGetWidth(self.view.bounds), 20)]; - RAC(photoNameLabel, text) = RACObserve(self.photoModel, photoName); + RAC(photoNameLabel, text) = RACObserve(self.viewModel, photoName); photoNameLabel.font = [UIFont systemFontOfSize:16]; photoNameLabel.textColor = [UIColor whiteColor]; photoNameLabel.textAlignment = NSTextAlignmentCenter; [self.view addSubview:photoNameLabel]; UILabel *photographerNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(photoNameLabel.frame), CGRectGetWidth(self.view.bounds), 20)]; - RAC(photographerNameLabel, text) = RACObserve(self.photoModel, photographerName); + RAC(photographerNameLabel, text) = RACObserve(self.viewModel, photographerName); photographerNameLabel.font = [UIFont systemFontOfSize:16]; photographerNameLabel.textColor = [UIColor colorWithWhite:0.5f alpha:1.0f]; photographerNameLabel.textAlignment = NSTextAlignmentCenter; @@ -87,14 +84,10 @@ voteButton.frame = CGRectMake(20, CGRectGetHeight(self.view.bounds) - 44 - 20, CGRectGetWidth(self.view.bounds) - 40, 44); voteButton.autoresizingMask = UIViewAutoresizingFlexibleTopMargin; // Note: can't use getter keypath - [RACObserve(self.photoModel, votedFor) subscribeNext:^(id x) { - if ([x boolValue]) { - [voteButton setTitle:@"Voted For!" forState:UIControlStateNormal]; - } else { - [voteButton setTitle:@"Vote" forState:UIControlStateNormal]; - } + [RACObserve(self.viewModel, voteButtonText) subscribeNext:^(id value) { + [voteButton setTitle:value forState:UIControlStateNormal]; }]; - voteButton.rac_command = [[RACCommand alloc] initWithEnabled:[RACObserve(self.photoModel, isVotedFor) not] signalBlock:^RACSignal *(id input) { + voteButton.rac_command = [[RACCommand alloc] initWithEnabled:self.viewModel.ableToVoteSignal signalBlock:^RACSignal *(id input) { // Assume that we're logged in at first. We'll replace this signal later if not. RACSignal *authSignal = [RACSignal empty]; @@ -120,7 +113,8 @@ return [authSignal then:^RACSignal *{ @strongify(self); - return [FRPPhotoImporter voteForPhoto:self.photoModel]; + [self.viewModel.voteCommand execute:nil]; + return [RACSignal empty]; }]; }]; [voteButton.rac_command.errors subscribeNext:^(id x) { diff --git a/FRP/FRPPhotoDetailViewModel.h b/FRP/FRPPhotoDetailViewModel.h new file mode 100644 index 0000000..9e8ebe9 --- /dev/null +++ b/FRP/FRPPhotoDetailViewModel.h @@ -0,0 +1,29 @@ +// +// FRPPhotoDetailViewModel.h +// FRP +// +// Created by Ash Furrow on 10/22/2013. +// Copyright (c) 2013 Ash Furrow. All rights reserved. +// + +#import + +@class FRPPhotoModel; + +@interface FRPPhotoDetailViewModel : NSObject + +-(instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel; + +@property (nonatomic, readonly) FRPPhotoModel *photoModel; + +@property (nonatomic, readonly) NSString *photoName; +@property (nonatomic, readonly) NSString *photoRating; +@property (nonatomic, readonly) NSString *photographerName; +@property (nonatomic, readonly) NSString *voteButtonText; + +@property (nonatomic, readonly) RACCommand *voteCommand; + +@property (nonatomic, readonly) BOOL loggedIn; +@property (nonatomic, readonly) RACSignal *ableToVoteSignal; + +@end diff --git a/FRP/FRPPhotoDetailViewModel.m b/FRP/FRPPhotoDetailViewModel.m new file mode 100644 index 0000000..1e85642 --- /dev/null +++ b/FRP/FRPPhotoDetailViewModel.m @@ -0,0 +1,69 @@ +// +// FRPPhotoDetailViewModel.m +// FRP +// +// Created by Ash Furrow on 10/22/2013. +// Copyright (c) 2013 Ash Furrow. All rights reserved. +// + +#import "FRPPhotoDetailViewModel.h" + +// Model +#import "FRPPhotoModel.h" + +// Utilities +#import "FRPPhotoImporter.h" + +@interface FRPPhotoDetailViewModel () + +@property (nonatomic, strong) FRPPhotoModel *photoModel; + +@property (nonatomic, strong) NSString *photoName; +@property (nonatomic, strong) NSString *photoRating; +@property (nonatomic, strong) NSString *photographerName; +@property (nonatomic, strong) NSString *voteButtonText; + +@property (nonatomic, strong) RACCommand *voteCommand; + +@property (nonatomic, strong) RACSignal *ableToVoteSignal; + +@end + +@implementation FRPPhotoDetailViewModel + +-(instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel { + self = [self init]; + if (!self) return nil; + + self.photoModel = photoModel; + + RAC(self, photoName) = RACObserve(self.photoModel, photoName); + RAC(self, photoRating) = [RACObserve(self.photoModel, rating) map:^id(id value) { + return [NSString stringWithFormat:@"%0.2f", [value floatValue]]; + }]; + RAC(self, photographerName) = RACObserve(self.photoModel, photographerName); + RAC(self, voteButtonText) = [RACObserve(self.photoModel, votedFor) map:^id(id value) { + if ([value boolValue]) { + return @"Voted For!"; + } else { + return @"Vote"; + } + }]; + + @weakify(self); + self.voteCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { + + @strongify(self); + return [FRPPhotoImporter voteForPhoto:self.photoModel]; + }]; + + self.ableToVoteSignal = [RACObserve(self.photoModel, votedFor) not]; + + return self; +} + +-(BOOL)loggedIn { + return [[PXRequest apiHelper] authMode] == PXAPIHelperModeOAuth; +} + +@end diff --git a/FRP/FRPPhotoViewModel.h b/FRP/FRPPhotoViewModel.h index 1a2655f..6364d03 100644 --- a/FRP/FRPPhotoViewModel.h +++ b/FRP/FRPPhotoViewModel.h @@ -14,6 +14,7 @@ -(instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel; +@property (nonatomic, readonly) FRPPhotoModel *photoModel; @property (nonatomic, readonly) RACCommand *viewDidAppearCommand; @property (nonatomic, readonly) RACSignal *photoImageSignal; diff --git a/Podfile b/Podfile index a183266..3ba4f78 100644 --- a/Podfile +++ b/Podfile @@ -2,7 +2,7 @@ platform :ios, "7.0" target "FRP" do -pod 'ReactiveCocoa', '2.1.3' +pod 'ReactiveCocoa', '2.1.4' pod 'libextobjc', '0.3' pod '500px-iOS-api', '1.0.5' pod 'SVProgressHUD', '0.9' @@ -11,12 +11,11 @@ end target "FRPTests" do -pod 'ReactiveCocoa', '2.1.3' +pod 'ReactiveCocoa', '2.1.4' pod 'libextobjc', '0.3' pod '500px-iOS-api', '1.0.5' pod 'Specta', '~> 0.1.11' pod 'Expecta', '~> 0.2' pod 'OCMock', '~> 2.0.1' -end - +end \ No newline at end of file diff --git a/Podfile.lock b/Podfile.lock index e191a92..bfefaec 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -87,12 +87,12 @@ PODS: - libextobjc/UmbrellaHeader (0.3) - libffi (3.0.13) - OCMock (2.0.1) - - ReactiveCocoa (2.1.3): + - ReactiveCocoa (2.1.4): - ReactiveCocoa/Core - ReactiveCocoa/no-arc - - ReactiveCocoa/Core (2.1.3): + - ReactiveCocoa/Core (2.1.4): - ReactiveCocoa/no-arc - - ReactiveCocoa/no-arc (2.1.3) + - ReactiveCocoa/no-arc (2.1.4) - Specta (0.1.11) - SVProgressHUD (0.9) @@ -101,7 +101,7 @@ DEPENDENCIES: - Expecta (~> 0.2) - libextobjc (= 0.3) - OCMock (~> 2.0.1) - - ReactiveCocoa (= 2.1.3) + - ReactiveCocoa (= 2.1.4) - Specta (~> 0.1.11) - SVProgressHUD (= 0.9) @@ -111,7 +111,7 @@ SPEC CHECKSUMS: libextobjc: 820a79dbbbc498611e04fffd07d2d52a5588e7ac libffi: 64ef39353e747bb2b25e1026afd96a157bf9231c OCMock: f0c099603f851d07f8d7f2efe26d05da721ec43f - ReactiveCocoa: 0142b823f4737effe6100f9fbb0a43bdcb629aae + ReactiveCocoa: 0e8725dd3c609128144c15192f4dbdab827f54ae Specta: 82746c6fd70b104c5d37cfbadb7bf15a2cbc4da0 SVProgressHUD: 03d4845ec8e64591726428a08236c6a5489d45f8