├── .gitignore ├── FRP.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── ash.xcuserdatad │ └── xcschemes │ ├── FRP.xcscheme │ └── xcschememanagement.plist ├── FRP.xcworkspace └── contents.xcworkspacedata ├── FRP ├── FRP-Info.plist ├── FRP-Prefix.pch ├── FRPAppDelegate.h ├── FRPAppDelegate.m ├── FRPCell.h ├── FRPCell.m ├── FRPFullSizePhotoViewController.h ├── FRPFullSizePhotoViewController.m ├── FRPFullSizePhotoViewModel.h ├── FRPFullSizePhotoViewModel.m ├── FRPGalleryFlowLayout.h ├── FRPGalleryFlowLayout.m ├── FRPGalleryViewController.h ├── FRPGalleryViewController.m ├── FRPGalleryViewModel.h ├── FRPGalleryViewModel.m ├── FRPLoginViewController.h ├── FRPLoginViewController.m ├── FRPLoginViewController.xib ├── FRPLoginViewModel.h ├── FRPLoginViewModel.m ├── FRPPhotoDetailViewController.h ├── FRPPhotoDetailViewController.m ├── FRPPhotoDetailViewModel.h ├── FRPPhotoDetailViewModel.m ├── FRPPhotoImporter.h ├── FRPPhotoImporter.m ├── FRPPhotoModel.h ├── FRPPhotoModel.m ├── FRPPhotoViewController.h ├── FRPPhotoViewController.m ├── FRPPhotoViewModel.h ├── FRPPhotoViewModel.m ├── en.lproj │ └── InfoPlist.strings └── main.m ├── FRPTests ├── FRPFullSizePhotoViewModelTests.m ├── FRPGalleryViewModelTests.m ├── FRPLoginViewModelTests.m ├── FRPPhotoImporterTests.m ├── FRPPhotoViewModelTests.m ├── FRPTests-Info.plist └── en.lproj │ └── InfoPlist.strings ├── LICENSE ├── Podfile ├── Podfile.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods 22 | -------------------------------------------------------------------------------- /FRP.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 17A5BBDB18BB2F6D000E2D24 /* FRPLoginViewModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 17A5BBDA18BB2F6D000E2D24 /* FRPLoginViewModelTests.m */; }; 11 | 17E5A10E18BB43C600AFC028 /* FRPPhotoImporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E5A10D18BB43C600AFC028 /* FRPPhotoImporterTests.m */; }; 12 | 2826EB375D254DDD8DE6EAA8 /* libPods-FRP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 772BE9E4C5824F1C8E5CDC45 /* libPods-FRP.a */; }; 13 | 5E595111180E065F002F44FA /* FRPFullSizePhotoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E595110180E065F002F44FA /* FRPFullSizePhotoViewController.m */; }; 14 | 5E595117180E0C33002F44FA /* FRPPhotoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E595116180E0C33002F44FA /* FRPPhotoViewController.m */; }; 15 | 5E59511A180E21E0002F44FA /* FRPPhotoDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E595119180E21E0002F44FA /* FRPPhotoDetailViewController.m */; }; 16 | 5E59511D181219AC002F44FA /* FRPLoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E59511C181219AC002F44FA /* FRPLoginViewController.m */; }; 17 | 5E59511F18121D09002F44FA /* FRPLoginViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5E59511E18121D09002F44FA /* FRPLoginViewController.xib */; }; 18 | 5E730B0E1815F3E4003FCB43 /* FRPGalleryViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E730B0D1815F3E4003FCB43 /* FRPGalleryViewModel.m */; }; 19 | 5E730B101815F78B003FCB43 /* FRPGalleryViewModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E730B0F1815F78B003FCB43 /* FRPGalleryViewModelTests.m */; }; 20 | 5E730B141815FE97003FCB43 /* FRPFullSizePhotoViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E730B131815FE97003FCB43 /* FRPFullSizePhotoViewModel.m */; }; 21 | 5E93AD9A186C781000795C9E /* FRPFullSizePhotoViewModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E93AD99186C781000795C9E /* FRPFullSizePhotoViewModelTests.m */; }; 22 | 5E93AD9C186C80DE00795C9E /* FRPPhotoViewModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E93AD9B186C80DE00795C9E /* FRPPhotoViewModelTests.m */; }; 23 | 5EAD1F5818173A3200C67860 /* FRPPhotoViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD1F5718173A3200C67860 /* FRPPhotoViewModel.m */; }; 24 | 5EAD1F5C18173F1500C67860 /* FRPLoginViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD1F5B18173F1500C67860 /* FRPLoginViewModel.m */; }; 25 | 5EAD1F601817418800C67860 /* FRPPhotoDetailViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD1F5F1817418800C67860 /* FRPPhotoDetailViewModel.m */; }; 26 | 5EBC599E180B247500B683A7 /* FRPCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EBC599D180B247500B683A7 /* FRPCell.m */; }; 27 | 5EBC59A6180B2AA200B683A7 /* FRPPhotoImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EBC59A5180B2AA200B683A7 /* FRPPhotoImporter.m */; }; 28 | 5EBC59A9180B2C4F00B683A7 /* FRPGalleryFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EBC59A8180B2C4F00B683A7 /* FRPGalleryFlowLayout.m */; }; 29 | 5EBE2AF5180B07D0007B6BF3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EBE2AF4180B07D0007B6BF3 /* Foundation.framework */; }; 30 | 5EBE2AF7180B07D0007B6BF3 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EBE2AF6180B07D0007B6BF3 /* CoreGraphics.framework */; }; 31 | 5EBE2AF9180B07D0007B6BF3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EBE2AF8180B07D0007B6BF3 /* UIKit.framework */; }; 32 | 5EBE2B01180B07D0007B6BF3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5EBE2AFF180B07D0007B6BF3 /* InfoPlist.strings */; }; 33 | 5EBE2B03180B07D0007B6BF3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EBE2B02180B07D0007B6BF3 /* main.m */; }; 34 | 5EBE2B07180B07D0007B6BF3 /* FRPAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EBE2B06180B07D0007B6BF3 /* FRPAppDelegate.m */; }; 35 | 5EBE2B13180B07D0007B6BF3 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EBE2B12180B07D0007B6BF3 /* XCTest.framework */; }; 36 | 5EBE2B14180B07D0007B6BF3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EBE2AF4180B07D0007B6BF3 /* Foundation.framework */; }; 37 | 5EBE2B15180B07D0007B6BF3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EBE2AF8180B07D0007B6BF3 /* UIKit.framework */; }; 38 | 5EBE2B16180B07D0007B6BF3 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EBE2AFA180B07D0007B6BF3 /* CoreData.framework */; }; 39 | 5EBE2B1E180B07D0007B6BF3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5EBE2B1C180B07D0007B6BF3 /* InfoPlist.strings */; }; 40 | 5EBE2B2B180B0AF1007B6BF3 /* FRPGalleryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EBE2B2A180B0AF1007B6BF3 /* FRPGalleryViewController.m */; }; 41 | F5C87C1873574C63889236C3 /* libPods-FRPTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5F89E0211BE405E85BE5C5A /* libPods-FRPTests.a */; }; 42 | /* End PBXBuildFile section */ 43 | 44 | /* Begin PBXContainerItemProxy section */ 45 | 5EBE2B17180B07D0007B6BF3 /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = 5EBE2AE9180B07D0007B6BF3 /* Project object */; 48 | proxyType = 1; 49 | remoteGlobalIDString = 5EBE2AF0180B07D0007B6BF3; 50 | remoteInfo = FRP; 51 | }; 52 | /* End PBXContainerItemProxy section */ 53 | 54 | /* Begin PBXFileReference section */ 55 | 17A5BBDA18BB2F6D000E2D24 /* FRPLoginViewModelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPLoginViewModelTests.m; sourceTree = ""; }; 56 | 17E5A10D18BB43C600AFC028 /* FRPPhotoImporterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoImporterTests.m; sourceTree = ""; }; 57 | 3AFF6E7183712769B899335A /* Pods-FRPTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FRPTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FRPTests/Pods-FRPTests.debug.xcconfig"; sourceTree = ""; }; 58 | 45B7C6E403355A80BA37853D /* Pods-FRP.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FRP.release.xcconfig"; path = "Pods/Target Support Files/Pods-FRP/Pods-FRP.release.xcconfig"; sourceTree = ""; }; 59 | 5E59510F180E065F002F44FA /* FRPFullSizePhotoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPFullSizePhotoViewController.h; sourceTree = ""; }; 60 | 5E595110180E065F002F44FA /* FRPFullSizePhotoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPFullSizePhotoViewController.m; sourceTree = ""; }; 61 | 5E595115180E0C33002F44FA /* FRPPhotoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPPhotoViewController.h; sourceTree = ""; }; 62 | 5E595116180E0C33002F44FA /* FRPPhotoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoViewController.m; sourceTree = ""; }; 63 | 5E595118180E21E0002F44FA /* FRPPhotoDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPPhotoDetailViewController.h; sourceTree = ""; }; 64 | 5E595119180E21E0002F44FA /* FRPPhotoDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoDetailViewController.m; sourceTree = ""; }; 65 | 5E59511B181219AC002F44FA /* FRPLoginViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPLoginViewController.h; sourceTree = ""; }; 66 | 5E59511C181219AC002F44FA /* FRPLoginViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPLoginViewController.m; sourceTree = ""; }; 67 | 5E59511E18121D09002F44FA /* FRPLoginViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FRPLoginViewController.xib; sourceTree = ""; }; 68 | 5E730B0C1815F3E4003FCB43 /* FRPGalleryViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPGalleryViewModel.h; sourceTree = ""; }; 69 | 5E730B0D1815F3E4003FCB43 /* FRPGalleryViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPGalleryViewModel.m; sourceTree = ""; }; 70 | 5E730B0F1815F78B003FCB43 /* FRPGalleryViewModelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPGalleryViewModelTests.m; sourceTree = ""; }; 71 | 5E730B121815FE97003FCB43 /* FRPFullSizePhotoViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPFullSizePhotoViewModel.h; sourceTree = ""; }; 72 | 5E730B131815FE97003FCB43 /* FRPFullSizePhotoViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPFullSizePhotoViewModel.m; sourceTree = ""; }; 73 | 5E93AD99186C781000795C9E /* FRPFullSizePhotoViewModelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPFullSizePhotoViewModelTests.m; sourceTree = ""; }; 74 | 5E93AD9B186C80DE00795C9E /* FRPPhotoViewModelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoViewModelTests.m; sourceTree = ""; }; 75 | 5EAD1F5618173A3200C67860 /* FRPPhotoViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPPhotoViewModel.h; sourceTree = ""; }; 76 | 5EAD1F5718173A3200C67860 /* FRPPhotoViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoViewModel.m; sourceTree = ""; }; 77 | 5EAD1F5A18173F1500C67860 /* FRPLoginViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPLoginViewModel.h; sourceTree = ""; }; 78 | 5EAD1F5B18173F1500C67860 /* FRPLoginViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPLoginViewModel.m; sourceTree = ""; }; 79 | 5EAD1F5E1817418800C67860 /* FRPPhotoDetailViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPPhotoDetailViewModel.h; sourceTree = ""; }; 80 | 5EAD1F5F1817418800C67860 /* FRPPhotoDetailViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoDetailViewModel.m; sourceTree = ""; }; 81 | 5EBC599C180B247500B683A7 /* FRPCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPCell.h; sourceTree = ""; }; 82 | 5EBC599D180B247500B683A7 /* FRPCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPCell.m; sourceTree = ""; }; 83 | 5EBC59A0180B268600B683A7 /* FRPPhotoModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPPhotoModel.h; sourceTree = ""; }; 84 | 5EBC59A1180B268600B683A7 /* FRPPhotoModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoModel.m; sourceTree = ""; }; 85 | 5EBC59A4180B2AA200B683A7 /* FRPPhotoImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPPhotoImporter.h; sourceTree = ""; }; 86 | 5EBC59A5180B2AA200B683A7 /* FRPPhotoImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPPhotoImporter.m; sourceTree = ""; }; 87 | 5EBC59A7180B2C4F00B683A7 /* FRPGalleryFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPGalleryFlowLayout.h; sourceTree = ""; }; 88 | 5EBC59A8180B2C4F00B683A7 /* FRPGalleryFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPGalleryFlowLayout.m; sourceTree = ""; }; 89 | 5EBE2AF1180B07D0007B6BF3 /* FRP.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FRP.app; sourceTree = BUILT_PRODUCTS_DIR; }; 90 | 5EBE2AF4180B07D0007B6BF3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 91 | 5EBE2AF6180B07D0007B6BF3 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 92 | 5EBE2AF8180B07D0007B6BF3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 93 | 5EBE2AFA180B07D0007B6BF3 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 94 | 5EBE2AFE180B07D0007B6BF3 /* FRP-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FRP-Info.plist"; sourceTree = ""; }; 95 | 5EBE2B00180B07D0007B6BF3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 96 | 5EBE2B02180B07D0007B6BF3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97 | 5EBE2B04180B07D0007B6BF3 /* FRP-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FRP-Prefix.pch"; sourceTree = ""; }; 98 | 5EBE2B05180B07D0007B6BF3 /* FRPAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FRPAppDelegate.h; sourceTree = ""; }; 99 | 5EBE2B06180B07D0007B6BF3 /* FRPAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FRPAppDelegate.m; sourceTree = ""; }; 100 | 5EBE2B11180B07D0007B6BF3 /* FRPTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FRPTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 101 | 5EBE2B12180B07D0007B6BF3 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 102 | 5EBE2B1B180B07D0007B6BF3 /* FRPTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FRPTests-Info.plist"; sourceTree = ""; }; 103 | 5EBE2B1D180B07D0007B6BF3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 104 | 5EBE2B29180B0AF1007B6BF3 /* FRPGalleryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRPGalleryViewController.h; sourceTree = ""; }; 105 | 5EBE2B2A180B0AF1007B6BF3 /* FRPGalleryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRPGalleryViewController.m; sourceTree = ""; }; 106 | 7127D6F69C47C55C8935454C /* Pods-FRP.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FRP.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FRP/Pods-FRP.debug.xcconfig"; sourceTree = ""; }; 107 | 772BE9E4C5824F1C8E5CDC45 /* libPods-FRP.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FRP.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 108 | CF7333D7B465DC81C75987DC /* Pods-FRPTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FRPTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FRPTests/Pods-FRPTests.release.xcconfig"; sourceTree = ""; }; 109 | E5F89E0211BE405E85BE5C5A /* libPods-FRPTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FRPTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 110 | /* End PBXFileReference section */ 111 | 112 | /* Begin PBXFrameworksBuildPhase section */ 113 | 5EBE2AEE180B07D0007B6BF3 /* Frameworks */ = { 114 | isa = PBXFrameworksBuildPhase; 115 | buildActionMask = 2147483647; 116 | files = ( 117 | 5EBE2AF7180B07D0007B6BF3 /* CoreGraphics.framework in Frameworks */, 118 | 5EBE2AF9180B07D0007B6BF3 /* UIKit.framework in Frameworks */, 119 | 5EBE2AF5180B07D0007B6BF3 /* Foundation.framework in Frameworks */, 120 | 2826EB375D254DDD8DE6EAA8 /* libPods-FRP.a in Frameworks */, 121 | ); 122 | runOnlyForDeploymentPostprocessing = 0; 123 | }; 124 | 5EBE2B0E180B07D0007B6BF3 /* Frameworks */ = { 125 | isa = PBXFrameworksBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | 5EBE2B13180B07D0007B6BF3 /* XCTest.framework in Frameworks */, 129 | 5EBE2B16180B07D0007B6BF3 /* CoreData.framework in Frameworks */, 130 | 5EBE2B15180B07D0007B6BF3 /* UIKit.framework in Frameworks */, 131 | 5EBE2B14180B07D0007B6BF3 /* Foundation.framework in Frameworks */, 132 | F5C87C1873574C63889236C3 /* libPods-FRPTests.a in Frameworks */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXFrameworksBuildPhase section */ 137 | 138 | /* Begin PBXGroup section */ 139 | 3BED0B61C697FB1682F9D1C0 /* Pods */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 7127D6F69C47C55C8935454C /* Pods-FRP.debug.xcconfig */, 143 | 45B7C6E403355A80BA37853D /* Pods-FRP.release.xcconfig */, 144 | 3AFF6E7183712769B899335A /* Pods-FRPTests.debug.xcconfig */, 145 | CF7333D7B465DC81C75987DC /* Pods-FRPTests.release.xcconfig */, 146 | ); 147 | name = Pods; 148 | sourceTree = ""; 149 | }; 150 | 5E59510E180E0634002F44FA /* View Controllers */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 5E730B0B1815F39D003FCB43 /* Gallery */, 154 | 5E730B111815FE81003FCB43 /* Full Size Photo */, 155 | 5EAD1F5518173A1E00C67860 /* Photo View Controller */, 156 | 5EAD1F5D1817417B00C67860 /* Photo Details */, 157 | 5EAD1F5918173E9700C67860 /* Login */, 158 | ); 159 | name = "View Controllers"; 160 | sourceTree = ""; 161 | }; 162 | 5E730B0B1815F39D003FCB43 /* Gallery */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 5EBE2B29180B0AF1007B6BF3 /* FRPGalleryViewController.h */, 166 | 5EBE2B2A180B0AF1007B6BF3 /* FRPGalleryViewController.m */, 167 | 5E730B0C1815F3E4003FCB43 /* FRPGalleryViewModel.h */, 168 | 5E730B0D1815F3E4003FCB43 /* FRPGalleryViewModel.m */, 169 | ); 170 | name = Gallery; 171 | sourceTree = ""; 172 | }; 173 | 5E730B111815FE81003FCB43 /* Full Size Photo */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 5E59510F180E065F002F44FA /* FRPFullSizePhotoViewController.h */, 177 | 5E595110180E065F002F44FA /* FRPFullSizePhotoViewController.m */, 178 | 5E730B121815FE97003FCB43 /* FRPFullSizePhotoViewModel.h */, 179 | 5E730B131815FE97003FCB43 /* FRPFullSizePhotoViewModel.m */, 180 | ); 181 | name = "Full Size Photo"; 182 | sourceTree = ""; 183 | }; 184 | 5EAD1F5518173A1E00C67860 /* Photo View Controller */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | 5E595115180E0C33002F44FA /* FRPPhotoViewController.h */, 188 | 5E595116180E0C33002F44FA /* FRPPhotoViewController.m */, 189 | 5EAD1F5618173A3200C67860 /* FRPPhotoViewModel.h */, 190 | 5EAD1F5718173A3200C67860 /* FRPPhotoViewModel.m */, 191 | ); 192 | name = "Photo View Controller"; 193 | sourceTree = ""; 194 | }; 195 | 5EAD1F5918173E9700C67860 /* Login */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | 5E59511B181219AC002F44FA /* FRPLoginViewController.h */, 199 | 5E59511C181219AC002F44FA /* FRPLoginViewController.m */, 200 | 5E59511E18121D09002F44FA /* FRPLoginViewController.xib */, 201 | 5EAD1F5A18173F1500C67860 /* FRPLoginViewModel.h */, 202 | 5EAD1F5B18173F1500C67860 /* FRPLoginViewModel.m */, 203 | ); 204 | name = Login; 205 | sourceTree = ""; 206 | }; 207 | 5EAD1F5D1817417B00C67860 /* Photo Details */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 5E595118180E21E0002F44FA /* FRPPhotoDetailViewController.h */, 211 | 5E595119180E21E0002F44FA /* FRPPhotoDetailViewController.m */, 212 | 5EAD1F5E1817418800C67860 /* FRPPhotoDetailViewModel.h */, 213 | 5EAD1F5F1817418800C67860 /* FRPPhotoDetailViewModel.m */, 214 | ); 215 | name = "Photo Details"; 216 | sourceTree = ""; 217 | }; 218 | 5EBC599F180B267400B683A7 /* UICollectionView Extensions */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 5EBC599C180B247500B683A7 /* FRPCell.h */, 222 | 5EBC599D180B247500B683A7 /* FRPCell.m */, 223 | 5EBC59A7180B2C4F00B683A7 /* FRPGalleryFlowLayout.h */, 224 | 5EBC59A8180B2C4F00B683A7 /* FRPGalleryFlowLayout.m */, 225 | ); 226 | name = "UICollectionView Extensions"; 227 | sourceTree = ""; 228 | }; 229 | 5EBC59A3180B268A00B683A7 /* Models */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 5EBC59A0180B268600B683A7 /* FRPPhotoModel.h */, 233 | 5EBC59A1180B268600B683A7 /* FRPPhotoModel.m */, 234 | ); 235 | name = Models; 236 | sourceTree = ""; 237 | }; 238 | 5EBE2AE8180B07D0007B6BF3 = { 239 | isa = PBXGroup; 240 | children = ( 241 | 5EBE2AFC180B07D0007B6BF3 /* FRP */, 242 | 5EBE2B19180B07D0007B6BF3 /* FRPTests */, 243 | 5EBE2AF3180B07D0007B6BF3 /* Frameworks */, 244 | 5EBE2AF2180B07D0007B6BF3 /* Products */, 245 | 3BED0B61C697FB1682F9D1C0 /* Pods */, 246 | ); 247 | sourceTree = ""; 248 | }; 249 | 5EBE2AF2180B07D0007B6BF3 /* Products */ = { 250 | isa = PBXGroup; 251 | children = ( 252 | 5EBE2AF1180B07D0007B6BF3 /* FRP.app */, 253 | 5EBE2B11180B07D0007B6BF3 /* FRPTests.xctest */, 254 | ); 255 | name = Products; 256 | sourceTree = ""; 257 | }; 258 | 5EBE2AF3180B07D0007B6BF3 /* Frameworks */ = { 259 | isa = PBXGroup; 260 | children = ( 261 | 5EBE2AF4180B07D0007B6BF3 /* Foundation.framework */, 262 | 5EBE2AF6180B07D0007B6BF3 /* CoreGraphics.framework */, 263 | 5EBE2AF8180B07D0007B6BF3 /* UIKit.framework */, 264 | 5EBE2AFA180B07D0007B6BF3 /* CoreData.framework */, 265 | 5EBE2B12180B07D0007B6BF3 /* XCTest.framework */, 266 | 772BE9E4C5824F1C8E5CDC45 /* libPods-FRP.a */, 267 | E5F89E0211BE405E85BE5C5A /* libPods-FRPTests.a */, 268 | ); 269 | name = Frameworks; 270 | sourceTree = ""; 271 | }; 272 | 5EBE2AFC180B07D0007B6BF3 /* FRP */ = { 273 | isa = PBXGroup; 274 | children = ( 275 | 5EBE2B05180B07D0007B6BF3 /* FRPAppDelegate.h */, 276 | 5EBE2B06180B07D0007B6BF3 /* FRPAppDelegate.m */, 277 | 5EBC59A4180B2AA200B683A7 /* FRPPhotoImporter.h */, 278 | 5EBC59A5180B2AA200B683A7 /* FRPPhotoImporter.m */, 279 | 5E59510E180E0634002F44FA /* View Controllers */, 280 | 5EBC59A3180B268A00B683A7 /* Models */, 281 | 5EBC599F180B267400B683A7 /* UICollectionView Extensions */, 282 | 5EBE2AFD180B07D0007B6BF3 /* Supporting Files */, 283 | ); 284 | path = FRP; 285 | sourceTree = ""; 286 | }; 287 | 5EBE2AFD180B07D0007B6BF3 /* Supporting Files */ = { 288 | isa = PBXGroup; 289 | children = ( 290 | 5EBE2AFE180B07D0007B6BF3 /* FRP-Info.plist */, 291 | 5EBE2AFF180B07D0007B6BF3 /* InfoPlist.strings */, 292 | 5EBE2B02180B07D0007B6BF3 /* main.m */, 293 | 5EBE2B04180B07D0007B6BF3 /* FRP-Prefix.pch */, 294 | ); 295 | name = "Supporting Files"; 296 | sourceTree = ""; 297 | }; 298 | 5EBE2B19180B07D0007B6BF3 /* FRPTests */ = { 299 | isa = PBXGroup; 300 | children = ( 301 | 17E5A10D18BB43C600AFC028 /* FRPPhotoImporterTests.m */, 302 | 17A5BBDA18BB2F6D000E2D24 /* FRPLoginViewModelTests.m */, 303 | 5E730B0F1815F78B003FCB43 /* FRPGalleryViewModelTests.m */, 304 | 5E93AD99186C781000795C9E /* FRPFullSizePhotoViewModelTests.m */, 305 | 5E93AD9B186C80DE00795C9E /* FRPPhotoViewModelTests.m */, 306 | 5EBE2B1A180B07D0007B6BF3 /* Supporting Files */, 307 | ); 308 | path = FRPTests; 309 | sourceTree = ""; 310 | }; 311 | 5EBE2B1A180B07D0007B6BF3 /* Supporting Files */ = { 312 | isa = PBXGroup; 313 | children = ( 314 | 5EBE2B1B180B07D0007B6BF3 /* FRPTests-Info.plist */, 315 | 5EBE2B1C180B07D0007B6BF3 /* InfoPlist.strings */, 316 | ); 317 | name = "Supporting Files"; 318 | sourceTree = ""; 319 | }; 320 | /* End PBXGroup section */ 321 | 322 | /* Begin PBXNativeTarget section */ 323 | 5EBE2AF0180B07D0007B6BF3 /* FRP */ = { 324 | isa = PBXNativeTarget; 325 | buildConfigurationList = 5EBE2B23180B07D0007B6BF3 /* Build configuration list for PBXNativeTarget "FRP" */; 326 | buildPhases = ( 327 | 88D5E1AB105C475DB1960326 /* Check Pods Manifest.lock */, 328 | 5EBE2AED180B07D0007B6BF3 /* Sources */, 329 | 5EBE2AEE180B07D0007B6BF3 /* Frameworks */, 330 | 5EBE2AEF180B07D0007B6BF3 /* Resources */, 331 | E46E00988B20439593D30FC8 /* Copy Pods Resources */, 332 | C1A6275438752999DB5F8165 /* Embed Pods Frameworks */, 333 | ); 334 | buildRules = ( 335 | ); 336 | dependencies = ( 337 | ); 338 | name = FRP; 339 | productName = FRP; 340 | productReference = 5EBE2AF1180B07D0007B6BF3 /* FRP.app */; 341 | productType = "com.apple.product-type.application"; 342 | }; 343 | 5EBE2B10180B07D0007B6BF3 /* FRPTests */ = { 344 | isa = PBXNativeTarget; 345 | buildConfigurationList = 5EBE2B26180B07D0007B6BF3 /* Build configuration list for PBXNativeTarget "FRPTests" */; 346 | buildPhases = ( 347 | 36E5A3DAD6634878B5FF59AA /* Check Pods Manifest.lock */, 348 | 5EBE2B0D180B07D0007B6BF3 /* Sources */, 349 | 5EBE2B0E180B07D0007B6BF3 /* Frameworks */, 350 | 5EBE2B0F180B07D0007B6BF3 /* Resources */, 351 | B5CABB6140E74D3BB80B0D5A /* Copy Pods Resources */, 352 | 90433242350D2414CAED381D /* Embed Pods Frameworks */, 353 | ); 354 | buildRules = ( 355 | ); 356 | dependencies = ( 357 | 5EBE2B18180B07D0007B6BF3 /* PBXTargetDependency */, 358 | ); 359 | name = FRPTests; 360 | productName = FRPTests; 361 | productReference = 5EBE2B11180B07D0007B6BF3 /* FRPTests.xctest */; 362 | productType = "com.apple.product-type.bundle.unit-test"; 363 | }; 364 | /* End PBXNativeTarget section */ 365 | 366 | /* Begin PBXProject section */ 367 | 5EBE2AE9180B07D0007B6BF3 /* Project object */ = { 368 | isa = PBXProject; 369 | attributes = { 370 | CLASSPREFIX = FRP; 371 | LastUpgradeCheck = 0500; 372 | ORGANIZATIONNAME = "Ash Furrow"; 373 | TargetAttributes = { 374 | 5EBE2B10180B07D0007B6BF3 = { 375 | TestTargetID = 5EBE2AF0180B07D0007B6BF3; 376 | }; 377 | }; 378 | }; 379 | buildConfigurationList = 5EBE2AEC180B07D0007B6BF3 /* Build configuration list for PBXProject "FRP" */; 380 | compatibilityVersion = "Xcode 3.2"; 381 | developmentRegion = English; 382 | hasScannedForEncodings = 0; 383 | knownRegions = ( 384 | en, 385 | ); 386 | mainGroup = 5EBE2AE8180B07D0007B6BF3; 387 | productRefGroup = 5EBE2AF2180B07D0007B6BF3 /* Products */; 388 | projectDirPath = ""; 389 | projectRoot = ""; 390 | targets = ( 391 | 5EBE2AF0180B07D0007B6BF3 /* FRP */, 392 | 5EBE2B10180B07D0007B6BF3 /* FRPTests */, 393 | ); 394 | }; 395 | /* End PBXProject section */ 396 | 397 | /* Begin PBXResourcesBuildPhase section */ 398 | 5EBE2AEF180B07D0007B6BF3 /* Resources */ = { 399 | isa = PBXResourcesBuildPhase; 400 | buildActionMask = 2147483647; 401 | files = ( 402 | 5EBE2B01180B07D0007B6BF3 /* InfoPlist.strings in Resources */, 403 | 5E59511F18121D09002F44FA /* FRPLoginViewController.xib in Resources */, 404 | ); 405 | runOnlyForDeploymentPostprocessing = 0; 406 | }; 407 | 5EBE2B0F180B07D0007B6BF3 /* Resources */ = { 408 | isa = PBXResourcesBuildPhase; 409 | buildActionMask = 2147483647; 410 | files = ( 411 | 5EBE2B1E180B07D0007B6BF3 /* InfoPlist.strings in Resources */, 412 | ); 413 | runOnlyForDeploymentPostprocessing = 0; 414 | }; 415 | /* End PBXResourcesBuildPhase section */ 416 | 417 | /* Begin PBXShellScriptBuildPhase section */ 418 | 36E5A3DAD6634878B5FF59AA /* Check Pods Manifest.lock */ = { 419 | isa = PBXShellScriptBuildPhase; 420 | buildActionMask = 2147483647; 421 | files = ( 422 | ); 423 | inputPaths = ( 424 | ); 425 | name = "Check Pods Manifest.lock"; 426 | outputPaths = ( 427 | ); 428 | runOnlyForDeploymentPostprocessing = 0; 429 | shellPath = /bin/sh; 430 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 431 | showEnvVarsInLog = 0; 432 | }; 433 | 88D5E1AB105C475DB1960326 /* Check Pods Manifest.lock */ = { 434 | isa = PBXShellScriptBuildPhase; 435 | buildActionMask = 2147483647; 436 | files = ( 437 | ); 438 | inputPaths = ( 439 | ); 440 | name = "Check Pods Manifest.lock"; 441 | outputPaths = ( 442 | ); 443 | runOnlyForDeploymentPostprocessing = 0; 444 | shellPath = /bin/sh; 445 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 446 | showEnvVarsInLog = 0; 447 | }; 448 | 90433242350D2414CAED381D /* Embed Pods Frameworks */ = { 449 | isa = PBXShellScriptBuildPhase; 450 | buildActionMask = 2147483647; 451 | files = ( 452 | ); 453 | inputPaths = ( 454 | ); 455 | name = "Embed Pods Frameworks"; 456 | outputPaths = ( 457 | ); 458 | runOnlyForDeploymentPostprocessing = 0; 459 | shellPath = /bin/sh; 460 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FRPTests/Pods-FRPTests-frameworks.sh\"\n"; 461 | showEnvVarsInLog = 0; 462 | }; 463 | B5CABB6140E74D3BB80B0D5A /* Copy Pods Resources */ = { 464 | isa = PBXShellScriptBuildPhase; 465 | buildActionMask = 2147483647; 466 | files = ( 467 | ); 468 | inputPaths = ( 469 | ); 470 | name = "Copy Pods Resources"; 471 | outputPaths = ( 472 | ); 473 | runOnlyForDeploymentPostprocessing = 0; 474 | shellPath = /bin/sh; 475 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FRPTests/Pods-FRPTests-resources.sh\"\n"; 476 | showEnvVarsInLog = 0; 477 | }; 478 | C1A6275438752999DB5F8165 /* Embed Pods Frameworks */ = { 479 | isa = PBXShellScriptBuildPhase; 480 | buildActionMask = 2147483647; 481 | files = ( 482 | ); 483 | inputPaths = ( 484 | ); 485 | name = "Embed Pods Frameworks"; 486 | outputPaths = ( 487 | ); 488 | runOnlyForDeploymentPostprocessing = 0; 489 | shellPath = /bin/sh; 490 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FRP/Pods-FRP-frameworks.sh\"\n"; 491 | showEnvVarsInLog = 0; 492 | }; 493 | E46E00988B20439593D30FC8 /* Copy Pods Resources */ = { 494 | isa = PBXShellScriptBuildPhase; 495 | buildActionMask = 2147483647; 496 | files = ( 497 | ); 498 | inputPaths = ( 499 | ); 500 | name = "Copy Pods Resources"; 501 | outputPaths = ( 502 | ); 503 | runOnlyForDeploymentPostprocessing = 0; 504 | shellPath = /bin/sh; 505 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FRP/Pods-FRP-resources.sh\"\n"; 506 | showEnvVarsInLog = 0; 507 | }; 508 | /* End PBXShellScriptBuildPhase section */ 509 | 510 | /* Begin PBXSourcesBuildPhase section */ 511 | 5EBE2AED180B07D0007B6BF3 /* Sources */ = { 512 | isa = PBXSourcesBuildPhase; 513 | buildActionMask = 2147483647; 514 | files = ( 515 | 5EBC599E180B247500B683A7 /* FRPCell.m in Sources */, 516 | 5E595111180E065F002F44FA /* FRPFullSizePhotoViewController.m in Sources */, 517 | 5EBC59A9180B2C4F00B683A7 /* FRPGalleryFlowLayout.m in Sources */, 518 | 5EAD1F5C18173F1500C67860 /* FRPLoginViewModel.m in Sources */, 519 | 5EAD1F601817418800C67860 /* FRPPhotoDetailViewModel.m in Sources */, 520 | 5E59511D181219AC002F44FA /* FRPLoginViewController.m in Sources */, 521 | 5EBE2B03180B07D0007B6BF3 /* main.m in Sources */, 522 | 5E730B0E1815F3E4003FCB43 /* FRPGalleryViewModel.m in Sources */, 523 | 5E59511A180E21E0002F44FA /* FRPPhotoDetailViewController.m in Sources */, 524 | 5E595117180E0C33002F44FA /* FRPPhotoViewController.m in Sources */, 525 | 5EBC59A6180B2AA200B683A7 /* FRPPhotoImporter.m in Sources */, 526 | 5EBE2B2B180B0AF1007B6BF3 /* FRPGalleryViewController.m in Sources */, 527 | 5E730B141815FE97003FCB43 /* FRPFullSizePhotoViewModel.m in Sources */, 528 | 5EAD1F5818173A3200C67860 /* FRPPhotoViewModel.m in Sources */, 529 | 5EBE2B07180B07D0007B6BF3 /* FRPAppDelegate.m in Sources */, 530 | ); 531 | runOnlyForDeploymentPostprocessing = 0; 532 | }; 533 | 5EBE2B0D180B07D0007B6BF3 /* Sources */ = { 534 | isa = PBXSourcesBuildPhase; 535 | buildActionMask = 2147483647; 536 | files = ( 537 | 5E93AD9A186C781000795C9E /* FRPFullSizePhotoViewModelTests.m in Sources */, 538 | 17E5A10E18BB43C600AFC028 /* FRPPhotoImporterTests.m in Sources */, 539 | 5E730B101815F78B003FCB43 /* FRPGalleryViewModelTests.m in Sources */, 540 | 5E93AD9C186C80DE00795C9E /* FRPPhotoViewModelTests.m in Sources */, 541 | 17A5BBDB18BB2F6D000E2D24 /* FRPLoginViewModelTests.m in Sources */, 542 | ); 543 | runOnlyForDeploymentPostprocessing = 0; 544 | }; 545 | /* End PBXSourcesBuildPhase section */ 546 | 547 | /* Begin PBXTargetDependency section */ 548 | 5EBE2B18180B07D0007B6BF3 /* PBXTargetDependency */ = { 549 | isa = PBXTargetDependency; 550 | target = 5EBE2AF0180B07D0007B6BF3 /* FRP */; 551 | targetProxy = 5EBE2B17180B07D0007B6BF3 /* PBXContainerItemProxy */; 552 | }; 553 | /* End PBXTargetDependency section */ 554 | 555 | /* Begin PBXVariantGroup section */ 556 | 5EBE2AFF180B07D0007B6BF3 /* InfoPlist.strings */ = { 557 | isa = PBXVariantGroup; 558 | children = ( 559 | 5EBE2B00180B07D0007B6BF3 /* en */, 560 | ); 561 | name = InfoPlist.strings; 562 | sourceTree = ""; 563 | }; 564 | 5EBE2B1C180B07D0007B6BF3 /* InfoPlist.strings */ = { 565 | isa = PBXVariantGroup; 566 | children = ( 567 | 5EBE2B1D180B07D0007B6BF3 /* en */, 568 | ); 569 | name = InfoPlist.strings; 570 | sourceTree = ""; 571 | }; 572 | /* End PBXVariantGroup section */ 573 | 574 | /* Begin XCBuildConfiguration section */ 575 | 5EBE2B21180B07D0007B6BF3 /* Debug */ = { 576 | isa = XCBuildConfiguration; 577 | buildSettings = { 578 | ALWAYS_SEARCH_USER_PATHS = NO; 579 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 580 | CLANG_CXX_LIBRARY = "libc++"; 581 | CLANG_ENABLE_MODULES = YES; 582 | CLANG_ENABLE_OBJC_ARC = YES; 583 | CLANG_WARN_BOOL_CONVERSION = YES; 584 | CLANG_WARN_CONSTANT_CONVERSION = YES; 585 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 586 | CLANG_WARN_EMPTY_BODY = YES; 587 | CLANG_WARN_ENUM_CONVERSION = YES; 588 | CLANG_WARN_INT_CONVERSION = YES; 589 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 590 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 591 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 592 | COPY_PHASE_STRIP = NO; 593 | GCC_C_LANGUAGE_STANDARD = gnu99; 594 | GCC_DYNAMIC_NO_PIC = NO; 595 | GCC_OPTIMIZATION_LEVEL = 0; 596 | GCC_PREPROCESSOR_DEFINITIONS = ( 597 | "DEBUG=1", 598 | "$(inherited)", 599 | ); 600 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 601 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 602 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 603 | GCC_WARN_UNDECLARED_SELECTOR = YES; 604 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 605 | GCC_WARN_UNUSED_FUNCTION = YES; 606 | GCC_WARN_UNUSED_VARIABLE = YES; 607 | ONLY_ACTIVE_ARCH = YES; 608 | SDKROOT = iphoneos; 609 | }; 610 | name = Debug; 611 | }; 612 | 5EBE2B22180B07D0007B6BF3 /* Release */ = { 613 | isa = XCBuildConfiguration; 614 | buildSettings = { 615 | ALWAYS_SEARCH_USER_PATHS = NO; 616 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 617 | CLANG_CXX_LIBRARY = "libc++"; 618 | CLANG_ENABLE_MODULES = YES; 619 | CLANG_ENABLE_OBJC_ARC = YES; 620 | CLANG_WARN_BOOL_CONVERSION = YES; 621 | CLANG_WARN_CONSTANT_CONVERSION = YES; 622 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 623 | CLANG_WARN_EMPTY_BODY = YES; 624 | CLANG_WARN_ENUM_CONVERSION = YES; 625 | CLANG_WARN_INT_CONVERSION = YES; 626 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 627 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 628 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 629 | COPY_PHASE_STRIP = YES; 630 | ENABLE_NS_ASSERTIONS = NO; 631 | GCC_C_LANGUAGE_STANDARD = gnu99; 632 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 633 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 634 | GCC_WARN_UNDECLARED_SELECTOR = YES; 635 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 636 | GCC_WARN_UNUSED_FUNCTION = YES; 637 | GCC_WARN_UNUSED_VARIABLE = YES; 638 | SDKROOT = iphoneos; 639 | VALIDATE_PRODUCT = YES; 640 | }; 641 | name = Release; 642 | }; 643 | 5EBE2B24180B07D0007B6BF3 /* Debug */ = { 644 | isa = XCBuildConfiguration; 645 | baseConfigurationReference = 7127D6F69C47C55C8935454C /* Pods-FRP.debug.xcconfig */; 646 | buildSettings = { 647 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 648 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 649 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 650 | GCC_PREFIX_HEADER = "FRP/FRP-Prefix.pch"; 651 | INFOPLIST_FILE = "FRP/FRP-Info.plist"; 652 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 653 | PRODUCT_NAME = "$(TARGET_NAME)"; 654 | WRAPPER_EXTENSION = app; 655 | }; 656 | name = Debug; 657 | }; 658 | 5EBE2B25180B07D0007B6BF3 /* Release */ = { 659 | isa = XCBuildConfiguration; 660 | baseConfigurationReference = 45B7C6E403355A80BA37853D /* Pods-FRP.release.xcconfig */; 661 | buildSettings = { 662 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 663 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 664 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 665 | GCC_PREFIX_HEADER = "FRP/FRP-Prefix.pch"; 666 | INFOPLIST_FILE = "FRP/FRP-Info.plist"; 667 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 668 | PRODUCT_NAME = "$(TARGET_NAME)"; 669 | WRAPPER_EXTENSION = app; 670 | }; 671 | name = Release; 672 | }; 673 | 5EBE2B27180B07D0007B6BF3 /* Debug */ = { 674 | isa = XCBuildConfiguration; 675 | baseConfigurationReference = 3AFF6E7183712769B899335A /* Pods-FRPTests.debug.xcconfig */; 676 | buildSettings = { 677 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/FRP.app/FRP"; 678 | FRAMEWORK_SEARCH_PATHS = ( 679 | "$(SDKROOT)/Developer/Library/Frameworks", 680 | "$(inherited)", 681 | "$(DEVELOPER_FRAMEWORKS_DIR)", 682 | ); 683 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 684 | GCC_PREFIX_HEADER = "FRP/FRP-Prefix.pch"; 685 | GCC_PREPROCESSOR_DEFINITIONS = ( 686 | "DEBUG=1", 687 | "$(inherited)", 688 | ); 689 | INFOPLIST_FILE = "FRPTests/FRPTests-Info.plist"; 690 | PRODUCT_NAME = "$(TARGET_NAME)"; 691 | TEST_HOST = "$(BUNDLE_LOADER)"; 692 | WRAPPER_EXTENSION = xctest; 693 | }; 694 | name = Debug; 695 | }; 696 | 5EBE2B28180B07D0007B6BF3 /* Release */ = { 697 | isa = XCBuildConfiguration; 698 | baseConfigurationReference = CF7333D7B465DC81C75987DC /* Pods-FRPTests.release.xcconfig */; 699 | buildSettings = { 700 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/FRP.app/FRP"; 701 | FRAMEWORK_SEARCH_PATHS = ( 702 | "$(SDKROOT)/Developer/Library/Frameworks", 703 | "$(inherited)", 704 | "$(DEVELOPER_FRAMEWORKS_DIR)", 705 | ); 706 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 707 | GCC_PREFIX_HEADER = "FRP/FRP-Prefix.pch"; 708 | INFOPLIST_FILE = "FRPTests/FRPTests-Info.plist"; 709 | PRODUCT_NAME = "$(TARGET_NAME)"; 710 | TEST_HOST = "$(BUNDLE_LOADER)"; 711 | WRAPPER_EXTENSION = xctest; 712 | }; 713 | name = Release; 714 | }; 715 | /* End XCBuildConfiguration section */ 716 | 717 | /* Begin XCConfigurationList section */ 718 | 5EBE2AEC180B07D0007B6BF3 /* Build configuration list for PBXProject "FRP" */ = { 719 | isa = XCConfigurationList; 720 | buildConfigurations = ( 721 | 5EBE2B21180B07D0007B6BF3 /* Debug */, 722 | 5EBE2B22180B07D0007B6BF3 /* Release */, 723 | ); 724 | defaultConfigurationIsVisible = 0; 725 | defaultConfigurationName = Release; 726 | }; 727 | 5EBE2B23180B07D0007B6BF3 /* Build configuration list for PBXNativeTarget "FRP" */ = { 728 | isa = XCConfigurationList; 729 | buildConfigurations = ( 730 | 5EBE2B24180B07D0007B6BF3 /* Debug */, 731 | 5EBE2B25180B07D0007B6BF3 /* Release */, 732 | ); 733 | defaultConfigurationIsVisible = 0; 734 | defaultConfigurationName = Release; 735 | }; 736 | 5EBE2B26180B07D0007B6BF3 /* Build configuration list for PBXNativeTarget "FRPTests" */ = { 737 | isa = XCConfigurationList; 738 | buildConfigurations = ( 739 | 5EBE2B27180B07D0007B6BF3 /* Debug */, 740 | 5EBE2B28180B07D0007B6BF3 /* Release */, 741 | ); 742 | defaultConfigurationIsVisible = 0; 743 | defaultConfigurationName = Release; 744 | }; 745 | /* End XCConfigurationList section */ 746 | }; 747 | rootObject = 5EBE2AE9180B07D0007B6BF3 /* Project object */; 748 | } 749 | -------------------------------------------------------------------------------- /FRP.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FRP.xcodeproj/xcuserdata/ash.xcuserdatad/xcschemes/FRP.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /FRP.xcodeproj/xcuserdata/ash.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FRP.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 5EBE2AF0180B07D0007B6BF3 16 | 17 | primary 18 | 19 | 20 | 5EBE2B10180B07D0007B6BF3 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /FRP.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FRP/FRP-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.ashfurrow.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /FRP/FRP-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | 15 | // Standard iOS Imports 16 | #import 17 | #import 18 | 19 | // Pods 20 | #import 21 | #import 22 | #import <500px-iOS-api/PXAPI.h> 23 | #import 24 | 25 | // App Delegate 26 | #import "FRPAppDelegate.h" 27 | #define AppDelegate ((FRPAppDelegate *)[[UIApplication sharedApplication] delegate]) 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /FRP/FRPAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPAppDelegate.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRPAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /FRP/FRPAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPAppDelegate.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPAppDelegate.h" 10 | 11 | #import "FRPGalleryViewController.h" 12 | 13 | @interface FRPAppDelegate () 14 | 15 | @property (nonatomic, strong) PXAPIHelper *apiHelper; 16 | 17 | @end 18 | 19 | @implementation FRPAppDelegate 20 | 21 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 22 | { 23 | NSString *consumerKey = @"DC2To2BS0ic1ChKDK15d44M42YHf9gbUJgdFoF0m"; 24 | NSString *consumerSecret = @"i8WL4chWoZ4kw9fh3jzHK7XzTer1y5tUNvsTFNnB"; 25 | 26 | [PXRequest setConsumerKey:consumerKey consumerSecret:consumerSecret]; 27 | 28 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 29 | self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[FRPGalleryViewController alloc] init]]; 30 | 31 | self.window.backgroundColor = [UIColor whiteColor]; 32 | [self.window makeKeyAndVisible]; 33 | return YES; 34 | } 35 | 36 | - (void)applicationWillResignActive:(UIApplication *)application 37 | { 38 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 39 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 40 | } 41 | 42 | - (void)applicationDidEnterBackground:(UIApplication *)application 43 | { 44 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 45 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 46 | } 47 | 48 | - (void)applicationWillEnterForeground:(UIApplication *)application 49 | { 50 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 51 | } 52 | 53 | - (void)applicationDidBecomeActive:(UIApplication *)application 54 | { 55 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /FRP/FRPCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPCell.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FRPPhotoModel; 12 | 13 | @interface FRPCell : UICollectionViewCell 14 | 15 | @property (nonatomic, strong) FRPPhotoModel *photoModel; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /FRP/FRPCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPCell.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPCell.h" 10 | #import "FRPPhotoModel.m" 11 | #import "NSData+AFDecompression.h" 12 | 13 | @interface FRPCell () 14 | 15 | @property (nonatomic, weak) UIImageView *imageView; 16 | 17 | @end 18 | 19 | @implementation FRPCell 20 | 21 | - (id)initWithFrame:(CGRect)frame 22 | { 23 | self = [super initWithFrame:frame]; 24 | if (!self) return nil; 25 | 26 | // Configure self 27 | self.backgroundColor = [UIColor darkGrayColor]; 28 | 29 | // Configure subivews 30 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds]; 31 | imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 32 | [self.contentView addSubview:imageView]; 33 | self.imageView = imageView; 34 | 35 | RAC(self.imageView, image) = [[[RACObserve(self, photoModel.thumbnailData) ignore:nil] map:^id(id value) { 36 | return [[RACSignal createSignal:^RACDisposable *(id subscriber) { 37 | [value af_decompressedImageFromJPEGDataWithCallback: 38 | ^(UIImage *decompressedImage) { 39 | [subscriber sendNext:decompressedImage]; 40 | [subscriber sendCompleted]; 41 | }]; 42 | return nil; 43 | }] subscribeOn:[RACScheduler scheduler]]; 44 | }] switchToLatest]; 45 | 46 | [self.rac_prepareForReuseSignal subscribeNext:^(id x) { 47 | self.imageView.image = nil; 48 | }]; 49 | 50 | return self; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /FRP/FRPFullSizePhotoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPFullSizePhotoViewController.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/15/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FRPFullSizePhotoViewController; 12 | 13 | @protocol FRPFullSizePhotoViewControllerDelegate 14 | 15 | -(void)userDidScroll:(FRPFullSizePhotoViewController *)viewController toPhotoAtIndex:(NSInteger)index; 16 | 17 | @end 18 | 19 | 20 | @class FRPFullSizePhotoViewModel; 21 | 22 | @interface FRPFullSizePhotoViewController : UIViewController 23 | 24 | @property (nonatomic, strong) FRPFullSizePhotoViewModel *viewModel; 25 | 26 | @property (nonatomic, weak) id delegate; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /FRP/FRPFullSizePhotoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPFullSizePhotoViewController.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/15/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | // View Controllers 10 | #import "FRPFullSizePhotoViewController.h" 11 | #import "FRPPhotoViewController.h" 12 | #import "FRPPhotoDetailViewController.h" 13 | 14 | // Models 15 | #import "FRPFullSizePhotoViewModel.h" 16 | #import "FRPPhotoViewModel.h" 17 | #import "FRPPhotoDetailViewModel.h" 18 | 19 | @interface FRPFullSizePhotoViewController () 20 | 21 | // Private properties 22 | @property (nonatomic, strong) UIPageViewController *pageViewController; 23 | 24 | @end 25 | 26 | @implementation FRPFullSizePhotoViewController 27 | 28 | -(instancetype)init 29 | { 30 | self = [super init]; 31 | if (!self) return nil; 32 | 33 | // View controllers 34 | self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:@{UIPageViewControllerOptionInterPageSpacingKey: @(30)}]; 35 | self.pageViewController.dataSource = self; 36 | self.pageViewController.delegate = self; 37 | [self addChildViewController:self.pageViewController]; 38 | 39 | return self; 40 | } 41 | 42 | - (void)viewDidLoad 43 | { 44 | [super viewDidLoad]; 45 | 46 | // Configure child view controllers 47 | [self.pageViewController setViewControllers:@[[self photoViewControllerForIndex:self.viewModel.initialPhotoIndex]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; 48 | 49 | // Configure self 50 | self.title = self.viewModel.initialPhotoName; 51 | 52 | @weakify(self); 53 | UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoDark]; 54 | infoButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { 55 | return [RACSignal createSignal:^RACDisposable *(id subscriber) { 56 | @strongify(self); 57 | 58 | FRPPhotoViewController *photoViewController = self.pageViewController.viewControllers.firstObject; 59 | FRPPhotoModel *photoModel = photoViewController.viewModel.model; 60 | 61 | FRPPhotoDetailViewModel *viewModel = [[FRPPhotoDetailViewModel alloc] initWithModel:photoModel]; 62 | 63 | FRPPhotoDetailViewController *viewController = [[FRPPhotoDetailViewController alloc] initWithViewModel:viewModel]; 64 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; 65 | 66 | [self presentViewController:navigationController animated:YES completion:^{ 67 | [subscriber sendCompleted]; 68 | }]; 69 | 70 | return nil; 71 | }]; 72 | }]; 73 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:infoButton]; 74 | 75 | // Configure self's view 76 | self.view.backgroundColor = [UIColor blackColor]; 77 | 78 | // Configure subviews 79 | self.pageViewController.view.frame = self.view.bounds; 80 | [self.view addSubview:self.pageViewController.view]; 81 | } 82 | 83 | #pragma mark - Private Methods 84 | 85 | -(FRPPhotoViewController *)photoViewControllerForIndex:(NSInteger)index { 86 | FRPPhotoModel *photoModel = [self.viewModel photoModelAtIndex:index]; 87 | if (photoModel) { 88 | FRPPhotoViewModel *photoViewModel = [[FRPPhotoViewModel alloc] initWithModel:photoModel]; 89 | FRPPhotoViewController *photoViewController = [[FRPPhotoViewController alloc] initWithViewModel:photoViewModel index:index]; 90 | return photoViewController; 91 | } 92 | 93 | return nil; 94 | } 95 | 96 | #pragma mark - UIPageViewControllerDelegate Methods 97 | 98 | - (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed { 99 | self.title = [((FRPPhotoViewController *)self.pageViewController.viewControllers.firstObject).viewModel photoName]; 100 | [self.delegate userDidScroll:self toPhotoAtIndex:[self.pageViewController.viewControllers.firstObject photoIndex]]; 101 | } 102 | 103 | #pragma mark - UIPageViewControllerDataSource Methods 104 | 105 | - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(FRPPhotoViewController *)viewController { 106 | return [self photoViewControllerForIndex:viewController.photoIndex - 1]; 107 | } 108 | 109 | - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(FRPPhotoViewController *)viewController { 110 | return [self photoViewControllerForIndex:viewController.photoIndex + 1]; 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /FRP/FRPFullSizePhotoViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPFullSizePhotoViewModel.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/21/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FRPPhotoModel; 12 | 13 | @interface FRPFullSizePhotoViewModel : RVMViewModel 14 | 15 | -(instancetype)initWithPhotoArray:(NSArray *)photoArray initialPhotoIndex:(NSInteger)initialPhotoIndex; 16 | -(FRPPhotoModel *)photoModelAtIndex:(NSInteger)index; 17 | 18 | @property (nonatomic, readonly, strong) NSArray *model; 19 | @property (nonatomic, readonly) NSInteger initialPhotoIndex; 20 | 21 | @property (nonatomic, readonly) NSString *initialPhotoName; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /FRP/FRPFullSizePhotoViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPFullSizePhotoViewModel.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/21/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPFullSizePhotoViewModel.h" 10 | 11 | // Model 12 | #import "FRPPhotoModel.h" 13 | 14 | @interface FRPFullSizePhotoViewModel () 15 | 16 | // Private access 17 | @property (nonatomic, assign) NSInteger initialPhotoIndex; 18 | 19 | @end 20 | 21 | @implementation FRPFullSizePhotoViewModel 22 | 23 | -(instancetype)initWithPhotoArray:(NSArray *)photoArray initialPhotoIndex:(NSInteger)initialPhotoIndex { 24 | self = [self initWithModel:photoArray]; 25 | if (!self) return nil; 26 | 27 | self.initialPhotoIndex = initialPhotoIndex; 28 | 29 | return self; 30 | } 31 | 32 | -(NSString *)initialPhotoName { 33 | FRPPhotoModel *photoModel = [self initialPhotoModel]; 34 | return [photoModel photoName]; 35 | } 36 | 37 | //-(NSString *)initialPhotoName { 38 | // return [self.model[self.initialPhotoIndex] photoName]; 39 | //} 40 | 41 | -(FRPPhotoModel *)photoModelAtIndex:(NSInteger)index { 42 | if (index < 0 || index > self.model.count - 1) { 43 | // Index was out of bounds, return nil 44 | return nil; 45 | } else { 46 | return self.model[index]; 47 | } 48 | } 49 | 50 | #pragma mark - Private Methods 51 | 52 | -(FRPPhotoModel *)initialPhotoModel { 53 | return [self photoModelAtIndex:self.initialPhotoIndex]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /FRP/FRPGalleryFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPGalleryFlowLayout.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRPGalleryFlowLayout : UICollectionViewFlowLayout 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FRP/FRPGalleryFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPGalleryFlowLayout.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPGalleryFlowLayout.h" 10 | 11 | @implementation FRPGalleryFlowLayout 12 | 13 | -(instancetype)init { 14 | if (!(self = [super init])) return nil; 15 | 16 | self.itemSize = CGSizeMake(145, 145); 17 | self.minimumInteritemSpacing = 10; 18 | self.minimumLineSpacing = 10; 19 | self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); 20 | 21 | return self; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /FRP/FRPGalleryViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPGalleryViewController.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRPGalleryViewController : UICollectionViewController 12 | 13 | -(id)init; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /FRP/FRPGalleryViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPGalleryViewController.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | // View Controllers 10 | #import "FRPGalleryViewController.h" 11 | #import "FRPFullSizePhotoViewController.h" 12 | #import "FRPLoginViewController.h" 13 | 14 | // View models 15 | #import "FRPGalleryViewModel.h" 16 | #import "FRPFullSizePhotoViewModel.h" 17 | 18 | // Views 19 | #import "FRPCell.h" 20 | 21 | // Utilities 22 | #import "FRPGalleryFlowLayout.h" 23 | 24 | static NSString *CellIdentifier = @"Cell"; 25 | 26 | @interface FRPGalleryViewController () 27 | 28 | // Private Properties 29 | @property (nonatomic, strong) FRPGalleryViewModel *viewModel; 30 | 31 | @end 32 | 33 | @implementation FRPGalleryViewController 34 | 35 | - (id)init 36 | { 37 | FRPGalleryFlowLayout *flowLayout = [[FRPGalleryFlowLayout alloc] init]; 38 | 39 | self = [self initWithCollectionViewLayout:flowLayout]; 40 | if (!self) return nil; 41 | 42 | self.viewModel = [[FRPGalleryViewModel alloc] init]; 43 | 44 | return self; 45 | } 46 | 47 | - (void)viewDidLoad 48 | { 49 | [super viewDidLoad]; 50 | 51 | // Configure self 52 | self.title = @"Popular on 500px"; 53 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Log In" style:UIBarButtonItemStylePlain target:nil action:nil]; 54 | 55 | // Configure view 56 | [self.collectionView registerClass:[FRPCell class] forCellWithReuseIdentifier:CellIdentifier]; 57 | 58 | // Binding to view model 59 | @weakify(self); 60 | [RACObserve(self.viewModel, model) subscribeNext:^(id x) { 61 | @strongify(self); 62 | [self.collectionView reloadData]; 63 | }]; 64 | 65 | [[self rac_signalForSelector:@selector(userDidScroll:toPhotoAtIndex:) fromProtocol:@protocol(FRPFullSizePhotoViewControllerDelegate)] subscribeNext:^(RACTuple *value) { 66 | @strongify(self); 67 | [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:[value.second integerValue] inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO]; 68 | }]; 69 | 70 | [[self rac_signalForSelector:@selector(collectionView:didSelectItemAtIndexPath:) fromProtocol:@protocol(UICollectionViewDelegate)] subscribeNext:^(RACTuple *arguments) { 71 | @strongify(self); 72 | 73 | NSIndexPath *indexPath = arguments.second; 74 | FRPFullSizePhotoViewModel *viewModel = [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:self.viewModel.model initialPhotoIndex:indexPath.item]; 75 | 76 | FRPFullSizePhotoViewController *viewController = [[FRPFullSizePhotoViewController alloc] init]; 77 | viewController.viewModel = viewModel; 78 | viewController.delegate = (id)self; 79 | [self.navigationController pushViewController:viewController animated:YES]; 80 | }]; 81 | 82 | self.navigationItem.rightBarButtonItem.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { 83 | return [RACSignal createSignal:^RACDisposable *(id subscriber) { 84 | @strongify(self); 85 | FRPLoginViewController *viewController = [[FRPLoginViewController alloc] initWithNibName:@"FRPLoginViewController" bundle:nil]; 86 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; 87 | 88 | [self presentViewController:navigationController animated:YES completion:^{ 89 | [subscriber sendCompleted]; 90 | }]; 91 | 92 | return nil; 93 | }]; 94 | }]; 95 | 96 | // Need to "reset" the cached values of respondsToSelector: of UIKit 97 | self.collectionView.delegate = nil; 98 | self.collectionView.delegate = self; 99 | } 100 | 101 | #pragma mark - UICollectionViewDataSource Methods 102 | 103 | -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 104 | return self.viewModel.model.count; 105 | } 106 | 107 | -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 108 | FRPCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath]; 109 | 110 | [cell setPhotoModel:self.viewModel.model[indexPath.row]]; 111 | 112 | return cell; 113 | } 114 | 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /FRP/FRPGalleryViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPGalleryViewModel.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/21/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRPGalleryViewModel : RVMViewModel 12 | 13 | @property (nonatomic, readonly, strong) NSArray *model; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /FRP/FRPGalleryViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPGalleryViewModel.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/21/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPGalleryViewModel.h" 10 | 11 | // Utilities 12 | #import "FRPPhotoImporter.h" 13 | 14 | @interface FRPGalleryViewModel () 15 | 16 | @end 17 | 18 | @implementation FRPGalleryViewModel 19 | 20 | -(instancetype)init { 21 | self = [super init]; 22 | if (!self) return nil; 23 | 24 | RAC(self, model) = [self importPhotosSignal]; 25 | 26 | return self; 27 | } 28 | 29 | -(RACSignal *)importPhotosSignal { 30 | return [[[FRPPhotoImporter importPhotos] logError] catchTo:[RACSignal empty]]; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /FRP/FRPLoginViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPLoginViewController.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/18/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRPLoginViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FRP/FRPLoginViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPLoginViewController.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/18/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPLoginViewController.h" 10 | 11 | // Utilities 12 | #import 13 | 14 | // View Model 15 | #import "FRPLoginViewModel.h" 16 | 17 | @interface FRPLoginViewController () 18 | 19 | @property (weak, nonatomic) IBOutlet UITextField *usernameTextField; 20 | @property (weak, nonatomic) IBOutlet UITextField *passwordTextField; 21 | 22 | @property (nonatomic, strong) FRPLoginViewModel *viewModel; 23 | 24 | @end 25 | 26 | @implementation FRPLoginViewController 27 | 28 | -(instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 29 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 30 | if (!self) return nil; 31 | 32 | self.viewModel = [FRPLoginViewModel new]; 33 | 34 | return self; 35 | } 36 | 37 | - (void)viewDidLoad 38 | { 39 | [super viewDidLoad]; 40 | // Do any additional setup after loading the view. 41 | 42 | // Configure navigation item 43 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Log In" style:UIBarButtonItemStylePlain target:nil action:nil]; 44 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:nil action:nil]; 45 | 46 | // Reactive Stuff 47 | @weakify(self); 48 | RAC(self.viewModel, username) = self.usernameTextField.rac_textSignal; 49 | RAC(self.viewModel, password) = self.passwordTextField.rac_textSignal; 50 | self.navigationItem.rightBarButtonItem.rac_command = self.viewModel.loginCommand; 51 | 52 | [[self.viewModel.loginCommand.executionSignals flattenMap:^(RACSignal *execution) { 53 | // Sends RACUnit after the execution completes. 54 | return [[execution ignoreValues] concat:[RACSignal return:RACUnit.defaultUnit]]; 55 | }] subscribeNext:^(id _) { 56 | @strongify(self); 57 | [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; 58 | }]; 59 | [self.viewModel.loginCommand.errors subscribeNext:^(id x) { 60 | NSLog(@"Login error: %@", x); 61 | }]; 62 | 63 | self.navigationItem.leftBarButtonItem.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { 64 | return [RACSignal createSignal:^RACDisposable *(id subscriber) { 65 | @strongify(self); 66 | 67 | [self.presentingViewController dismissViewControllerAnimated:YES completion:^{ 68 | [subscriber sendCompleted]; 69 | }]; 70 | 71 | return nil; 72 | }]; 73 | }]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /FRP/FRPLoginViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /FRP/FRPLoginViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPLoginViewModel.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/22/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRPLoginViewModel : RVMViewModel 12 | 13 | @property (nonatomic, readonly) RACCommand *loginCommand; 14 | @property (nonatomic, strong) NSString *username; 15 | @property (nonatomic, strong) NSString *password; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /FRP/FRPLoginViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPLoginViewModel.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/22/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPLoginViewModel.h" 10 | #import "FRPPhotoImporter.h" 11 | 12 | @interface FRPLoginViewModel () 13 | 14 | @property (nonatomic, strong) RACCommand *loginCommand; 15 | 16 | @end 17 | 18 | @implementation FRPLoginViewModel 19 | 20 | -(instancetype)init { 21 | self = [super init]; 22 | if (!self) return nil; 23 | 24 | @weakify(self); 25 | self.loginCommand = [[RACCommand alloc] initWithEnabled:[self validateLoginInputs] 26 | signalBlock:^RACSignal *(id input) { 27 | @strongify(self); 28 | return [FRPPhotoImporter logInWithUsername:self.username password:self.password]; 29 | }]; 30 | 31 | return self; 32 | } 33 | 34 | - (RACSignal *)validateLoginInputs 35 | { 36 | return [RACSignal combineLatest:@[RACObserve(self, username), RACObserve(self, password)] reduce:^id(NSString *username, NSString *password){ 37 | return @(username.length > 0 && password.length > 0); 38 | }]; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /FRP/FRPPhotoDetailViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoDetailViewController.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/15/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FRPPhotoDetailViewModel; 12 | 13 | @interface FRPPhotoDetailViewController : UIViewController 14 | 15 | -(instancetype)initWithViewModel:(FRPPhotoDetailViewModel *)viewModel; 16 | 17 | @property (nonatomic, readonly) FRPPhotoDetailViewModel *viewModel; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /FRP/FRPPhotoDetailViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoDetailViewController.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/15/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | // View Controllers 10 | #import "FRPPhotoDetailViewController.h" 11 | #import "FRPLoginViewController.h" 12 | 13 | // Model 14 | #import "FRPPhotoDetailViewModel.h" 15 | 16 | // Utilities 17 | #import 18 | 19 | @interface FRPPhotoDetailViewController () 20 | 21 | // Private assignment 22 | @property (nonatomic, strong) FRPPhotoDetailViewModel *viewModel; 23 | 24 | @end 25 | 26 | @implementation FRPPhotoDetailViewController 27 | 28 | -(instancetype)initWithViewModel:(FRPPhotoDetailViewModel *)viewModel 29 | { 30 | self = [self init]; 31 | if (!self) return nil; 32 | 33 | self.viewModel = viewModel; 34 | 35 | return self; 36 | } 37 | 38 | - (void)viewDidLoad 39 | { 40 | [super viewDidLoad]; 41 | 42 | @weakify(self); 43 | 44 | // Configure self 45 | self.title = self.viewModel.photoName; 46 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:nil action:nil]; 47 | self.navigationItem.rightBarButtonItem.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { 48 | return [RACSignal createSignal:^RACDisposable *(id subscriber) { 49 | @strongify(self); 50 | [self.presentingViewController dismissViewControllerAnimated:YES completion:^{ 51 | [subscriber sendCompleted]; 52 | }]; 53 | 54 | return nil; 55 | }]; 56 | }]; 57 | 58 | // Configure self's view 59 | self.view.backgroundColor = [UIColor blackColor]; 60 | 61 | // Configure subviews 62 | UILabel *ratingLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 64, CGRectGetWidth(self.view.bounds), 100)]; 63 | RAC(ratingLabel, text) = RACObserve(self.viewModel, photoRating); 64 | ratingLabel.font = [UIFont boldSystemFontOfSize:80]; 65 | ratingLabel.textColor = [UIColor whiteColor]; 66 | ratingLabel.textAlignment = NSTextAlignmentCenter; 67 | [self.view addSubview:ratingLabel]; 68 | 69 | UILabel *photoNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(ratingLabel.frame), CGRectGetWidth(self.view.bounds), 20)]; 70 | RAC(photoNameLabel, text) = RACObserve(self.viewModel, photoName); 71 | photoNameLabel.font = [UIFont systemFontOfSize:16]; 72 | photoNameLabel.textColor = [UIColor whiteColor]; 73 | photoNameLabel.textAlignment = NSTextAlignmentCenter; 74 | [self.view addSubview:photoNameLabel]; 75 | 76 | UILabel *photographerNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(photoNameLabel.frame), CGRectGetWidth(self.view.bounds), 20)]; 77 | RAC(photographerNameLabel, text) = RACObserve(self.viewModel, photographerName); 78 | photographerNameLabel.font = [UIFont systemFontOfSize:16]; 79 | photographerNameLabel.textColor = [UIColor colorWithWhite:0.5f alpha:1.0f]; 80 | photographerNameLabel.textAlignment = NSTextAlignmentCenter; 81 | [self.view addSubview:photographerNameLabel]; 82 | 83 | UIButton *voteButton = [UIButton buttonWithType:UIButtonTypeCustom]; 84 | voteButton.frame = CGRectMake(20, CGRectGetHeight(self.view.bounds) - 44 - 20, CGRectGetWidth(self.view.bounds) - 40, 44); 85 | voteButton.autoresizingMask = UIViewAutoresizingFlexibleTopMargin; 86 | // Note: can't use getter keypath 87 | [RACObserve(self.viewModel, votePromptText) subscribeNext:^(id value) { 88 | [voteButton setTitle:value forState:UIControlStateNormal]; 89 | }]; 90 | voteButton.rac_command = [[RACCommand alloc] initWithEnabled:self.viewModel.voteCommand.enabled signalBlock:^RACSignal *(id input) { 91 | // Assume that we're logged in at first. We'll replace this signal later if not. 92 | RACSignal *authSignal = [RACSignal empty]; 93 | 94 | if ([[PXRequest apiHelper] authMode] == PXAPIHelperModeNoAuth) { 95 | // Not logged in. Replace signal. 96 | authSignal = [[RACSignal createSignal:^RACDisposable *(id subscriber) { 97 | @strongify(self); 98 | 99 | FRPLoginViewController *viewController = [[FRPLoginViewController alloc] initWithNibName:@"FRPLoginViewController" bundle:nil]; 100 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; 101 | 102 | [self presentViewController:navigationController animated:YES completion:^{ 103 | [subscriber sendCompleted]; 104 | }]; 105 | 106 | return nil; 107 | }] then:^RACSignal *{ 108 | @strongify(self); 109 | // take:1 so that this signal completes once we've re-appeared. 110 | return [[self rac_signalForSelector:@selector(viewDidAppear:)] take:1]; 111 | }]; 112 | } 113 | 114 | return [authSignal then:^RACSignal *{ 115 | @strongify(self); 116 | return [[self.viewModel.voteCommand execute:nil] ignoreValues]; 117 | }]; 118 | }]; 119 | [voteButton.rac_command.errors subscribeNext:^(id x) { 120 | [x subscribeNext:^(NSError *error) { 121 | [SVProgressHUD showErrorWithStatus:[error localizedDescription]]; 122 | }]; 123 | }]; 124 | [self.view addSubview:voteButton]; 125 | } 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /FRP/FRPPhotoDetailViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoDetailViewModel.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/22/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FRPPhotoModel; 12 | 13 | @interface FRPPhotoDetailViewModel : RVMViewModel 14 | 15 | @property (nonatomic, readonly) FRPPhotoModel *model; 16 | 17 | @property (nonatomic, readonly) NSString *photoName; 18 | @property (nonatomic, readonly) NSString *photoRating; 19 | @property (nonatomic, readonly) NSString *photographerName; 20 | @property (nonatomic, readonly) NSString *votePromptText; 21 | 22 | @property (nonatomic, readonly) RACCommand *voteCommand; 23 | 24 | @property (nonatomic, readonly) BOOL loggedIn; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /FRP/FRPPhotoDetailViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoDetailViewModel.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/22/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPPhotoDetailViewModel.h" 10 | 11 | // Model 12 | #import "FRPPhotoModel.h" 13 | 14 | // Utilities 15 | #import "FRPPhotoImporter.h" 16 | 17 | @interface FRPPhotoDetailViewModel () 18 | 19 | @property (nonatomic, strong) NSString *photoName; 20 | @property (nonatomic, strong) NSString *photoRating; 21 | @property (nonatomic, strong) NSString *photographerName; 22 | @property (nonatomic, strong) NSString *votePromptText; 23 | 24 | @property (nonatomic, strong) RACCommand *voteCommand; 25 | 26 | @end 27 | 28 | @implementation FRPPhotoDetailViewModel 29 | 30 | -(instancetype)initWithModel:(FRPPhotoModel *)photoModel { 31 | self = [super initWithModel:photoModel]; 32 | if (!self) return nil; 33 | 34 | RAC(self, photoName) = RACObserve(self.model, photoName); 35 | RAC(self, photoRating) = [RACObserve(self.model, rating) map:^id(id value) { 36 | return [NSString stringWithFormat:@"%0.2f", [value floatValue]]; 37 | }]; 38 | RAC(self, photographerName) = RACObserve(self.model, photographerName); 39 | RAC(self, votePromptText) = [RACObserve(self.model, votedFor) map:^id(id value) { 40 | if ([value boolValue]) { 41 | return @"Voted For!"; 42 | } else { 43 | return @"Vote"; 44 | } 45 | }]; 46 | 47 | @weakify(self); 48 | self.voteCommand = [[RACCommand alloc] initWithEnabled:[RACObserve(self.model, votedFor) not] signalBlock:^RACSignal *(id input) { 49 | @strongify(self); 50 | return [FRPPhotoImporter voteForPhoto:self.model]; 51 | }]; 52 | 53 | return self; 54 | } 55 | 56 | -(BOOL)loggedIn { 57 | return [[PXRequest apiHelper] authMode] == PXAPIHelperModeOAuth; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /FRP/FRPPhotoImporter.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoImporter.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FRPPhotoModel; 12 | 13 | @interface FRPPhotoImporter : NSObject 14 | 15 | // Note: we're using RACSignal as the declared return type, even though it's a RACSubject 16 | +(RACSignal *)importPhotos; 17 | 18 | +(RACSignal *)fetchPhotoDetails:(FRPPhotoModel *)photoModel; 19 | 20 | +(RACSignal *)logInWithUsername:(NSString *)username password:(NSString *)password; 21 | 22 | +(RACSignal *)voteForPhoto:(FRPPhotoModel *)photoModel; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /FRP/FRPPhotoImporter.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoImporter.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPPhotoImporter.h" 10 | #import "FRPPhotoModel.h" 11 | 12 | @implementation FRPPhotoImporter 13 | 14 | +(NSURLRequest *)popularURLRequest { 15 | return [[PXRequest apiHelper] urlRequestForPhotoFeature:PXAPIHelperPhotoFeaturePopular resultsPerPage:100 page:0 photoSizes:PXPhotoModelSizeThumbnail sortOrder:PXAPIHelperSortOrderRating except:PXPhotoModelCategoryNude]; 16 | } 17 | 18 | +(NSURLRequest *)photoURLRequest:(FRPPhotoModel *)photoModel { 19 | return [[PXRequest apiHelper] urlRequestForPhotoID:photoModel.identifier.integerValue]; 20 | } 21 | 22 | +(RACSignal *)requestPhotoData 23 | { 24 | NSURLRequest *request = [self popularURLRequest]; 25 | 26 | return [[NSURLConnection rac_sendAsynchronousRequest:request] reduceEach:^id(NSURLResponse *response, NSData *data){ 27 | return data; 28 | }]; 29 | } 30 | 31 | +(RACSignal *)importPhotos { 32 | return [[[[[self requestPhotoData] deliverOn:[RACScheduler mainThreadScheduler]] map:^id(NSData *data) { 33 | id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; 34 | 35 | return [[[results[@"photos"] rac_sequence] map:^id(NSDictionary *photoDictionary) { 36 | FRPPhotoModel *model = [FRPPhotoModel new]; 37 | 38 | [self configurePhotoModel:model withDictionary:photoDictionary]; 39 | [self downloadThumbnailForPhotoModel:model]; 40 | 41 | return model; 42 | }] array]; 43 | }] publish] autoconnect]; 44 | } 45 | 46 | +(RACSignal *)fetchPhotoDetails:(FRPPhotoModel *)photoModel { 47 | NSURLRequest *request = [self photoURLRequest:photoModel]; 48 | return [[[[[[NSURLConnection rac_sendAsynchronousRequest:request] reduceEach:^id(NSURLResponse *response, NSData *data){ 49 | return data; 50 | }] deliverOn:[RACScheduler mainThreadScheduler]] map:^id(NSData *data) { 51 | id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil][@"photo"]; 52 | 53 | [self configurePhotoModel:photoModel withDictionary:results]; 54 | [self downloadFullsizedImageForPhotoModel:photoModel]; 55 | 56 | return photoModel; 57 | }] publish] autoconnect]; 58 | } 59 | 60 | +(RACSignal *)logInWithUsername:(NSString *)username password:(NSString *)password { 61 | return [RACSignal createSignal:^RACDisposable *(id subscriber) { 62 | [PXRequest authenticateWithUserName:username password:password completion:^(BOOL success) { 63 | if (success) { 64 | [subscriber sendCompleted]; 65 | } else { 66 | [subscriber sendError:[NSError errorWithDomain:@"500px API" code:0 userInfo:@{NSLocalizedDescriptionKey: @"Could not log in."}]]; 67 | } 68 | }]; 69 | 70 | // Cannot cancel request 71 | return nil; 72 | }]; 73 | } 74 | 75 | +(RACSignal *)voteForPhoto:(FRPPhotoModel *)photoModel { 76 | return [[[RACSignal createSignal:^RACDisposable *(id subscriber) { 77 | PXRequest *voteRequest = [PXRequest requestToVoteForPhoto:[photoModel.identifier integerValue] completion:^(NSDictionary *results, NSError *error) { 78 | if (error) { 79 | [subscriber sendError:error]; 80 | } else { 81 | photoModel.votedFor = YES; 82 | [subscriber sendCompleted]; 83 | } 84 | }]; 85 | 86 | return [RACDisposable disposableWithBlock:^{ 87 | if (voteRequest.requestStatus == PXRequestStatusStarted) { 88 | [voteRequest cancel]; 89 | } 90 | }]; 91 | }] publish] autoconnect]; 92 | } 93 | 94 | #pragma mark - Private Methods 95 | 96 | +(void)configurePhotoModel:(FRPPhotoModel *)photoModel withDictionary:(NSDictionary *)dictionary { 97 | // Basics details fetched with the first, basic request 98 | photoModel.photoName = dictionary[@"name"]; 99 | photoModel.identifier = dictionary[@"id"]; 100 | photoModel.photographerName = dictionary[@"user"][@"username"]; 101 | photoModel.rating = dictionary[@"rating"]; 102 | 103 | photoModel.thumbnailURL = [self urlForImageSize:3 inDictionary:dictionary[@"images"]]; 104 | 105 | if (dictionary[@"voted"]) { 106 | photoModel.votedFor = [dictionary[@"voted"] boolValue]; 107 | } 108 | 109 | // Extended attributes fetched with subsequent request 110 | if (dictionary[@"comments_count"]) { 111 | photoModel.fullsizedURL = [self urlForImageSize:4 inDictionary:dictionary[@"images"]]; 112 | } 113 | } 114 | 115 | +(NSString *)urlForImageSize:(NSInteger)size inDictionary:(NSArray *)array { 116 | /* 117 | ( 118 | { 119 | size = 3; 120 | url = "http://ppcdn.500px.org/49204370/b125a49d0863e0ba05d8196072b055876159f33e/3.jpg"; 121 | } 122 | ); 123 | */ 124 | 125 | return [[[[[array rac_sequence] filter:^BOOL(NSDictionary *value) { 126 | return [value[@"size"] integerValue] == size; 127 | }] map:^id(id value) { 128 | return value[@"url"]; 129 | }] array] firstObject]; 130 | } 131 | 132 | +(void)downloadThumbnailForPhotoModel:(FRPPhotoModel *)photoModel { 133 | RAC(photoModel, thumbnailData) = [self download:photoModel.thumbnailURL]; 134 | } 135 | 136 | +(void)downloadFullsizedImageForPhotoModel:(FRPPhotoModel *)photoModel { 137 | RAC(photoModel, fullsizedData) = [self download:photoModel.fullsizedURL]; 138 | } 139 | 140 | +(RACSignal *)download:(NSString *)urlString { 141 | NSAssert(urlString, @"URL must not be nil"); 142 | 143 | NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]; 144 | 145 | return [[[NSURLConnection rac_sendAsynchronousRequest:request] reduceEach:^id(NSURLResponse *response, NSData *data){ 146 | return data; 147 | }] deliverOn:[RACScheduler mainThreadScheduler]]; 148 | } 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /FRP/FRPPhotoModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoModel.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRPPhotoModel : NSObject 12 | 13 | @property (nonatomic, strong) NSString *photoName; 14 | @property (nonatomic, strong) NSNumber *identifier; 15 | @property (nonatomic, strong) NSString *photographerName; 16 | @property (nonatomic, strong) NSNumber *rating; 17 | @property (nonatomic, strong) NSString *thumbnailURL; 18 | @property (nonatomic, strong) NSData *thumbnailData; 19 | @property (nonatomic, strong) NSString *fullsizedURL; 20 | @property (nonatomic, strong) NSData *fullsizedData; 21 | @property (nonatomic, assign, getter = isVotedFor) BOOL votedFor; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /FRP/FRPPhotoModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoModel.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPPhotoModel.h" 10 | 11 | @implementation FRPPhotoModel 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FRP/FRPPhotoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoViewController.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/15/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FRPPhotoViewModel; 12 | 13 | @interface FRPPhotoViewController : UIViewController 14 | 15 | -(instancetype)initWithViewModel:(FRPPhotoViewModel *)viewModel index:(NSInteger)photoIndex; 16 | 17 | @property (nonatomic, readonly) NSInteger photoIndex; 18 | 19 | @property (nonatomic, readonly) FRPPhotoViewModel *viewModel; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /FRP/FRPPhotoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoViewController.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/15/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPPhotoViewController.h" 10 | 11 | // Model 12 | #import "FRPPhotoViewModel.h" 13 | 14 | // Utilities 15 | #import 16 | 17 | @interface FRPPhotoViewController () 18 | 19 | // Private assignment 20 | @property (nonatomic, assign) NSInteger photoIndex; 21 | @property (nonatomic, strong) FRPPhotoViewModel *viewModel; 22 | 23 | // Private properties 24 | @property (nonatomic, weak) UIImageView *imageView; 25 | 26 | @end 27 | 28 | @implementation FRPPhotoViewController 29 | 30 | -(instancetype)initWithViewModel:(FRPPhotoViewModel *)viewModel index:(NSInteger)photoIndex 31 | { 32 | self = [self init]; 33 | if (!self) return nil; 34 | 35 | self.viewModel = viewModel; 36 | self.photoIndex = photoIndex; 37 | 38 | return self; 39 | } 40 | 41 | -(void)viewDidLoad { 42 | [super viewDidLoad]; 43 | 44 | // Configure self's view 45 | self.view.backgroundColor = [UIColor blackColor]; 46 | 47 | // Configure subviews 48 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; 49 | RAC(imageView, image) = RACObserve(self.viewModel, photoImage); 50 | imageView.contentMode = UIViewContentModeScaleAspectFit; 51 | [self.view addSubview:imageView]; 52 | self.imageView = imageView; 53 | 54 | [RACObserve(self.viewModel, loading) subscribeNext:^(NSNumber *loading){ 55 | if (loading.boolValue) { 56 | [SVProgressHUD show]; 57 | } else { 58 | [SVProgressHUD dismiss]; 59 | } 60 | }]; 61 | } 62 | 63 | -(void)viewWillAppear:(BOOL)animated { 64 | [super viewWillAppear:animated]; 65 | 66 | self.viewModel.active = YES; 67 | } 68 | 69 | -(void)viewDidDisappear:(BOOL)animated { 70 | [super viewDidDisappear:animated]; 71 | 72 | self.viewModel.active = NO; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /FRP/FRPPhotoViewModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoViewModel.h 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/22/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FRPPhotoModel; 12 | 13 | @interface FRPPhotoViewModel : RVMViewModel 14 | 15 | @property (nonatomic, readonly) FRPPhotoModel *model; 16 | 17 | @property (nonatomic, readonly) UIImage *photoImage; 18 | @property (nonatomic, readonly, getter = isLoading) BOOL loading; 19 | 20 | -(NSString *)photoName; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /FRP/FRPPhotoViewModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoViewModel.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/22/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "FRPPhotoViewModel.h" 10 | 11 | //Utilities 12 | #import "FRPPhotoImporter.h" 13 | #import "FRPPhotoModel.h" 14 | 15 | @interface FRPPhotoViewModel () 16 | 17 | @property (nonatomic, strong) UIImage *photoImage; 18 | @property (nonatomic, assign, getter = isLoading) BOOL loading; 19 | 20 | @end 21 | 22 | @implementation FRPPhotoViewModel 23 | 24 | -(instancetype)initWithModel:(FRPPhotoModel *)photoModel { 25 | self = [super initWithModel:photoModel]; 26 | if (!self) return nil; 27 | 28 | @weakify(self); 29 | [self.didBecomeActiveSignal subscribeNext:^(id x) { 30 | @strongify(self); 31 | [self downloadPhotoModelDetails]; 32 | }]; 33 | 34 | RAC(self, photoImage) = [RACObserve(self.model, fullsizedData) map:^id(id value) { 35 | return [UIImage imageWithData:value]; 36 | }]; 37 | 38 | return self; 39 | } 40 | 41 | #pragma mark - Public Methods 42 | 43 | -(NSString *)photoName { 44 | return self.model.photoName; 45 | } 46 | 47 | #pragma mark - Private Methods 48 | 49 | -(void)downloadPhotoModelDetails { 50 | self.loading = YES; 51 | 52 | @weakify(self); 53 | [[FRPPhotoImporter fetchPhotoDetails:self.model] subscribeError:^(NSError *error) { 54 | NSLog(@"Could not fetch photo details: %@", error); 55 | } completed:^{ 56 | @strongify(self); 57 | self.loading = NO; 58 | NSLog(@"Fetched photo details."); 59 | }]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /FRP/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /FRP/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/13/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "FRPAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([FRPAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FRPTests/FRPFullSizePhotoViewModelTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPFullSizePhotoViewModelTests.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 12/26/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | #define EXP_SHORTHAND 11 | #import 12 | #import 13 | 14 | #import "FRPFullSizePhotoViewModel.h" 15 | #import "FRPPhotoModel.h" 16 | 17 | @interface FRPFullSizePhotoViewModel () 18 | 19 | -(FRPPhotoModel *)initialPhotoModel; 20 | 21 | @end 22 | 23 | SpecBegin(FRPFullSizePhotoViewModel) 24 | 25 | describe(@"FRPFullSizePhotomodel", ^{ 26 | it (@"should assign correct attributes when initialized", ^{ 27 | NSArray *model = @[]; 28 | NSInteger initialPhotoIndex = 1337; 29 | 30 | FRPFullSizePhotoViewModel *viewModel = [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex]; 31 | 32 | expect(model).to.equal(viewModel.model); 33 | expect(initialPhotoIndex).to.equal(viewModel.initialPhotoIndex); 34 | }); 35 | 36 | it (@"should return nil for an out-of-bounds photo index", ^{ 37 | NSArray *model = @[[NSObject new]]; 38 | NSInteger initialPhotoIndex = 0; 39 | 40 | FRPFullSizePhotoViewModel *viewModel = [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex]; 41 | 42 | id subzeroModel = [viewModel photoModelAtIndex:-1]; 43 | expect(subzeroModel).to.beNil(); 44 | 45 | id aboveBoundsModel = [viewModel photoModelAtIndex:model.count]; 46 | expect(aboveBoundsModel).to.beNil(); 47 | }); 48 | 49 | it (@"should return the correct model for photoModelAtIndex:", ^{ 50 | id photoModel = [NSObject new]; 51 | NSArray *model = @[photoModel]; 52 | NSInteger initialPhotoIndex = 0; 53 | 54 | FRPFullSizePhotoViewModel *viewModel = [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex]; 55 | 56 | id returnedModel = [viewModel photoModelAtIndex:0]; 57 | expect(returnedModel).to.equal(photoModel); 58 | }); 59 | 60 | it (@"should return the correct initial photo model", ^{ 61 | NSArray *model = @[[NSObject new]]; 62 | NSInteger initialPhotoIndex = 0; 63 | 64 | FRPFullSizePhotoViewModel *viewModel = [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex]; 65 | id mockViewModel = [OCMockObject partialMockForObject:viewModel]; 66 | 67 | [[[mockViewModel expect] andReturn:model[0]] photoModelAtIndex:initialPhotoIndex]; 68 | 69 | id returnedObject = [mockViewModel initialPhotoModel]; 70 | 71 | expect(returnedObject).to.equal(model[0]); 72 | 73 | [mockViewModel verify]; 74 | }); 75 | }); 76 | 77 | SpecEnd 78 | -------------------------------------------------------------------------------- /FRPTests/FRPGalleryViewModelTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPGalleryViewModelTests.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 10/21/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "Specta.h" 10 | #import 11 | 12 | #import "FRPGalleryViewModel.h" 13 | 14 | @interface FRPGalleryViewModel () 15 | 16 | -(RACSignal *)importPhotosSignal; 17 | 18 | @end 19 | 20 | SpecBegin(FRPGalleryViewModel) 21 | 22 | describe(@"FRPGalleryViewModel", ^{ 23 | it(@"should be initialized and call importPhotos", ^{ 24 | id mockObject = [OCMockObject mockForClass:[FRPGalleryViewModel class]]; 25 | [[[mockObject expect] andReturn:[RACSignal empty]] importPhotosSignal]; 26 | 27 | mockObject = [mockObject init]; 28 | 29 | [mockObject verify]; 30 | [mockObject stopMocking]; 31 | }); 32 | }); 33 | 34 | 35 | SpecEnd -------------------------------------------------------------------------------- /FRPTests/FRPLoginViewModelTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPLoginViewModelTests.m 3 | // FRP 4 | // 5 | // Created by Phat, Le Tan on 2/24/14. 6 | // Copyright (c) 2014 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "Specta.h" 10 | #define EXP_SHORTHAND 11 | #import 12 | #import 13 | 14 | #import "FRPLoginViewModel.h" 15 | 16 | @interface FRPLoginViewModel() 17 | 18 | - (RACSignal *)validateLoginInputs; 19 | 20 | @end 21 | 22 | SpecBegin(FRPLoginViewModel) 23 | 24 | __block FRPLoginViewModel *viewModel; 25 | 26 | beforeEach(^{ 27 | viewModel = [[FRPLoginViewModel alloc] initWithModel:nil]; 28 | }); 29 | 30 | describe(@"FRPLoginViewModel", ^{ 31 | it(@"disable login command when username is empty", ^{ 32 | viewModel.username = @""; 33 | viewModel.password = @"password"; 34 | __block id result; 35 | [[viewModel validateLoginInputs] subscribeNext:^(id x) { 36 | result = x; 37 | }]; 38 | expect(result).to.equal(@0); 39 | }); 40 | 41 | it(@"disable login command when password is empty", ^{ 42 | viewModel.username = @"username"; 43 | viewModel.password = @""; 44 | __block id result; 45 | [[viewModel validateLoginInputs] subscribeNext:^(id x) { 46 | result = x; 47 | }]; 48 | expect(result).to.equal(@0); 49 | }); 50 | 51 | it(@"enable login command when username and password are available", ^{ 52 | viewModel.username = @"username"; 53 | viewModel.password = @"password"; 54 | __block id result; 55 | [[viewModel validateLoginInputs] subscribeNext:^(id x) { 56 | result = x; 57 | }]; 58 | expect(result).to.equal(@1); 59 | }); 60 | }); 61 | 62 | afterEach(^{ 63 | viewModel = nil; 64 | }); 65 | 66 | SpecEnd -------------------------------------------------------------------------------- /FRPTests/FRPPhotoImporterTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoImporterTests.m 3 | // FRP 4 | // 5 | // Created by Phat, Le Tan on 2/24/14. 6 | // Copyright (c) 2014 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import "Specta.h" 10 | #define EXP_SHORTHAND 11 | #import 12 | #import 13 | 14 | #import "FRPPhotoImporter.h" 15 | 16 | @interface FRPPhotoImporter() 17 | 18 | +(RACSignal *)requestPhotoData; 19 | 20 | @end 21 | 22 | SpecBegin(FRPPhotoImporter) 23 | 24 | describe(@"FRPPhotoImporter_Login", ^{ 25 | __block id mock; 26 | beforeEach(^{ 27 | mock = [OCMockObject mockForClass:[PXRequest class]]; 28 | }); 29 | 30 | it(@"logins successfully", ^{ 31 | __block id success = @0; 32 | void (^theBlock)(NSInvocation *) = ^(NSInvocation *invocation) { 33 | void (^passedBlock)( BOOL ); 34 | [invocation getArgument:&passedBlock atIndex:4]; 35 | passedBlock(YES); 36 | }; 37 | [[[mock stub] andDo:theBlock] authenticateWithUserName:[OCMArg any] password:[OCMArg any] completion:[OCMArg any]]; 38 | 39 | [[FRPPhotoImporter logInWithUsername:@"username" password:@"password"] subscribeCompleted:^{ 40 | success = @1; 41 | }]; 42 | expect(success).to.equal(@1); 43 | }); 44 | 45 | it(@"returns error when login unsuccessfully", ^{ 46 | __block id expected_error; 47 | void (^theBlock)(NSInvocation *) = ^(NSInvocation *invocation) { 48 | void (^passedBlock)( BOOL ); 49 | [invocation getArgument:&passedBlock atIndex:4]; 50 | passedBlock(NO); 51 | }; 52 | [[[mock stub] andDo:theBlock] authenticateWithUserName:[OCMArg any] password:[OCMArg any] completion:[OCMArg any]]; 53 | 54 | [[FRPPhotoImporter logInWithUsername:@"username" password:@"password"] subscribeError:^(NSError *error) { 55 | expected_error = error; 56 | }]; 57 | expect([expected_error domain]).to.equal(@"500px API"); 58 | }); 59 | 60 | afterEach(^{ 61 | mock = nil; 62 | }); 63 | }); 64 | 65 | describe(@"FRPPhotoImporter_ImportPhotos", ^{ 66 | it(@"requests popular photo data", ^{ 67 | id mock = [OCMockObject mockForClass:[NSURLConnection class]]; 68 | __block id resultData; 69 | RACSignal *stubAsyn = [RACSignal createSignal:^RACDisposable *(id subscriber) { 70 | [subscriber sendNext:RACTuplePack(nil, [NSData data])]; 71 | return nil; 72 | }]; 73 | [[[mock stub] andReturn:stubAsyn] rac_sendAsynchronousRequest:[OCMArg any]]; 74 | 75 | [[FRPPhotoImporter requestPhotoData] subscribeNext:^(id x) { 76 | resultData = x; 77 | }]; 78 | expect(resultData).to.equal([NSData data]); 79 | }); 80 | }); 81 | 82 | SpecEnd 83 | -------------------------------------------------------------------------------- /FRPTests/FRPPhotoViewModelTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRPPhotoViewModelTests.m 3 | // FRP 4 | // 5 | // Created by Ash Furrow on 12/26/2013. 6 | // Copyright (c) 2013 Ash Furrow. All rights reserved. 7 | // 8 | 9 | #import 10 | #define EXP_SHORTHAND 11 | #import 12 | #import 13 | 14 | #import "FRPPhotoViewModel.h" 15 | #import "FRPPhotoModel.h" 16 | 17 | @interface FRPPhotoViewModel () 18 | 19 | -(void)downloadPhotoModelDetails; 20 | 21 | @end 22 | 23 | SpecBegin(FRPPhotoViewModel) 24 | 25 | describe(@"FRPPhotoViewModel", ^{ 26 | it (@"should return the photo's name property when photoName is invoked", ^{ 27 | NSString *name = @"Ash"; 28 | 29 | id mockPhotoModel = [OCMockObject mockForClass:[FRPPhotoModel class]]; 30 | [[[mockPhotoModel stub] andReturn:name] photoName]; 31 | 32 | FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:nil]; 33 | id mockViewModel = [OCMockObject partialMockForObject:viewModel]; 34 | [[[mockViewModel stub] andReturn:mockPhotoModel] model]; 35 | 36 | id returnedName = [mockViewModel photoName]; 37 | 38 | expect(returnedName).to.equal(name); 39 | 40 | [mockPhotoModel stopMocking]; 41 | }); 42 | 43 | it (@"should download photo model details when it becomes active", ^{ 44 | FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:nil]; 45 | 46 | id mockViewModel = [OCMockObject partialMockForObject:viewModel]; 47 | [[mockViewModel expect] downloadPhotoModelDetails]; 48 | 49 | [mockViewModel setActive:YES]; 50 | 51 | [mockViewModel verify]; 52 | }); 53 | 54 | it (@"should correctly map image data to UIImage", ^{ 55 | UIImage *image = [[UIImage alloc] init]; 56 | NSData *imageData = [NSData data]; 57 | 58 | id mockImage = [OCMockObject mockForClass:[UIImage class]]; 59 | [[[mockImage stub] andReturn:image] imageWithData:imageData]; 60 | 61 | FRPPhotoModel *photoModel = [[FRPPhotoModel alloc] init]; 62 | photoModel.fullsizedData = imageData; 63 | 64 | __unused FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:photoModel]; 65 | 66 | [mockImage verify]; 67 | [mockImage stopMocking]; 68 | }); 69 | 70 | it (@"should return the correct photo name", ^{ 71 | NSString *name = @"Ash"; 72 | 73 | FRPPhotoModel *photoModel = [[FRPPhotoModel alloc] init]; 74 | photoModel.photoName = name; 75 | 76 | FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:photoModel]; 77 | 78 | NSString *returnedName = [viewModel photoName]; 79 | 80 | expect(name).to.equal(returnedName); 81 | }); 82 | }); 83 | 84 | SpecEnd 85 | -------------------------------------------------------------------------------- /FRPTests/FRPTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.ashfurrow.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FRPTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Ash Furrow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | platform :ios, "8.0" 4 | 5 | inhibit_all_warnings! 6 | 7 | target "FRP" do 8 | 9 | pod 'ReactiveCocoa' 10 | pod 'ReactiveViewModel', tag: '0.1.2', git: 'https://github.com/ashfurrow/ReactiveViewModel.git' 11 | pod '500px-iOS-api' 12 | pod 'SVProgressHUD', '1.0' 13 | pod 'AFImageDownloader' 14 | pod 'libextobjc' 15 | 16 | end 17 | 18 | target "FRPTests" do 19 | 20 | pod 'Specta', '~> 0.2.1' 21 | pod 'Expecta', '~> 0.2' 22 | pod 'OCMock', '~> 2.2.2' 23 | 24 | end 25 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - 500px-iOS-api (1.0.5) 3 | - AFImageDownloader (1.1.0) 4 | - Expecta (0.4.2) 5 | - libextobjc (0.4.1): 6 | - libextobjc/EXTADT (= 0.4.1) 7 | - libextobjc/EXTConcreteProtocol (= 0.4.1) 8 | - libextobjc/EXTKeyPathCoding (= 0.4.1) 9 | - libextobjc/EXTNil (= 0.4.1) 10 | - libextobjc/EXTSafeCategory (= 0.4.1) 11 | - libextobjc/EXTScope (= 0.4.1) 12 | - libextobjc/EXTSelectorChecking (= 0.4.1) 13 | - libextobjc/EXTSynthesize (= 0.4.1) 14 | - libextobjc/NSInvocation+EXT (= 0.4.1) 15 | - libextobjc/NSMethodSignature+EXT (= 0.4.1) 16 | - libextobjc/RuntimeExtensions (= 0.4.1) 17 | - libextobjc/UmbrellaHeader (= 0.4.1) 18 | - libextobjc/EXTADT (0.4.1): 19 | - libextobjc/RuntimeExtensions 20 | - libextobjc/EXTConcreteProtocol (0.4.1): 21 | - libextobjc/RuntimeExtensions 22 | - libextobjc/EXTKeyPathCoding (0.4.1): 23 | - libextobjc/RuntimeExtensions 24 | - libextobjc/EXTNil (0.4.1): 25 | - libextobjc/RuntimeExtensions 26 | - libextobjc/EXTSafeCategory (0.4.1): 27 | - libextobjc/RuntimeExtensions 28 | - libextobjc/EXTScope (0.4.1): 29 | - libextobjc/RuntimeExtensions 30 | - libextobjc/EXTSelectorChecking (0.4.1): 31 | - libextobjc/RuntimeExtensions 32 | - libextobjc/EXTSynthesize (0.4.1): 33 | - libextobjc/RuntimeExtensions 34 | - libextobjc/NSInvocation+EXT (0.4.1): 35 | - libextobjc/RuntimeExtensions 36 | - libextobjc/NSMethodSignature+EXT (0.4.1): 37 | - libextobjc/RuntimeExtensions 38 | - libextobjc/RuntimeExtensions (0.4.1) 39 | - libextobjc/UmbrellaHeader (0.4.1) 40 | - OCMock (2.2.4) 41 | - ReactiveCocoa (2.5): 42 | - ReactiveCocoa/UI (= 2.5) 43 | - ReactiveCocoa/Core (2.5): 44 | - ReactiveCocoa/no-arc 45 | - ReactiveCocoa/no-arc (2.5) 46 | - ReactiveCocoa/UI (2.5): 47 | - ReactiveCocoa/Core 48 | - ReactiveViewModel (0.1.1): 49 | - ReactiveCocoa (~> 2.1) 50 | - Specta (0.2.1) 51 | - SVProgressHUD (1.0) 52 | 53 | DEPENDENCIES: 54 | - 500px-iOS-api 55 | - AFImageDownloader 56 | - Expecta (~> 0.2) 57 | - libextobjc 58 | - OCMock (~> 2.2.2) 59 | - ReactiveCocoa 60 | - ReactiveViewModel (from `https://github.com/ashfurrow/ReactiveViewModel.git`, 61 | tag `0.1.2`) 62 | - Specta (~> 0.2.1) 63 | - SVProgressHUD (= 1.0) 64 | 65 | EXTERNAL SOURCES: 66 | ReactiveViewModel: 67 | :git: https://github.com/ashfurrow/ReactiveViewModel.git 68 | :tag: 0.1.2 69 | 70 | CHECKOUT OPTIONS: 71 | ReactiveViewModel: 72 | :git: https://github.com/ashfurrow/ReactiveViewModel.git 73 | :tag: 0.1.2 74 | 75 | SPEC CHECKSUMS: 76 | 500px-iOS-api: d9e46cb6300cef1a12964641374aa467d3cfb891 77 | AFImageDownloader: 98888db67fa2b3947b5e76f9015971eaa3cea9d5 78 | Expecta: 78b4e8b0c8291fa4524d7f74016b6065c2e391ec 79 | libextobjc: a650fc1bf489a3d3a9bc2e621efa3e1006fc5471 80 | OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 81 | ReactiveCocoa: e2db045570aa97c695e7aa97c2bcab222ae51f4a 82 | ReactiveViewModel: 5d852269482387abd4e343cff4c05004967a0707 83 | Specta: 15a276a6343867b426d5ed135d5aa4d04123a573 84 | SVProgressHUD: 46088807596a795cbcb4affd92cf3f13b6ab3dda 85 | 86 | COCOAPODS: 0.39.0 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **500px [shut down their API](https://support.500px.com/hc/en-us/articles/360002435653-API-)**, which this repo relied on. **The code here will no longer work**. Sorry about that – it's a shame, too, since their API was something that made 500px special and now it's gone. 2 | 3 | ![](http://static.ashfurrow.com/github/FRPII.png) 4 | ======================== 5 | 6 | A demonstration of how to use FRP with ReactiveCocoa in an iOS context using the 500px API. 7 | 8 | This is code to accompany [my book](https://leanpub.com/iosfrp). 9 | 10 | Installing 11 | ---------- 12 | 13 | Clone the repo and run `pod install` from the repo's directory to install the required pods. 14 | 15 | ```sh 16 | git clone https://github.com/ashfurrow/FunctionalReactivePixels.git 17 | cd FunctionalReactivePixels 18 | pod install 19 | ``` 20 | 21 | Please **do not** click the "Download ZIP" button on this page. 22 | 23 | ![](http://static.ashfurrow.com/github/functionalreactivepixels.png) 24 | 25 | Please don't go all creepy with my consumer key. 26 | --------------------------------------------------------------------------------