├── .gitignore ├── LICENSE ├── Podfile ├── Podfile.lock ├── Pods ├── Alamofire │ ├── LICENSE │ ├── README.md │ └── Source │ │ ├── AFError.swift │ │ ├── Alamofire.swift │ │ ├── DispatchQueue+Alamofire.swift │ │ ├── MultipartFormData.swift │ │ ├── NetworkReachabilityManager.swift │ │ ├── Notifications.swift │ │ ├── ParameterEncoding.swift │ │ ├── Request.swift │ │ ├── Response.swift │ │ ├── ResponseSerialization.swift │ │ ├── Result.swift │ │ ├── ServerTrustPolicy.swift │ │ ├── SessionDelegate.swift │ │ ├── SessionManager.swift │ │ ├── TaskDelegate.swift │ │ ├── Timeline.swift │ │ └── Validation.swift ├── AlamofireObjectMapper │ ├── AlamofireObjectMapper │ │ └── AlamofireObjectMapper.swift │ ├── LICENSE │ └── README.md ├── Kingfisher │ ├── LICENSE │ ├── README.md │ └── Sources │ │ ├── AnimatedImageView.swift │ │ ├── Box.swift │ │ ├── CacheSerializer.swift │ │ ├── Filter.swift │ │ ├── Image.swift │ │ ├── ImageCache.swift │ │ ├── ImageDownloader.swift │ │ ├── ImagePrefetcher.swift │ │ ├── ImageProcessor.swift │ │ ├── ImageTransition.swift │ │ ├── ImageView+Kingfisher.swift │ │ ├── Indicator.swift │ │ ├── Kingfisher.h │ │ ├── Kingfisher.swift │ │ ├── KingfisherManager.swift │ │ ├── KingfisherOptionsInfo.swift │ │ ├── RequestModifier.swift │ │ ├── Resource.swift │ │ ├── String+MD5.swift │ │ ├── ThreadHelper.swift │ │ └── UIButton+Kingfisher.swift ├── Manifest.lock ├── ObjectMapper │ ├── LICENSE │ ├── README-CN.md │ └── Sources │ │ ├── CustomDateFormatTransform.swift │ │ ├── DataTransform.swift │ │ ├── DateFormatterTransform.swift │ │ ├── DateTransform.swift │ │ ├── DictionaryTransform.swift │ │ ├── EnumOperators.swift │ │ ├── EnumTransform.swift │ │ ├── FromJSON.swift │ │ ├── HexColorTransform.swift │ │ ├── ISO8601DateTransform.swift │ │ ├── ImmutableMappable.swift │ │ ├── Map.swift │ │ ├── MapError.swift │ │ ├── Mappable.swift │ │ ├── Mapper.swift │ │ ├── NSDecimalNumberTransform.swift │ │ ├── Operators.swift │ │ ├── ToJSON.swift │ │ ├── TransformOf.swift │ │ ├── TransformOperators.swift │ │ ├── TransformType.swift │ │ └── URLTransform.swift ├── Pods.xcodeproj │ └── project.pbxproj ├── ReachabilitySwift │ ├── LICENSE │ ├── README.md │ └── Reachability │ │ └── Reachability.swift └── Target Support Files │ ├── Alamofire │ ├── Alamofire-dummy.m │ ├── Alamofire-prefix.pch │ ├── Alamofire-umbrella.h │ ├── Alamofire.modulemap │ ├── Alamofire.xcconfig │ └── Info.plist │ ├── AlamofireObjectMapper │ ├── AlamofireObjectMapper-dummy.m │ ├── AlamofireObjectMapper-prefix.pch │ ├── AlamofireObjectMapper-umbrella.h │ ├── AlamofireObjectMapper.modulemap │ ├── AlamofireObjectMapper.xcconfig │ └── Info.plist │ ├── Kingfisher │ ├── Info.plist │ ├── Kingfisher-dummy.m │ ├── Kingfisher-prefix.pch │ ├── Kingfisher-umbrella.h │ ├── Kingfisher.modulemap │ └── Kingfisher.xcconfig │ ├── ObjectMapper │ ├── Info.plist │ ├── ObjectMapper-dummy.m │ ├── ObjectMapper-prefix.pch │ ├── ObjectMapper-umbrella.h │ ├── ObjectMapper.modulemap │ └── ObjectMapper.xcconfig │ ├── Pods-Quaggify │ ├── Info.plist │ ├── Pods-Quaggify-acknowledgements.markdown │ ├── Pods-Quaggify-acknowledgements.plist │ ├── Pods-Quaggify-dummy.m │ ├── Pods-Quaggify-frameworks.sh │ ├── Pods-Quaggify-resources.sh │ ├── Pods-Quaggify-umbrella.h │ ├── Pods-Quaggify.debug.xcconfig │ ├── Pods-Quaggify.modulemap │ └── Pods-Quaggify.release.xcconfig │ ├── Pods-QuaggifyTests │ ├── Info.plist │ ├── Pods-QuaggifyTests-acknowledgements.markdown │ ├── Pods-QuaggifyTests-acknowledgements.plist │ ├── Pods-QuaggifyTests-dummy.m │ ├── Pods-QuaggifyTests-frameworks.sh │ ├── Pods-QuaggifyTests-resources.sh │ ├── Pods-QuaggifyTests-umbrella.h │ ├── Pods-QuaggifyTests.debug.xcconfig │ ├── Pods-QuaggifyTests.modulemap │ └── Pods-QuaggifyTests.release.xcconfig │ └── ReachabilitySwift │ ├── Info.plist │ ├── ReachabilitySwift-dummy.m │ ├── ReachabilitySwift-prefix.pch │ ├── ReachabilitySwift-umbrella.h │ ├── ReachabilitySwift.modulemap │ └── ReachabilitySwift.xcconfig ├── Quaggify.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Quaggify.xcworkspace └── contents.xcworkspacedata ├── Quaggify ├── API.swift ├── Album.swift ├── AlbumCell.swift ├── AlbumHeaderView.swift ├── AlbumViewController.swift ├── AlbumsViewController.swift ├── Alert.swift ├── AppDelegate.swift ├── Array+Extension.swift ├── Artist.swift ├── ArtistCell.swift ├── ArtistHeaderView.swift ├── ArtistViewController.swift ├── ArtistsViewController.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── icon_add_playlist.imageset │ │ ├── Contents.json │ │ ├── music_add-512 copy-1.png │ │ ├── music_add-512 copy-2.png │ │ └── music_add-512 copy.png │ ├── icon_more.imageset │ │ ├── Contents.json │ │ ├── icon_more.png │ │ ├── icon_more@2x.png │ │ └── icon_more@3x.png │ ├── icon_remove.imageset │ │ ├── Contents.json │ │ ├── icon_remove.png │ │ ├── icon_remove@2x.png │ │ └── icon_remove@3x.png │ ├── placeholder.imageset │ │ ├── Contents.json │ │ ├── placeholder-1.png │ │ ├── placeholder-2.png │ │ └── placeholder.png │ ├── tab_icon_browse.imageset │ │ ├── Contents.json │ │ ├── tab_icon_browse.png │ │ ├── tab_icon_browse@2x.png │ │ └── tab_icon_browse@3x.png │ ├── tab_icon_browse_filled.imageset │ │ ├── Contents.json │ │ ├── tab_icon_browse_filled.png │ │ ├── tab_icon_browse_filled@2x.png │ │ └── tab_icon_browse_filled@3x.png │ ├── tab_icon_home.imageset │ │ ├── Contents.json │ │ ├── tab_icon_home.png │ │ ├── tab_icon_home@2x.png │ │ └── tab_icon_home_filled@3x.png │ ├── tab_icon_home_filled.imageset │ │ ├── Contents.json │ │ ├── Home Filled-75.png │ │ ├── tab_icon_home_filled.png │ │ └── tab_icon_home_filled@2x.png │ ├── tab_icon_library.imageset │ │ ├── Contents.json │ │ ├── tab_icon_library.png │ │ ├── tab_icon_library@2x.png │ │ └── tab_icon_library@3x.png │ ├── tab_icon_library_filled.imageset │ │ ├── Contents.json │ │ ├── tab_icon_library_filled.png │ │ ├── tab_icon_library_filled@2x.png │ │ └── tab_icon_library_filled@3x.png │ └── tab_icon_search.imageset │ │ ├── Contents.json │ │ ├── tab_icon_search.png │ │ ├── tab_icon_search@2x.png │ │ └── tab_icon_search@3x.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── CollectionViewCell.swift ├── ColorPalette.swift ├── ExternalIds.swift ├── ExternalUrls.swift ├── Followers.swift ├── Font.swift ├── HomeViewController.swift ├── Image.swift ├── Info.plist ├── LibraryViewController.swift ├── LoadingFooterView.swift ├── LoginViewController.swift ├── Montserrat-Bold.ttf ├── Montserrat-Regular.ttf ├── NavigationController.swift ├── Notification+Extension.swift ├── Playlist.swift ├── PlaylistCell.swift ├── PlaylistHeaderView.swift ├── PlaylistTrack.swift ├── PlaylistTracks.swift ├── PlaylistViewController.swift ├── PlaylistsViewController.swift ├── Quaggify.xcdatamodeld │ ├── .xccurrentversion │ └── Quaggify.xcdatamodel │ │ └── contents ├── RecentSearches.swift ├── RecentSearchesCell.swift ├── RefreshTokenResponse.swift ├── ScrollDelegate.swift ├── SearchHeaderView.swift ├── SearchViewController.swift ├── SeeAllCell.swift ├── SpotifyFooter.swift ├── SpotifyObject.swift ├── SpotifyObjectType.swift ├── SpotifySearchResponse.swift ├── SpotifyService.swift ├── String+Extension.swift ├── TabBarController.swift ├── Track.swift ├── TrackCell.swift ├── TrackOptionsViewController.swift ├── TrackViewController.swift ├── TracksViewController.swift ├── UIApplication+Extension.swift ├── UICollectionReusableView+Extension.swift ├── UIColor+Extension.swift ├── UINavigationBar+Extension.swift ├── UIView+Extension.swift ├── URL+Extension.swift ├── User.swift └── ViewController.swift ├── QuaggifyTests ├── Info.plist └── QuaggifyTests.swift ├── README.md └── assets ├── overview1.png └── overview2.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jonathan Bijos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '11.0' 3 | 4 | target 'Quaggify' do 5 | use_frameworks! 6 | 7 | pod 'Alamofire', '~> 4.3' 8 | pod 'AlamofireObjectMapper', '~> 4.0' 9 | pod 'ObjectMapper', '~> 2.2' 10 | pod 'ReachabilitySwift' 11 | pod 'Kingfisher', '~> 3.4.0' 12 | 13 | # Pods for Quaggify 14 | 15 | target 'QuaggifyTests' do 16 | inherit! :search_paths 17 | # Pods for testing 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.3.0) 3 | - AlamofireObjectMapper (4.0.1): 4 | - Alamofire (~> 4.1) 5 | - ObjectMapper (~> 2.0) 6 | - Kingfisher (3.4.0) 7 | - ObjectMapper (2.2.2) 8 | - ReachabilitySwift (3) 9 | 10 | DEPENDENCIES: 11 | - Alamofire (~> 4.3) 12 | - AlamofireObjectMapper (~> 4.0) 13 | - Kingfisher (~> 3.4.0) 14 | - ObjectMapper (~> 2.2) 15 | - ReachabilitySwift 16 | 17 | SPEC CHECKSUMS: 18 | Alamofire: 856a113053a7bc9cbe5d6367a555d773fc5cfef7 19 | AlamofireObjectMapper: 842d2eed43a4d0593ff0110d54ac86a170c4519c 20 | Kingfisher: 5690cfb3d1bddf3a98d679a3a34d0b7294974ed2 21 | ObjectMapper: 9e385c2295bcc4e16eabbcfc85db801442bba545 22 | ReachabilitySwift: f5b9bb30a0777fac8f09ce8b067e32faeb29bb64 23 | 24 | PODFILE CHECKSUM: 6161f2f96882405063f686c06f32b4a212dcbf07 25 | 26 | COCOAPODS: 1.2.0 27 | -------------------------------------------------------------------------------- /Pods/Alamofire/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/DispatchQueue+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Dispatch 26 | import Foundation 27 | 28 | extension DispatchQueue { 29 | static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) } 30 | static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) } 31 | static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) } 32 | static var background: DispatchQueue { return DispatchQueue.global(qos: .background) } 33 | 34 | func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) { 35 | asyncAfter(deadline: .now() + delay, execute: closure) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension Notification.Name { 28 | /// Used as a namespace for all `URLSessionTask` related notifications. 29 | public struct Task { 30 | /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`. 31 | public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume") 32 | 33 | /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`. 34 | public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend") 35 | 36 | /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`. 37 | public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel") 38 | 39 | /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`. 40 | public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete") 41 | } 42 | } 43 | 44 | // MARK: - 45 | 46 | extension Notification { 47 | /// Used as a namespace for all `Notification` user info dictionary keys. 48 | public struct Key { 49 | /// User info dictionary key representing the `URLSessionTask` associated with the notification. 50 | public static let Task = "org.alamofire.notification.key.task" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // 4 | // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | /// Used to represent whether a request was successful or encountered an error. 28 | /// 29 | /// - success: The request and all post processing operations were successful resulting in the serialization of the 30 | /// provided associated value. 31 | /// 32 | /// - failure: The request encountered an error resulting in a failure. The associated values are the original data 33 | /// provided by the server as well as the error that caused the failure. 34 | public enum Result { 35 | case success(Value) 36 | case failure(Error) 37 | 38 | /// Returns `true` if the result is a success, `false` otherwise. 39 | public var isSuccess: Bool { 40 | switch self { 41 | case .success: 42 | return true 43 | case .failure: 44 | return false 45 | } 46 | } 47 | 48 | /// Returns `true` if the result is a failure, `false` otherwise. 49 | public var isFailure: Bool { 50 | return !isSuccess 51 | } 52 | 53 | /// Returns the associated value if the result is a success, `nil` otherwise. 54 | public var value: Value? { 55 | switch self { 56 | case .success(let value): 57 | return value 58 | case .failure: 59 | return nil 60 | } 61 | } 62 | 63 | /// Returns the associated error value if the result is a failure, `nil` otherwise. 64 | public var error: Error? { 65 | switch self { 66 | case .success: 67 | return nil 68 | case .failure(let error): 69 | return error 70 | } 71 | } 72 | } 73 | 74 | // MARK: - CustomStringConvertible 75 | 76 | extension Result: CustomStringConvertible { 77 | /// The textual representation used when written to an output stream, which includes whether the result was a 78 | /// success or failure. 79 | public var description: String { 80 | switch self { 81 | case .success: 82 | return "SUCCESS" 83 | case .failure: 84 | return "FAILURE" 85 | } 86 | } 87 | } 88 | 89 | // MARK: - CustomDebugStringConvertible 90 | 91 | extension Result: CustomDebugStringConvertible { 92 | /// The debug textual representation used when written to an output stream, which includes whether the result was a 93 | /// success or failure in addition to the value or error. 94 | public var debugDescription: String { 95 | switch self { 96 | case .success(let value): 97 | return "SUCCESS: \(value)" 98 | case .failure(let error): 99 | return "FAILURE: \(error)" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Pods/AlamofireObjectMapper/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tristan Himmelman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Pods/Kingfisher/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Wei Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Pods/Kingfisher/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Kingfisher 4 | 5 |

6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | codebeat badge 22 | 23 | 24 | 25 | 26 |

27 | 28 | Kingfisher is a lightweight, pure-Swift library for downloading and caching images from the web. This project is heavily inspired by the popular [SDWebImage](https://github.com/rs/SDWebImage). It provides you a chance to use a pure-Swift alternative in your next app. 29 | 30 | ## Features 31 | 32 | - [x] Asynchronous image downloading and caching. 33 | - [x] `URLSession`-based networking. Basic image processors and filters supplied. 34 | - [x] Multiple-layer cache for both memory and disk. 35 | - [x] Cancelable downloading and processing tasks to improve performance. 36 | - [x] Independent components. Use the downloader or caching system separately as you need. 37 | - [x] Prefetching images and showing them from cache later when necessary. 38 | - [x] Extensions for `UIImageView`, `NSImage` and `UIButton` to directly set an image from a URL. 39 | - [x] Built-in transition animation when setting images. 40 | - [x] Extensible image processing and image format support. 41 | 42 | The simplest use-case is setting an image to an image view with the `UIImageView` extension: 43 | 44 | ```swift 45 | let url = URL(string: "url_of_your_image") 46 | imageView.kf.setImage(with: url) 47 | ``` 48 | 49 | Kingfisher will download the image from `url`, send it to both the memory cache and the disk cache, and display it in `imageView`. When you use the same code later, the image will be retrieved from cache and shown immediately. 50 | 51 | ## Requirements 52 | 53 | - iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+ 54 | - Swift 3 (Kingfisher 3.x), Swift 2.3 (Kingfisher 2.x) 55 | 56 | Main development of Kingfisher will support Swift 3. Only critical bug fixes will be made for Kingfisher 2.x. 57 | 58 | [Kingfisher 3.0 Migration Guide](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-3.0-Migration-Guide) - If you are upgrading to Kingfisher 3.x from an earlier version, please read this for more information. 59 | 60 | ## Next Steps 61 | 62 | We prepared a [wiki page](https://github.com/onevcat/Kingfisher/wiki). You can find tons of useful things there. 63 | 64 | * [Installation Guide](https://github.com/onevcat/Kingfisher/wiki/Installation-Guide) - Follow it to integrate Kingfisher into your project. 65 | * [Cheat Sheet](https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet)- Curious about what Kingfisher could do and how would it look like when used in your project? See this page for useful code snippets. If you are already familiar with Kingfisher, you could also learn new tricks to improve the way you use Kingfisher! 66 | * [API Reference](http://cocoadocs.org/docsets/Kingfisher/) - Lastly, please remember to read the full whenever you may need a more detailed reference. 67 | 68 | ## Other 69 | 70 | ### Future of Kingfisher 71 | 72 | I want to keep Kingfisher lightweight. This framework will focus on providing a simple solution for downloading and caching images. This doesn’t mean the framework can’t be improved. Kingfisher is far from perfect, so necessary and useful updates will be made to make it better. 73 | 74 | ### About the logo 75 | 76 | The logo of Kingfisher is inspired by [Tangram (七巧板)](http://en.wikipedia.org/wiki/Tangram), a dissection puzzle consisting of seven flat shapes from China. I believe she's a kingfisher bird instead of a swift, but someone insists that she is a pigeon. I guess I should give her a name. Hi, guys, do you have any suggestions? 77 | 78 | ### Contact 79 | 80 | Follow and contact me on [Twitter](http://twitter.com/onevcat) or [Sina Weibo](http://weibo.com/onevcat). If you find an issue, just [open a ticket](https://github.com/onevcat/Kingfisher/issues/new). Pull requests are warmly welcome as well. 81 | 82 | ### License 83 | 84 | Kingfisher is released under the MIT license. See LICENSE for details. 85 | 86 | 87 | -------------------------------------------------------------------------------- /Pods/Kingfisher/Sources/Box.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Box.swift 3 | // Kingfisher 4 | // 5 | // Created by WANG WEI on 2016/09/12. 6 | // Copyright © 2016年 Wei Wang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Box { 12 | let value: T 13 | init(value: T) { 14 | self.value = value 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Pods/Kingfisher/Sources/CacheSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CacheSerializer.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 2016/09/02. 6 | // 7 | // Copyright (c) 2017 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | /// An `CacheSerializer` would be used to convert some data to an image object for 30 | /// retrieving from disk cache and vice versa for storing to disk cache. 31 | public protocol CacheSerializer { 32 | 33 | /// Get the serialized data from a provided image 34 | /// and optional original data for caching to disk. 35 | /// 36 | /// 37 | /// - parameter image: The image needed to be serialized. 38 | /// - parameter original: The original data which is just downloaded. 39 | /// If the image is retrieved from cache instead of 40 | /// downloaded, it will be `nil`. 41 | /// 42 | /// - returns: A data which will be stored to cache, or `nil` when no valid 43 | /// data could be serialized. 44 | func data(with image: Image, original: Data?) -> Data? 45 | 46 | /// Get an image deserialized from provided data. 47 | /// 48 | /// - parameter data: The data from which an image should be deserialized. 49 | /// - parameter options: Options for deserialization. 50 | /// 51 | /// - returns: An image deserialized or `nil` when no valid image 52 | /// could be deserialized. 53 | func image(with data: Data, options: KingfisherOptionsInfo?) -> Image? 54 | } 55 | 56 | 57 | /// `DefaultCacheSerializer` is a basic `CacheSerializer` used in default cache of 58 | /// Kingfisher. It could serialize and deserialize PNG, JEPG and GIF images. For 59 | /// image other than these formats, a normalized `pngRepresentation` will be used. 60 | public struct DefaultCacheSerializer: CacheSerializer { 61 | 62 | public static let `default` = DefaultCacheSerializer() 63 | private init() {} 64 | 65 | public func data(with image: Image, original: Data?) -> Data? { 66 | let imageFormat = original?.kf.imageFormat ?? .unknown 67 | 68 | let data: Data? 69 | switch imageFormat { 70 | case .PNG: data = image.kf.pngRepresentation() 71 | case .JPEG: data = image.kf.jpegRepresentation(compressionQuality: 1.0) 72 | case .GIF: data = image.kf.gifRepresentation() 73 | case .unknown: data = original ?? image.kf.normalized.kf.pngRepresentation() 74 | } 75 | 76 | return data 77 | } 78 | 79 | public func image(with data: Data, options: KingfisherOptionsInfo?) -> Image? { 80 | let options = options ?? KingfisherEmptyOptionsInfo 81 | return Kingfisher.image( 82 | data: data, 83 | scale: options.scaleFactor, 84 | preloadAllGIFData: options.preloadAllGIFData, 85 | onlyFirstFrame: options.onlyLoadFirstFrame) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Pods/Kingfisher/Sources/ImageTransition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageTransition.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/9/18. 6 | // 7 | // Copyright (c) 2017 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | #if os(macOS) 28 | // Not implemented for macOS and watchOS yet. 29 | 30 | import AppKit 31 | 32 | /// Image transition is not supported on macOS. 33 | public enum ImageTransition { 34 | case none 35 | var duration: TimeInterval { 36 | return 0 37 | } 38 | } 39 | 40 | #elseif os(watchOS) 41 | import UIKit 42 | /// Image transition is not supported on watchOS. 43 | public enum ImageTransition { 44 | case none 45 | var duration: TimeInterval { 46 | return 0 47 | } 48 | } 49 | #else 50 | import UIKit 51 | 52 | /** 53 | Transition effect which will be used when an image downloaded and set by `UIImageView` extension API in Kingfisher. 54 | You can assign an enum value with transition duration as an item in `KingfisherOptionsInfo` 55 | to enable the animation transition. 56 | 57 | Apple's UIViewAnimationOptions is used under the hood. 58 | For custom transition, you should specified your own transition options, animations and 59 | comletion handler as well. 60 | */ 61 | public enum ImageTransition { 62 | /// No animation transistion. 63 | case none 64 | 65 | /// Fade in the loaded image. 66 | case fade(TimeInterval) 67 | 68 | /// Flip from left transition. 69 | case flipFromLeft(TimeInterval) 70 | 71 | /// Flip from right transition. 72 | case flipFromRight(TimeInterval) 73 | 74 | /// Flip from top transition. 75 | case flipFromTop(TimeInterval) 76 | 77 | /// Flip from bottom transition. 78 | case flipFromBottom(TimeInterval) 79 | 80 | /// Custom transition. 81 | case custom(duration: TimeInterval, 82 | options: UIViewAnimationOptions, 83 | animations: ((UIImageView, UIImage) -> Void)?, 84 | completion: ((Bool) -> Void)?) 85 | 86 | var duration: TimeInterval { 87 | switch self { 88 | case .none: return 0 89 | case .fade(let duration): return duration 90 | 91 | case .flipFromLeft(let duration): return duration 92 | case .flipFromRight(let duration): return duration 93 | case .flipFromTop(let duration): return duration 94 | case .flipFromBottom(let duration): return duration 95 | 96 | case .custom(let duration, _, _, _): return duration 97 | } 98 | } 99 | 100 | var animationOptions: UIViewAnimationOptions { 101 | switch self { 102 | case .none: return [] 103 | case .fade(_): return .transitionCrossDissolve 104 | 105 | case .flipFromLeft(_): return .transitionFlipFromLeft 106 | case .flipFromRight(_): return .transitionFlipFromRight 107 | case .flipFromTop(_): return .transitionFlipFromTop 108 | case .flipFromBottom(_): return .transitionFlipFromBottom 109 | 110 | case .custom(_, let options, _, _): return options 111 | } 112 | } 113 | 114 | var animations: ((UIImageView, UIImage) -> Void)? { 115 | switch self { 116 | case .custom(_, _, let animations, _): return animations 117 | default: return { $0.image = $1 } 118 | } 119 | } 120 | 121 | var completion: ((Bool) -> Void)? { 122 | switch self { 123 | case .custom(_, _, _, let completion): return completion 124 | default: return nil 125 | } 126 | } 127 | } 128 | #endif 129 | -------------------------------------------------------------------------------- /Pods/Kingfisher/Sources/Kingfisher.h: -------------------------------------------------------------------------------- 1 | // 2 | // Kingfisher.h 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/4/6. 6 | // 7 | // Copyright (c) 2017 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | #import 28 | 29 | //! Project version number for Kingfisher. 30 | FOUNDATION_EXPORT double KingfisherVersionNumber; 31 | 32 | //! Project version string for Kingfisher. 33 | FOUNDATION_EXPORT const unsigned char KingfisherVersionString[]; 34 | 35 | // In this header, you should import all the public headers of your framework using statements like #import 36 | 37 | 38 | -------------------------------------------------------------------------------- /Pods/Kingfisher/Sources/Kingfisher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Kingfisher.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 16/9/14. 6 | // 7 | // Copyright (c) 2017 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | import ImageIO 29 | 30 | #if os(macOS) 31 | import AppKit 32 | public typealias Image = NSImage 33 | public typealias Color = NSColor 34 | public typealias ImageView = NSImageView 35 | typealias Button = NSButton 36 | #else 37 | import UIKit 38 | public typealias Image = UIImage 39 | public typealias Color = UIColor 40 | #if !os(watchOS) 41 | public typealias ImageView = UIImageView 42 | typealias Button = UIButton 43 | #endif 44 | #endif 45 | 46 | public final class Kingfisher { 47 | public let base: Base 48 | public init(_ base: Base) { 49 | self.base = base 50 | } 51 | } 52 | 53 | /** 54 | A type that has Kingfisher extensions. 55 | */ 56 | public protocol KingfisherCompatible { 57 | associatedtype CompatibleType 58 | var kf: CompatibleType { get } 59 | } 60 | 61 | public extension KingfisherCompatible { 62 | public var kf: Kingfisher { 63 | get { return Kingfisher(self) } 64 | } 65 | } 66 | 67 | extension Image: KingfisherCompatible { } 68 | #if !os(watchOS) 69 | extension ImageView: KingfisherCompatible { } 70 | extension Button: KingfisherCompatible { } 71 | #endif 72 | -------------------------------------------------------------------------------- /Pods/Kingfisher/Sources/RequestModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestModifier.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 2016/09/05. 6 | // 7 | // Copyright (c) 2017 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | /// Request modifier of image downloader. 30 | public protocol ImageDownloadRequestModifier { 31 | func modified(for request: URLRequest) -> URLRequest? 32 | } 33 | 34 | struct NoModifier: ImageDownloadRequestModifier { 35 | static let `default` = NoModifier() 36 | private init() {} 37 | func modified(for request: URLRequest) -> URLRequest? { 38 | return request 39 | } 40 | } 41 | 42 | public struct AnyModifier: ImageDownloadRequestModifier { 43 | 44 | let block: (URLRequest) -> URLRequest? 45 | 46 | public func modified(for request: URLRequest) -> URLRequest? { 47 | return block(request) 48 | } 49 | 50 | public init(modify: @escaping (URLRequest) -> URLRequest? ) { 51 | block = modify 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Pods/Kingfisher/Sources/Resource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Resource.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/4/6. 6 | // 7 | // Copyright (c) 2017 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | 30 | /// `Resource` protocol defines how to download and cache a resource from network. 31 | public protocol Resource { 32 | /// The key used in cache. 33 | var cacheKey: String { get } 34 | 35 | /// The target image URL. 36 | var downloadURL: URL { get } 37 | } 38 | 39 | /** 40 | ImageResource is a simple combination of `downloadURL` and `cacheKey`. 41 | 42 | When passed to image view set methods, Kingfisher will try to download the target 43 | image from the `downloadURL`, and then store it with the `cacheKey` as the key in cache. 44 | */ 45 | public struct ImageResource: Resource { 46 | /// The key used in cache. 47 | public let cacheKey: String 48 | 49 | /// The target image URL. 50 | public let downloadURL: URL 51 | 52 | /** 53 | Create a resource. 54 | 55 | - parameter downloadURL: The target image URL. 56 | - parameter cacheKey: The cache key. If `nil`, Kingfisher will use the `absoluteString` of `downloadURL` as the key. 57 | 58 | - returns: A resource. 59 | */ 60 | public init(downloadURL: URL, cacheKey: String? = nil) { 61 | self.downloadURL = downloadURL 62 | self.cacheKey = cacheKey ?? downloadURL.absoluteString 63 | } 64 | } 65 | 66 | /** 67 | URL conforms to `Resource` in Kingfisher. 68 | The `absoluteString` of this URL is used as `cacheKey`. And the URL itself will be used as `downloadURL`. 69 | If you need customize the url and/or cache key, use `ImageResource` instead. 70 | */ 71 | extension URL: Resource { 72 | public var cacheKey: String { return absoluteString } 73 | public var downloadURL: URL { return self } 74 | } 75 | -------------------------------------------------------------------------------- /Pods/Kingfisher/Sources/ThreadHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThreadHelper.swift 3 | // Kingfisher 4 | // 5 | // Created by Wei Wang on 15/10/9. 6 | // 7 | // Copyright (c) 2017 Wei Wang 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | // THE SOFTWARE. 26 | 27 | import Foundation 28 | 29 | extension DispatchQueue { 30 | // This method will dispatch the `block` to self. 31 | // If `self` is the main queue, and current thread is main thread, the block 32 | // will be invoked immediately instead of being dispatched. 33 | func safeAsync(_ block: @escaping ()->()) { 34 | if self === DispatchQueue.main && Thread.isMainThread { 35 | block() 36 | } else { 37 | async { block() } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.3.0) 3 | - AlamofireObjectMapper (4.0.1): 4 | - Alamofire (~> 4.1) 5 | - ObjectMapper (~> 2.0) 6 | - Kingfisher (3.4.0) 7 | - ObjectMapper (2.2.2) 8 | - ReachabilitySwift (3) 9 | 10 | DEPENDENCIES: 11 | - Alamofire (~> 4.3) 12 | - AlamofireObjectMapper (~> 4.0) 13 | - Kingfisher (~> 3.4.0) 14 | - ObjectMapper (~> 2.2) 15 | - ReachabilitySwift 16 | 17 | SPEC CHECKSUMS: 18 | Alamofire: 856a113053a7bc9cbe5d6367a555d773fc5cfef7 19 | AlamofireObjectMapper: 842d2eed43a4d0593ff0110d54ac86a170c4519c 20 | Kingfisher: 5690cfb3d1bddf3a98d679a3a34d0b7294974ed2 21 | ObjectMapper: 9e385c2295bcc4e16eabbcfc85db801442bba545 22 | ReachabilitySwift: f5b9bb30a0777fac8f09ce8b067e32faeb29bb64 23 | 24 | PODFILE CHECKSUM: 6161f2f96882405063f686c06f32b4a212dcbf07 25 | 26 | COCOAPODS: 1.2.0 27 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2014 Hearst 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/CustomDateFormatTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomDateFormatTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Dan McCracken on 3/8/15. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class CustomDateFormatTransform: DateFormatterTransform { 32 | 33 | public init(formatString: String) { 34 | let formatter = DateFormatter() 35 | formatter.locale = Locale(identifier: "en_US_POSIX") 36 | formatter.dateFormat = formatString 37 | 38 | super.init(dateFormatter: formatter) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/DataTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Yagrushkin, Evgeny on 8/30/16. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class DataTransform: TransformType { 32 | public typealias Object = Data 33 | public typealias JSON = String 34 | 35 | public init() {} 36 | 37 | open func transformFromJSON(_ value: Any?) -> Data? { 38 | guard let string = value as? String else{ 39 | return nil 40 | } 41 | return Data(base64Encoded: string) 42 | } 43 | 44 | open func transformToJSON(_ value: Data?) -> String? { 45 | guard let data = value else{ 46 | return nil 47 | } 48 | return data.base64EncodedString() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/DateFormatterTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormatterTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 2015-03-09. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class DateFormatterTransform: TransformType { 32 | public typealias Object = Date 33 | public typealias JSON = String 34 | 35 | public let dateFormatter: DateFormatter 36 | 37 | public init(dateFormatter: DateFormatter) { 38 | self.dateFormatter = dateFormatter 39 | } 40 | 41 | open func transformFromJSON(_ value: Any?) -> Date? { 42 | if let dateString = value as? String { 43 | return dateFormatter.date(from: dateString) 44 | } 45 | return nil 46 | } 47 | 48 | open func transformToJSON(_ value: Date?) -> String? { 49 | if let date = value { 50 | return dateFormatter.string(from: date) 51 | } 52 | return nil 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/DateTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 2014-10-13. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class DateTransform: TransformType { 32 | public typealias Object = Date 33 | public typealias JSON = Double 34 | 35 | public init() {} 36 | 37 | open func transformFromJSON(_ value: Any?) -> Date? { 38 | if let timeInt = value as? Double { 39 | return Date(timeIntervalSince1970: TimeInterval(timeInt)) 40 | } 41 | 42 | if let timeStr = value as? String { 43 | return Date(timeIntervalSince1970: TimeInterval(atof(timeStr))) 44 | } 45 | 46 | return nil 47 | } 48 | 49 | open func transformToJSON(_ value: Date?) -> Double? { 50 | if let date = value { 51 | return Double(date.timeIntervalSince1970) 52 | } 53 | return nil 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/DictionaryTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DictionaryTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Milen Halachev on 7/20/16. 6 | // Copyright © 2016 hearst. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | ///Transforms [String: AnyObject] <-> [Key: Value] where Key is RawRepresentable as String, Value is Mappable 12 | public struct DictionaryTransform: TransformType where Key: Hashable, Key: RawRepresentable, Key.RawValue == String, Value: Mappable { 13 | 14 | public init() { 15 | 16 | } 17 | 18 | public func transformFromJSON(_ value: Any?) -> [Key: Value]? { 19 | 20 | guard let json = value as? [String: Any] else { 21 | 22 | return nil 23 | } 24 | 25 | let result = json.reduce([:]) { (result, element) -> [Key: Value] in 26 | 27 | guard 28 | let key = Key(rawValue: element.0), 29 | let valueJSON = element.1 as? [String: Any], 30 | let value = Value(JSON: valueJSON) 31 | else { 32 | 33 | return result 34 | } 35 | 36 | var result = result 37 | result[key] = value 38 | return result 39 | } 40 | 41 | return result 42 | } 43 | 44 | public func transformToJSON(_ value: [Key: Value]?) -> Any? { 45 | 46 | let result = value?.reduce([:]) { (result, element) -> [String: Any] in 47 | 48 | let key = element.0.rawValue 49 | let value = element.1.toJSON() 50 | 51 | var result = result 52 | result[key] = value 53 | return result 54 | } 55 | 56 | return result 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/EnumOperators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnumOperators.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 2016-09-26. 6 | // Copyright © 2016 hearst. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | // MARK:- Raw Representable types 13 | 14 | /// Object of Raw Representable type 15 | public func <- (left: inout T, right: Map) { 16 | left <- (right, EnumTransform()) 17 | } 18 | 19 | public func >>> (left: T, right: Map) { 20 | left >>> (right, EnumTransform()) 21 | } 22 | 23 | 24 | /// Optional Object of Raw Representable type 25 | public func <- (left: inout T?, right: Map) { 26 | left <- (right, EnumTransform()) 27 | } 28 | 29 | public func >>> (left: T?, right: Map) { 30 | left >>> (right, EnumTransform()) 31 | } 32 | 33 | 34 | /// Implicitly Unwrapped Optional Object of Raw Representable type 35 | public func <- (left: inout T!, right: Map) { 36 | left <- (right, EnumTransform()) 37 | } 38 | 39 | // MARK:- Arrays of Raw Representable type 40 | 41 | /// Array of Raw Representable object 42 | public func <- (left: inout [T], right: Map) { 43 | left <- (right, EnumTransform()) 44 | } 45 | 46 | public func >>> (left: [T], right: Map) { 47 | left >>> (right, EnumTransform()) 48 | } 49 | 50 | 51 | /// Array of Raw Representable object 52 | public func <- (left: inout [T]?, right: Map) { 53 | left <- (right, EnumTransform()) 54 | } 55 | 56 | public func >>> (left: [T]?, right: Map) { 57 | left >>> (right, EnumTransform()) 58 | } 59 | 60 | 61 | /// Array of Raw Representable object 62 | public func <- (left: inout [T]!, right: Map) { 63 | left <- (right, EnumTransform()) 64 | } 65 | 66 | // MARK:- Dictionaries of Raw Representable type 67 | 68 | /// Dictionary of Raw Representable object 69 | public func <- (left: inout [String: T], right: Map) { 70 | left <- (right, EnumTransform()) 71 | } 72 | 73 | public func >>> (left: [String: T], right: Map) { 74 | left >>> (right, EnumTransform()) 75 | } 76 | 77 | 78 | /// Dictionary of Raw Representable object 79 | public func <- (left: inout [String: T]?, right: Map) { 80 | left <- (right, EnumTransform()) 81 | } 82 | 83 | public func >>> (left: [String: T]?, right: Map) { 84 | left >>> (right, EnumTransform()) 85 | } 86 | 87 | 88 | /// Dictionary of Raw Representable object 89 | public func <- (left: inout [String: T]!, right: Map) { 90 | left <- (right, EnumTransform()) 91 | } 92 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/EnumTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnumTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Kaan Dedeoglu on 3/20/15. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class EnumTransform: TransformType { 32 | public typealias Object = T 33 | public typealias JSON = T.RawValue 34 | 35 | public init() {} 36 | 37 | open func transformFromJSON(_ value: Any?) -> T? { 38 | if let raw = value as? T.RawValue { 39 | return T(rawValue: raw) 40 | } 41 | return nil 42 | } 43 | 44 | open func transformToJSON(_ value: T?) -> T.RawValue? { 45 | if let obj = value { 46 | return obj.rawValue 47 | } 48 | return nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/HexColorTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HexColorTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Vitaliy Kuzmenko on 10/10/16. 6 | // Copyright © 2016 hearst. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) || os(watchOS) 10 | import UIKit 11 | #else 12 | import Cocoa 13 | #endif 14 | 15 | open class HexColorTransform: TransformType { 16 | 17 | #if os(iOS) || os(tvOS) || os(watchOS) 18 | public typealias Object = UIColor 19 | #else 20 | public typealias Object = NSColor 21 | #endif 22 | 23 | public typealias JSON = String 24 | 25 | var prefix: Bool = false 26 | 27 | var alpha: Bool = false 28 | 29 | public init(prefixToJSON: Bool = false, alphaToJSON: Bool = false) { 30 | alpha = alphaToJSON 31 | prefix = prefixToJSON 32 | } 33 | 34 | open func transformFromJSON(_ value: Any?) -> Object? { 35 | if let rgba = value as? String { 36 | if rgba.hasPrefix("#") { 37 | let index = rgba.characters.index(rgba.startIndex, offsetBy: 1) 38 | let hex = rgba.substring(from: index) 39 | return getColor(hex: hex) 40 | } else { 41 | return getColor(hex: rgba) 42 | } 43 | } 44 | return nil 45 | } 46 | 47 | open func transformToJSON(_ value: Object?) -> JSON? { 48 | if let value = value { 49 | return hexString(color: value) 50 | } 51 | return nil 52 | } 53 | 54 | fileprivate func hexString(color: Object) -> String { 55 | let comps = color.cgColor.components! 56 | let r = Int(comps[0] * 255) 57 | let g = Int(comps[1] * 255) 58 | let b = Int(comps[2] * 255) 59 | let a = Int(comps[3] * 255) 60 | var hexString: String = "" 61 | if prefix { 62 | hexString = "#" 63 | } 64 | hexString += String(format: "%02X%02X%02X", r, g, b) 65 | 66 | if alpha { 67 | hexString += String(format: "%02X", a) 68 | } 69 | return hexString 70 | } 71 | 72 | fileprivate func getColor(hex: String) -> Object? { 73 | var red: CGFloat = 0.0 74 | var green: CGFloat = 0.0 75 | var blue: CGFloat = 0.0 76 | var alpha: CGFloat = 1.0 77 | 78 | let scanner = Scanner(string: hex) 79 | var hexValue: CUnsignedLongLong = 0 80 | if scanner.scanHexInt64(&hexValue) { 81 | switch (hex.characters.count) { 82 | case 3: 83 | red = CGFloat((hexValue & 0xF00) >> 8) / 15.0 84 | green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0 85 | blue = CGFloat(hexValue & 0x00F) / 15.0 86 | case 4: 87 | red = CGFloat((hexValue & 0xF000) >> 12) / 15.0 88 | green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0 89 | blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0 90 | alpha = CGFloat(hexValue & 0x000F) / 15.0 91 | case 6: 92 | red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0 93 | green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0 94 | blue = CGFloat(hexValue & 0x0000FF) / 255.0 95 | case 8: 96 | red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0 97 | green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0 98 | blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0 99 | alpha = CGFloat(hexValue & 0x000000FF) / 255.0 100 | default: 101 | // Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8 102 | return nil 103 | } 104 | } else { 105 | // "Scan hex error 106 | return nil 107 | } 108 | #if os(iOS) || os(tvOS) || os(watchOS) 109 | return UIColor(red: red, green: green, blue: blue, alpha: alpha) 110 | #else 111 | return NSColor(calibratedRed: red, green: green, blue: blue, alpha: alpha) 112 | #endif 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/ISO8601DateTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ISO8601DateTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Jean-Pierre Mouilleseaux on 21 Nov 2014. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class ISO8601DateTransform: DateFormatterTransform { 32 | 33 | public init() { 34 | let formatter = DateFormatter() 35 | formatter.locale = Locale(identifier: "en_US_POSIX") 36 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" 37 | 38 | super.init(dateFormatter: formatter) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/MapError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapError.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 2016-09-26. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | public struct MapError: Error { 32 | public var key: String? 33 | public var currentValue: Any? 34 | public var reason: String? 35 | public var file: StaticString? 36 | public var function: StaticString? 37 | public var line: UInt? 38 | 39 | public init(key: String?, currentValue: Any?, reason: String?, file: StaticString? = nil, function: StaticString? = nil, line: UInt? = nil) { 40 | self.key = key 41 | self.currentValue = currentValue 42 | self.reason = reason 43 | self.file = file 44 | self.function = function 45 | self.line = line 46 | } 47 | } 48 | 49 | extension MapError: CustomStringConvertible { 50 | 51 | private var location: String? { 52 | guard let file = file, let function = function, let line = line else { return nil } 53 | let fileName = ((String(describing: file).components(separatedBy: "/").last ?? "").components(separatedBy: ".").first ?? "") 54 | return "\(fileName).\(function):\(line)" 55 | } 56 | 57 | public var description: String { 58 | let info: [(String, Any?)] = [ 59 | ("- reason", reason), 60 | ("- location", location), 61 | ("- key", key), 62 | ("- currentValue", currentValue), 63 | ] 64 | let infoString = info.map { "\($0): \($1 ?? "nil")" }.joined(separator: "\n") 65 | return "Got an error while mapping.\n\(infoString)" 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/Mappable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mappable.swift 3 | // ObjectMapper 4 | // 5 | // Created by Scott Hoyt on 10/25/15. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | /// BaseMappable should not be implemented directly. Mappable or StaticMappable should be used instead 32 | public protocol BaseMappable { 33 | /// This function is where all variable mappings should occur. It is executed by Mapper during the mapping (serialization and deserialization) process. 34 | mutating func mapping(map: Map) 35 | } 36 | 37 | public protocol Mappable: BaseMappable { 38 | /// This function can be used to validate JSON prior to mapping. Return nil to cancel mapping at this point 39 | init?(map: Map) 40 | } 41 | 42 | public protocol StaticMappable: BaseMappable { 43 | /// This is function that can be used to: 44 | /// 1) provide an existing cached object to be used for mapping 45 | /// 2) return an object of another class (which conforms to BaseMappable) to be used for mapping. For instance, you may inspect the JSON to infer the type of object that should be used for any given mapping 46 | static func objectForMapping(map: Map) -> BaseMappable? 47 | } 48 | 49 | public extension BaseMappable { 50 | 51 | /// Initializes object from a JSON String 52 | public init?(JSONString: String, context: MapContext? = nil) { 53 | if let obj: Self = Mapper(context: context).map(JSONString: JSONString) { 54 | self = obj 55 | } else { 56 | return nil 57 | } 58 | } 59 | 60 | /// Initializes object from a JSON Dictionary 61 | public init?(JSON: [String: Any], context: MapContext? = nil) { 62 | if let obj: Self = Mapper(context: context).map(JSON: JSON) { 63 | self = obj 64 | } else { 65 | return nil 66 | } 67 | } 68 | 69 | /// Returns the JSON Dictionary for the object 70 | public func toJSON() -> [String: Any] { 71 | return Mapper().toJSON(self) 72 | } 73 | 74 | /// Returns the JSON String for the object 75 | public func toJSONString(prettyPrint: Bool = false) -> String? { 76 | return Mapper().toJSONString(self, prettyPrint: prettyPrint) 77 | } 78 | } 79 | 80 | public extension Array where Element: BaseMappable { 81 | 82 | /// Initialize Array from a JSON String 83 | public init?(JSONString: String, context: MapContext? = nil) { 84 | if let obj: [Element] = Mapper(context: context).mapArray(JSONString: JSONString) { 85 | self = obj 86 | } else { 87 | return nil 88 | } 89 | } 90 | 91 | /// Initialize Array from a JSON Array 92 | public init?(JSONArray: [[String: Any]], context: MapContext? = nil) { 93 | if let obj: [Element] = Mapper(context: context).mapArray(JSONArray: JSONArray) { 94 | self = obj 95 | } else { 96 | return nil 97 | } 98 | } 99 | 100 | /// Returns the JSON Array 101 | public func toJSON() -> [[String: Any]] { 102 | return Mapper().toJSONArray(self) 103 | } 104 | 105 | /// Returns the JSON String for the object 106 | public func toJSONString(prettyPrint: Bool = false) -> String? { 107 | return Mapper().toJSONString(self, prettyPrint: prettyPrint) 108 | } 109 | } 110 | 111 | public extension Set where Element: BaseMappable { 112 | 113 | /// Initializes a set from a JSON String 114 | public init?(JSONString: String, context: MapContext? = nil) { 115 | if let obj: Set = Mapper(context: context).mapSet(JSONString: JSONString) { 116 | self = obj 117 | } else { 118 | return nil 119 | } 120 | } 121 | 122 | /// Initializes a set from JSON 123 | public init?(JSONArray: [[String: Any]], context: MapContext? = nil) { 124 | guard let obj = Mapper(context: context).mapSet(JSONArray: JSONArray) as Set? else { 125 | return nil 126 | } 127 | self = obj 128 | } 129 | 130 | /// Returns the JSON Set 131 | public func toJSON() -> [[String: Any]] { 132 | return Mapper().toJSONSet(self) 133 | } 134 | 135 | /// Returns the JSON String for the object 136 | public func toJSONString(prettyPrint: Bool = false) -> String? { 137 | return Mapper().toJSONString(self, prettyPrint: prettyPrint) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/NSDecimalNumberTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformOf.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 8/22/16. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class NSDecimalNumberTransform: TransformType { 32 | public typealias Object = NSDecimalNumber 33 | public typealias JSON = String 34 | 35 | public init() {} 36 | 37 | open func transformFromJSON(_ value: Any?) -> NSDecimalNumber? { 38 | if let string = value as? String { 39 | return NSDecimalNumber(string: string) 40 | } 41 | if let double = value as? Double { 42 | return NSDecimalNumber(value: double) 43 | } 44 | return nil 45 | } 46 | 47 | open func transformToJSON(_ value: NSDecimalNumber?) -> String? { 48 | guard let value = value else { return nil } 49 | return value.description 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/TransformOf.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformOf.swift 3 | // ObjectMapper 4 | // 5 | // Created by Syo Ikeda on 1/23/15. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | open class TransformOf: TransformType { 30 | public typealias Object = ObjectType 31 | public typealias JSON = JSONType 32 | 33 | private let fromJSON: (JSONType?) -> ObjectType? 34 | private let toJSON: (ObjectType?) -> JSONType? 35 | 36 | public init(fromJSON: @escaping(JSONType?) -> ObjectType?, toJSON: @escaping(ObjectType?) -> JSONType?) { 37 | self.fromJSON = fromJSON 38 | self.toJSON = toJSON 39 | } 40 | 41 | open func transformFromJSON(_ value: Any?) -> ObjectType? { 42 | return fromJSON(value as? JSONType) 43 | } 44 | 45 | open func transformToJSON(_ value: ObjectType?) -> JSONType? { 46 | return toJSON(value) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/TransformType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformType.swift 3 | // ObjectMapper 4 | // 5 | // Created by Syo Ikeda on 2/4/15. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | public protocol TransformType { 30 | associatedtype Object 31 | associatedtype JSON 32 | 33 | func transformFromJSON(_ value: Any?) -> Object? 34 | func transformToJSON(_ value: Object?) -> JSON? 35 | } 36 | -------------------------------------------------------------------------------- /Pods/ObjectMapper/Sources/URLTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 2014-10-27. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class URLTransform: TransformType { 32 | public typealias Object = URL 33 | public typealias JSON = String 34 | private let shouldEncodeURLString: Bool 35 | 36 | /** 37 | Initializes the URLTransform with an option to encode URL strings before converting them to an NSURL 38 | - parameter shouldEncodeUrlString: when true (the default) the string is encoded before passing 39 | to `NSURL(string:)` 40 | - returns: an initialized transformer 41 | */ 42 | public init(shouldEncodeURLString: Bool = true) { 43 | self.shouldEncodeURLString = shouldEncodeURLString 44 | } 45 | 46 | open func transformFromJSON(_ value: Any?) -> URL? { 47 | guard let URLString = value as? String else { return nil } 48 | 49 | if !shouldEncodeURLString { 50 | return URL(string: URLString) 51 | } 52 | 53 | guard let escapedURLString = URLString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else { 54 | return nil 55 | } 56 | return URL(string: escapedURLString) 57 | } 58 | 59 | open func transformToJSON(_ value: URL?) -> String? { 60 | if let URL = value { 61 | return URL.absoluteString 62 | } 63 | return nil 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Pods/ReachabilitySwift/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Ashley Mills 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Alamofire : NSObject 3 | @end 4 | @implementation PodsDummy_Alamofire 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double AlamofireVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire.modulemap: -------------------------------------------------------------------------------- 1 | framework module Alamofire { 2 | umbrella header "Alamofire-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Alamofire 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 4.3.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/AlamofireObjectMapper/AlamofireObjectMapper-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_AlamofireObjectMapper : NSObject 3 | @end 4 | @implementation PodsDummy_AlamofireObjectMapper 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/AlamofireObjectMapper/AlamofireObjectMapper-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/AlamofireObjectMapper/AlamofireObjectMapper-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double AlamofireObjectMapperVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char AlamofireObjectMapperVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/AlamofireObjectMapper/AlamofireObjectMapper.modulemap: -------------------------------------------------------------------------------- 1 | framework module AlamofireObjectMapper { 2 | umbrella header "AlamofireObjectMapper-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/AlamofireObjectMapper/AlamofireObjectMapper.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/AlamofireObjectMapper 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/ObjectMapper" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 5 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 6 | PODS_BUILD_DIR = $BUILD_DIR 7 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/AlamofireObjectMapper 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/AlamofireObjectMapper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 4.0.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Kingfisher/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.4.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Kingfisher/Kingfisher-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Kingfisher : NSObject 3 | @end 4 | @implementation PodsDummy_Kingfisher 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Kingfisher/Kingfisher-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Kingfisher/Kingfisher-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | #import "Kingfisher.h" 14 | 15 | FOUNDATION_EXPORT double KingfisherVersionNumber; 16 | FOUNDATION_EXPORT const unsigned char KingfisherVersionString[]; 17 | 18 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Kingfisher/Kingfisher.modulemap: -------------------------------------------------------------------------------- 1 | framework module Kingfisher { 2 | umbrella header "Kingfisher-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Kingfisher/Kingfisher.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Kingfisher 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_LDFLAGS = -framework "CFNetwork" 5 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 6 | PODS_BUILD_DIR = $BUILD_DIR 7 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Kingfisher 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | SWIFT_VERSION = 3.0 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ObjectMapper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.2.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ObjectMapper/ObjectMapper-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_ObjectMapper : NSObject 3 | @end 4 | @implementation PodsDummy_ObjectMapper 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ObjectMapper/ObjectMapper-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ObjectMapper/ObjectMapper-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double ObjectMapperVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char ObjectMapperVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ObjectMapper/ObjectMapper.modulemap: -------------------------------------------------------------------------------- 1 | framework module ObjectMapper { 2 | umbrella header "ObjectMapper-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ObjectMapper/ObjectMapper.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/ObjectMapper 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/ObjectMapper 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | SWIFT_VERSION = 3.0 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Quaggify/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Quaggify/Pods-Quaggify-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Quaggify : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Quaggify 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Quaggify/Pods-Quaggify-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 63 | 64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 65 | code_sign_cmd="$code_sign_cmd &" 66 | fi 67 | echo "$code_sign_cmd" 68 | eval "$code_sign_cmd" 69 | fi 70 | } 71 | 72 | # Strip invalid architectures 73 | strip_invalid_archs() { 74 | binary="$1" 75 | # Get architectures for current file 76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 77 | stripped="" 78 | for arch in $archs; do 79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 80 | # Strip non-valid architectures in-place 81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 82 | stripped="$stripped $arch" 83 | fi 84 | done 85 | if [[ "$stripped" ]]; then 86 | echo "Stripped $binary of architectures:$stripped" 87 | fi 88 | } 89 | 90 | 91 | if [[ "$CONFIGURATION" == "Debug" ]]; then 92 | install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework" 93 | install_framework "$BUILT_PRODUCTS_DIR/AlamofireObjectMapper/AlamofireObjectMapper.framework" 94 | install_framework "$BUILT_PRODUCTS_DIR/Kingfisher/Kingfisher.framework" 95 | install_framework "$BUILT_PRODUCTS_DIR/ObjectMapper/ObjectMapper.framework" 96 | install_framework "$BUILT_PRODUCTS_DIR/ReachabilitySwift/ReachabilitySwift.framework" 97 | fi 98 | if [[ "$CONFIGURATION" == "Release" ]]; then 99 | install_framework "$BUILT_PRODUCTS_DIR/Alamofire/Alamofire.framework" 100 | install_framework "$BUILT_PRODUCTS_DIR/AlamofireObjectMapper/AlamofireObjectMapper.framework" 101 | install_framework "$BUILT_PRODUCTS_DIR/Kingfisher/Kingfisher.framework" 102 | install_framework "$BUILT_PRODUCTS_DIR/ObjectMapper/ObjectMapper.framework" 103 | install_framework "$BUILT_PRODUCTS_DIR/ReachabilitySwift/ReachabilitySwift.framework" 104 | fi 105 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 106 | wait 107 | fi 108 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Quaggify/Pods-Quaggify-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_QuaggifyVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_QuaggifyVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Quaggify/Pods-Quaggify.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/AlamofireObjectMapper" "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" "$PODS_CONFIGURATION_BUILD_DIR/ObjectMapper" "$PODS_CONFIGURATION_BUILD_DIR/ReachabilitySwift" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/AlamofireObjectMapper/AlamofireObjectMapper.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ObjectMapper/ObjectMapper.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ReachabilitySwift/ReachabilitySwift.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "AlamofireObjectMapper" -framework "Kingfisher" -framework "ObjectMapper" -framework "ReachabilitySwift" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Quaggify/Pods-Quaggify.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_Quaggify { 2 | umbrella header "Pods-Quaggify-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Quaggify/Pods-Quaggify.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/AlamofireObjectMapper" "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" "$PODS_CONFIGURATION_BUILD_DIR/ObjectMapper" "$PODS_CONFIGURATION_BUILD_DIR/ReachabilitySwift" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/AlamofireObjectMapper/AlamofireObjectMapper.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ObjectMapper/ObjectMapper.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ReachabilitySwift/ReachabilitySwift.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "AlamofireObjectMapper" -framework "Kingfisher" -framework "ObjectMapper" -framework "ReachabilitySwift" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = $BUILD_DIR 9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-QuaggifyTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-QuaggifyTests/Pods-QuaggifyTests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-QuaggifyTests/Pods-QuaggifyTests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-QuaggifyTests/Pods-QuaggifyTests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_QuaggifyTests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_QuaggifyTests 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-QuaggifyTests/Pods-QuaggifyTests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" 63 | 64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 65 | code_sign_cmd="$code_sign_cmd &" 66 | fi 67 | echo "$code_sign_cmd" 68 | eval "$code_sign_cmd" 69 | fi 70 | } 71 | 72 | # Strip invalid architectures 73 | strip_invalid_archs() { 74 | binary="$1" 75 | # Get architectures for current file 76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 77 | stripped="" 78 | for arch in $archs; do 79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 80 | # Strip non-valid architectures in-place 81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 82 | stripped="$stripped $arch" 83 | fi 84 | done 85 | if [[ "$stripped" ]]; then 86 | echo "Stripped $binary of architectures:$stripped" 87 | fi 88 | } 89 | 90 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 91 | wait 92 | fi 93 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-QuaggifyTests/Pods-QuaggifyTests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_QuaggifyTestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_QuaggifyTestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-QuaggifyTests/Pods-QuaggifyTests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/AlamofireObjectMapper" "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" "$PODS_CONFIGURATION_BUILD_DIR/ObjectMapper" "$PODS_CONFIGURATION_BUILD_DIR/ReachabilitySwift" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/AlamofireObjectMapper/AlamofireObjectMapper.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ObjectMapper/ObjectMapper.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ReachabilitySwift/ReachabilitySwift.framework/Headers" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT}/Pods 8 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-QuaggifyTests/Pods-QuaggifyTests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_QuaggifyTests { 2 | umbrella header "Pods-QuaggifyTests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-QuaggifyTests/Pods-QuaggifyTests.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Alamofire" "$PODS_CONFIGURATION_BUILD_DIR/AlamofireObjectMapper" "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher" "$PODS_CONFIGURATION_BUILD_DIR/ObjectMapper" "$PODS_CONFIGURATION_BUILD_DIR/ReachabilitySwift" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Alamofire/Alamofire.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/AlamofireObjectMapper/AlamofireObjectMapper.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ObjectMapper/ObjectMapper.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/ReachabilitySwift/ReachabilitySwift.framework/Headers" 5 | PODS_BUILD_DIR = $BUILD_DIR 6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT}/Pods 8 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ReachabilitySwift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_ReachabilitySwift : NSObject 3 | @end 4 | @implementation PodsDummy_ReachabilitySwift 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double ReachabilitySwiftVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char ReachabilitySwiftVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift.modulemap: -------------------------------------------------------------------------------- 1 | framework module ReachabilitySwift { 2 | umbrella header "ReachabilitySwift-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/ReachabilitySwift 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 4 | OTHER_LDFLAGS = -framework "SystemConfiguration" 5 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 6 | PODS_BUILD_DIR = $BUILD_DIR 7 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/ReachabilitySwift 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | -------------------------------------------------------------------------------- /Quaggify.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Quaggify.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Quaggify/API.swift: -------------------------------------------------------------------------------- 1 | // 2 | // API.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | struct API { 10 | static func fetchCurrentUser (service: SpotifyService = SpotifyService.shared, completion: @escaping (User?, Error?) -> Void) { 11 | service.fetchCurrentUser(completion: completion) 12 | } 13 | 14 | static func requestToken (code: String, service: SpotifyService = SpotifyService.shared, completion: @escaping (Error?) -> Void) { 15 | service.requestToken(code: code, completion: completion) 16 | } 17 | 18 | static func fetchSearchResults (query: String, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifySearchResponse?, Error?) -> Void) { 19 | service.fetchSearchResults(query: query, completion: completion) 20 | } 21 | 22 | static func fetchAlbums (query: String, limit: Int = 20, offset: Int = 0, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifyObject?, Error?) -> Void) { 23 | service.fetchAlbums(query: query, limit: limit, offset: offset, completion: completion) 24 | } 25 | 26 | static func fetchAlbumTracks (album: Album?, limit: Int = 20, offset: Int = 0, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifyObject?, Error?) -> Void) { 27 | service.fetchAlbumTracks(album: album, limit: limit, offset: offset, completion: completion) 28 | } 29 | 30 | static func fetchArtist (artist: Artist?, service: SpotifyService = SpotifyService.shared, completion: @escaping (Artist?, Error?) -> Void) { 31 | service.fetchArtist(artist: artist, completion: completion) 32 | } 33 | 34 | static func fetchArtists (query: String, limit: Int = 20, offset: Int = 0, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifyObject?, Error?) -> Void) { 35 | service.fetchArtists(query: query, limit: limit, offset: offset, completion: completion) 36 | } 37 | 38 | static func fetchArtistAlbums (artist: Artist?, limit: Int = 20, offset: Int = 0, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifyObject?, Error?) -> Void) { 39 | service.fetchArtistAlbums(artist: artist, limit: limit, offset: offset, completion: completion) 40 | } 41 | 42 | static func fetchTrack (track: Track?, service: SpotifyService = SpotifyService.shared, completion: @escaping (Track?, Error?) -> Void) { 43 | service.fetchTrack(track: track, completion: completion) 44 | } 45 | 46 | static func fetchTracks (query: String, limit: Int = 20, offset: Int = 0, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifyObject?, Error?) -> Void) { 47 | service.fetchTracks(query: query, limit: limit, offset: offset, completion: completion) 48 | } 49 | 50 | static func fetchNewReleases (limit: Int = 20, offset: Int = 0, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifyObject?, Error?) -> Void) { 51 | service.fetchNewReleases(limit: limit, offset: offset, completion: completion) 52 | } 53 | 54 | static func fetchPlaylists (query: String, limit: Int = 20, offset: Int = 0, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifyObject?, Error?) -> Void) { 55 | service.fetchPlaylists(query: query, limit: limit, offset: offset, completion: completion) 56 | } 57 | 58 | static func fetchPlaylistTracks (playlist: Playlist?, limit: Int = 20, offset: Int = 0, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifyObject?, Error?) -> Void) { 59 | service.fetchPlaylistTracks(playlist: playlist, limit: limit, offset: offset, completion: completion) 60 | } 61 | 62 | static func removePlaylistTrack (track: Track?, position: Int?, playlist: Playlist?, service: SpotifyService = SpotifyService.shared, completion: @escaping(String?, Error?) -> Void) { 63 | service.removePlaylistTrack(track: track, position: position, playlist: playlist, completion: completion) 64 | } 65 | 66 | static func addTrackToPlaylist (track: Track?, playlist: Playlist?, service: SpotifyService = SpotifyService.shared, completion: @escaping (String?, Error?) -> Void) { 67 | service.addTrackToPlaylist(track: track, playlist: playlist, completion: completion) 68 | } 69 | 70 | static func fetchCurrentUsersPlaylists (limit: Int = 20, offset: Int = 0, service: SpotifyService = SpotifyService.shared, completion: @escaping (SpotifyObject?, Error?) -> Void) { 71 | service.fetchCurrentUsersPlaylists(limit: limit, offset: offset, completion: completion) 72 | } 73 | 74 | static func createNewPlaylist (name: String, service: SpotifyService = SpotifyService.shared, completion: @escaping (Playlist?, Error?) -> Void) { 75 | service.createNewPlaylist(name: name, completion: completion) 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Quaggify/Album.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Album.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ObjectMapper 11 | 12 | struct Album: Mappable { 13 | 14 | var albumType: String? 15 | var artists: [Artist]? 16 | var availableMarkets: [String]? 17 | var externalUrls: ExternalUrls? 18 | var href: String? 19 | var id: String? 20 | var images: [Image]? 21 | var name: String? 22 | var type: SpotifyObjectType? 23 | var uri: String? 24 | 25 | init?(map: Map) { 26 | 27 | } 28 | 29 | mutating func mapping(map: Map) { 30 | albumType <- map["album_type"] 31 | artists <- map["artists"] 32 | availableMarkets <- map["available_markets"] 33 | externalUrls <- map["external_urls"] 34 | href <- map["href"] 35 | id <- map["id"] 36 | images <- map["images"] 37 | name <- map["name"] 38 | type <- map["type"] 39 | uri <- map["uri"] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Quaggify/AlbumCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumCell.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Kingfisher 11 | 12 | class AlbumCell: CollectionViewCell { 13 | 14 | var album: Album? { 15 | didSet { 16 | guard let album = album else { 17 | return 18 | } 19 | if let smallerImage = album.images?[safe: 1], let imgUrlString = smallerImage.url, let url = URL(string: imgUrlString) { 20 | imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 21 | } else if let smallerImage = album.images?[safe: 0], let imgUrlString = smallerImage.url, let url = URL(string: imgUrlString) { 22 | imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 23 | } else { 24 | imageView.image = #imageLiteral(resourceName: "placeholder") 25 | } 26 | if let albumName = album.name { 27 | titleLabel.text = albumName 28 | } 29 | if let artists = album.artists { 30 | let names = artists.map { $0.name ?? "Uknown Artist" }.joined(separator: ", ") 31 | subTitleLabel.text = names 32 | } 33 | } 34 | } 35 | 36 | let imageView: UIImageView = { 37 | let iv = UIImageView() 38 | iv.contentMode = .scaleAspectFill 39 | iv.clipsToBounds = true 40 | return iv 41 | }() 42 | 43 | let stackView: UIStackView = { 44 | let sv = UIStackView() 45 | sv.axis = .vertical 46 | sv.alignment = .fill 47 | sv.distribution = .fillEqually 48 | sv.backgroundColor = .clear 49 | sv.spacing = 4 50 | return sv 51 | }() 52 | 53 | let titleLabel: UILabel = { 54 | let label = UILabel() 55 | label.textColor = ColorPalette.white 56 | label.font = Font.montSerratRegular(size: 16) 57 | label.textAlignment = .left 58 | label.translatesAutoresizingMaskIntoConstraints = false 59 | return label 60 | }() 61 | 62 | let subTitleLabel: UILabel = { 63 | let label = UILabel() 64 | label.textColor = ColorPalette.lightGray 65 | label.font = Font.montSerratRegular(size: 12) 66 | label.textAlignment = .left 67 | label.translatesAutoresizingMaskIntoConstraints = false 68 | return label 69 | }() 70 | 71 | override func setupViews() { 72 | super.setupViews() 73 | 74 | addSubview(imageView) 75 | addSubview(stackView) 76 | 77 | stackView.addArrangedSubview(titleLabel) 78 | stackView.addArrangedSubview(subTitleLabel) 79 | 80 | imageView.anchorCenterYToSuperview() 81 | imageView.anchor(nil, left: leftAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 8, bottomConstant: 0, rightConstant: 0, widthConstant: 64, heightConstant: 64) 82 | 83 | stackView.anchor(imageView.topAnchor, left: imageView.rightAnchor, bottom: imageView.bottomAnchor, right: rightAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 8, rightConstant: 8, widthConstant: 0, heightConstant: 0) 84 | } 85 | 86 | } 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Quaggify/AlbumHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumHeaderView.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 05/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AlbumHeaderView: UICollectionReusableView { 12 | var album: Album? { 13 | didSet { 14 | guard let album = album else { 15 | return 16 | } 17 | if let img = album.images?[safe: 0], let imgUrlString = img.url, let url = URL(string: imgUrlString) { 18 | albumImageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 19 | } else { 20 | albumImageView.image = #imageLiteral(resourceName: "placeholder") 21 | } 22 | if let albumName = album.name { 23 | albumNameLabel.text = albumName 24 | } 25 | if let artistName = album.artists?.first?.name { 26 | artistNameLabel.text = "Album by \(artistName)".uppercased() 27 | } 28 | } 29 | } 30 | 31 | let albumImageView: UIImageView = { 32 | let iv = UIImageView() 33 | iv.contentMode = .scaleAspectFill 34 | iv.clipsToBounds = true 35 | return iv 36 | }() 37 | 38 | let artistNameLabel: UILabel = { 39 | let label = UILabel() 40 | label.backgroundColor = .clear 41 | label.textColor = ColorPalette.white 42 | label.textAlignment = .left 43 | label.font = Font.montSerratRegular(size: 16) 44 | return label 45 | }() 46 | 47 | let albumNameLabel: UILabel = { 48 | let label = UILabel() 49 | label.backgroundColor = .clear 50 | label.textColor = ColorPalette.white 51 | label.textAlignment = .center 52 | label.font = Font.montSerratBold(size: 30) 53 | return label 54 | }() 55 | 56 | override init (frame: CGRect) { 57 | super.init(frame: frame) 58 | setupViews() 59 | } 60 | 61 | required init?(coder aDecoder: NSCoder) { 62 | fatalError("init(coder:) has not been implemented") 63 | } 64 | 65 | private let artistNameHeight: CGFloat = 30 66 | private let albumImgViewSize = CGSize(width: 150, height: 150) 67 | 68 | private func setupViews () { 69 | backgroundColor = .clear 70 | addSubview(albumImageView) 71 | addSubview(artistNameLabel) 72 | addSubview(albumNameLabel) 73 | 74 | artistNameLabel.anchor(topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 0, rightConstant: 8, widthConstant: 0, heightConstant: artistNameHeight) 75 | 76 | albumImageView.anchorCenterXToSuperview() 77 | albumImageView.anchor(artistNameLabel.bottomAnchor, left: nil, bottom: nil, right: nil, topConstant: 16, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: albumImgViewSize.width, heightConstant: albumImgViewSize.height) 78 | albumNameLabel.anchor(albumImageView.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 8, rightConstant: 8, widthConstant: 0, heightConstant: 0) 79 | } 80 | } 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Quaggify/Alert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alert.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 02/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class Alert: NSObject { 13 | static let shared = Alert() 14 | private override init () {} 15 | 16 | var isShown = false 17 | 18 | func show(title: String, message: String, completion: (() -> Void)? = nil) { 19 | if isShown == true { 20 | return 21 | } 22 | isShown = true 23 | 24 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 25 | alertController.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] _ in 26 | self?.isShown = false 27 | completion?() 28 | }) 29 | UIApplication.topViewController()?.present(alertController, animated: true, completion: nil) 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Quaggify/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | 18 | window = UIWindow(frame: UIScreen.main.bounds) 19 | 20 | window?.makeKeyAndVisible() 21 | if SpotifyService.shared.isLoggedIn { 22 | window?.rootViewController = TabBarController() 23 | } else { 24 | window?.rootViewController = LoginViewController() 25 | } 26 | 27 | return true 28 | } 29 | 30 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { 31 | if let code = url.queryItemValueFor(key: "code") { 32 | API.requestToken(code: code) { [weak self] (error) in 33 | if let error = error { 34 | print(error) 35 | Alert.shared.show(title: "Error", message: error.localizedDescription) 36 | } else { 37 | API.fetchCurrentUser { (user, err) in 38 | if let err = err { 39 | Alert.shared.show(title: "Error", message: err.localizedDescription) 40 | } else if let user = user { 41 | User.current = user 42 | User.current.saveToDefaults() 43 | 44 | let tabBarVC = TabBarController() 45 | tabBarVC.didLogin = true 46 | self?.window?.rootViewController = tabBarVC 47 | } 48 | } 49 | } 50 | } 51 | } else if let error = url.queryItemValueFor(key: "error") { 52 | print(error) 53 | Alert.shared.show(title: "Error", message: error) 54 | } 55 | return true 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /Quaggify/Array+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Extension.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array { 12 | subscript (safe index: Int) -> Element? { 13 | return index < count ? self[index] : nil 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Quaggify/Artist.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Artist.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct Artist: Mappable { 12 | 13 | var externalUrls: ExternalUrls? 14 | var followers: Followers? 15 | var genres: [String]? 16 | var href: String? 17 | var id: String? 18 | var images: [Image]? 19 | var name: String? 20 | var popularity: Int? 21 | var type: SpotifyObjectType? 22 | var uri: String? 23 | 24 | init?(map: Map) { 25 | 26 | } 27 | 28 | mutating func mapping(map: Map) { 29 | externalUrls <- map["external_urls"] 30 | followers <- map["followers"] 31 | genres <- map["genres"] 32 | href <- map["href"] 33 | id <- map["id"] 34 | images <- map["images"] 35 | name <- map["name"] 36 | popularity <- map["popularity"] 37 | type <- map["type"] 38 | uri <- map["uri"] 39 | } 40 | } 41 | 42 | extension Artist: Equatable { 43 | static func ==(lhs: Artist, rhs: Artist) -> Bool { 44 | return lhs.id == rhs.id 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Quaggify/ArtistCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistCell.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 01/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ArtistCell: CollectionViewCell { 12 | var artist: Artist? { 13 | didSet { 14 | guard let artist = artist else { 15 | return 16 | } 17 | if let smallerImage = artist.images?[safe: 2], let imgUrlString = smallerImage.url, let url = URL(string: imgUrlString) { 18 | imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 19 | } else if let smallerImage = artist.images?[safe: 1], let imgUrlString = smallerImage.url, let url = URL(string: imgUrlString) { 20 | imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 21 | } else if let smallerImage = artist.images?[safe: 0], let imgUrlString = smallerImage.url, let url = URL(string: imgUrlString) { 22 | imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 23 | } else { 24 | imageView.image = #imageLiteral(resourceName: "placeholder") 25 | } 26 | if let artistName = artist.name { 27 | titleLabel.text = artistName 28 | } 29 | if let totalFollowers = artist.followers?.total { 30 | subTitleLabel.text = "\(totalFollowers) Followers".uppercased() 31 | } 32 | } 33 | } 34 | 35 | let imageView: UIImageView = { 36 | let iv = UIImageView() 37 | iv.contentMode = .scaleAspectFill 38 | iv.clipsToBounds = true 39 | return iv 40 | }() 41 | 42 | let stackView: UIStackView = { 43 | let sv = UIStackView() 44 | sv.axis = .vertical 45 | sv.alignment = .fill 46 | sv.distribution = .fillEqually 47 | sv.backgroundColor = .clear 48 | sv.spacing = 4 49 | return sv 50 | }() 51 | 52 | let titleLabel: UILabel = { 53 | let label = UILabel() 54 | label.textColor = ColorPalette.white 55 | label.font = Font.montSerratBold(size: 16) 56 | label.textAlignment = .left 57 | label.translatesAutoresizingMaskIntoConstraints = false 58 | return label 59 | }() 60 | 61 | let subTitleLabel: UILabel = { 62 | let label = UILabel() 63 | label.textColor = ColorPalette.lightGray 64 | label.font = Font.montSerratRegular(size: 12) 65 | label.textAlignment = .left 66 | label.translatesAutoresizingMaskIntoConstraints = false 67 | return label 68 | }() 69 | 70 | private let imgViewSize = CGSize(width: 64, height: 64) 71 | 72 | override func setupViews() { 73 | super.setupViews() 74 | 75 | addSubview(imageView) 76 | imageView.layer.cornerRadius = imgViewSize.height / 2 77 | addSubview(stackView) 78 | 79 | stackView.addArrangedSubview(titleLabel) 80 | stackView.addArrangedSubview(subTitleLabel) 81 | 82 | imageView.anchorCenterYToSuperview() 83 | imageView.anchor(nil, left: leftAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 8, bottomConstant: 0, rightConstant: 0, widthConstant: imgViewSize.width, heightConstant: imgViewSize.height) 84 | 85 | stackView.anchor(imageView.topAnchor, left: imageView.rightAnchor, bottom: imageView.bottomAnchor, right: rightAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 8, rightConstant: 8, widthConstant: 0, heightConstant: 0) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Quaggify/ArtistHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArtistHeaderView.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 05/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ArtistHeaderView: UICollectionReusableView { 12 | var artist: Artist? { 13 | didSet { 14 | guard let artist = artist else { 15 | return 16 | } 17 | if let img = artist.images?[safe: 0], let imgUrlString = img.url, let url = URL(string: imgUrlString) { 18 | imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 19 | } else { 20 | imageView.image = #imageLiteral(resourceName: "placeholder") 21 | } 22 | if let artistName = artist.name { 23 | titleLabel.text = artistName 24 | } 25 | if let totalFollowers = artist.followers?.total { 26 | subTitleLabel.text = "\(totalFollowers) Followers".uppercased() 27 | } 28 | } 29 | } 30 | 31 | let imageView: UIImageView = { 32 | let iv = UIImageView() 33 | iv.contentMode = .scaleAspectFill 34 | iv.clipsToBounds = true 35 | return iv 36 | }() 37 | 38 | let titleLabel: UILabel = { 39 | let label = UILabel() 40 | label.backgroundColor = .clear 41 | label.textColor = ColorPalette.white 42 | label.textAlignment = .center 43 | label.font = Font.montSerratBold(size: 20) 44 | return label 45 | }() 46 | 47 | var subTitleLabel: UILabel = { 48 | let label = UILabel() 49 | label.font = Font.montSerratRegular(size: 14) 50 | label.textColor = ColorPalette.lightGray 51 | label.textAlignment = .center 52 | return label 53 | }() 54 | 55 | override init (frame: CGRect) { 56 | super.init(frame: frame) 57 | setupViews() 58 | } 59 | 60 | required init?(coder aDecoder: NSCoder) { 61 | fatalError("init(coder:) has not been implemented") 62 | } 63 | 64 | private let imgViewSize = CGSize(width: 150, height: 150) 65 | 66 | private func setupViews () { 67 | addSubview(imageView) 68 | addSubview(titleLabel) 69 | addSubview(subTitleLabel) 70 | 71 | imageView.layer.cornerRadius = imgViewSize.height / 2 72 | imageView.anchorCenterXToSuperview() 73 | imageView.anchor(topAnchor, left: nil, bottom: nil, right: nil, topConstant: 8, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: imgViewSize.width, heightConstant: imgViewSize.height) 74 | 75 | titleLabel.anchor(imageView.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 0, rightConstant: 8, widthConstant: 0, heightConstant: 24) 76 | 77 | subTitleLabel.anchor(titleLabel.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 8, rightConstant: 8, widthConstant: 0, heightConstant: 0) 78 | 79 | backgroundColor = .clear 80 | } 81 | } 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_add_playlist.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "music_add-512 copy-1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "music_add-512 copy.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "music_add-512 copy-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_add_playlist.imageset/music_add-512 copy-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/icon_add_playlist.imageset/music_add-512 copy-1.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_add_playlist.imageset/music_add-512 copy-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/icon_add_playlist.imageset/music_add-512 copy-2.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_add_playlist.imageset/music_add-512 copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/icon_add_playlist.imageset/music_add-512 copy.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_more.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_more.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon_more@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon_more@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_more.imageset/icon_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/icon_more.imageset/icon_more.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_more.imageset/icon_more@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/icon_more.imageset/icon_more@2x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_more.imageset/icon_more@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/icon_more.imageset/icon_more@3x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_remove.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_remove.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon_remove@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon_remove@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_remove.imageset/icon_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/icon_remove.imageset/icon_remove.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_remove.imageset/icon_remove@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/icon_remove.imageset/icon_remove@2x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/icon_remove.imageset/icon_remove@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/icon_remove.imageset/icon_remove@3x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "placeholder.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "placeholder-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "placeholder-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/placeholder.imageset/placeholder-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/placeholder.imageset/placeholder-1.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/placeholder.imageset/placeholder-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/placeholder.imageset/placeholder-2.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/placeholder.imageset/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/placeholder.imageset/placeholder.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_browse.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab_icon_browse.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab_icon_browse@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab_icon_browse@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_browse.imageset/tab_icon_browse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_browse.imageset/tab_icon_browse.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_browse.imageset/tab_icon_browse@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_browse.imageset/tab_icon_browse@2x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_browse.imageset/tab_icon_browse@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_browse.imageset/tab_icon_browse@3x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_browse_filled.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab_icon_browse_filled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab_icon_browse_filled@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab_icon_browse_filled@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_browse_filled.imageset/tab_icon_browse_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_browse_filled.imageset/tab_icon_browse_filled.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_browse_filled.imageset/tab_icon_browse_filled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_browse_filled.imageset/tab_icon_browse_filled@2x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_browse_filled.imageset/tab_icon_browse_filled@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_browse_filled.imageset/tab_icon_browse_filled@3x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab_icon_home.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab_icon_home@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab_icon_home_filled@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_home.imageset/tab_icon_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_home.imageset/tab_icon_home.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_home.imageset/tab_icon_home@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_home.imageset/tab_icon_home@2x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_home.imageset/tab_icon_home_filled@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_home.imageset/tab_icon_home_filled@3x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_home_filled.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab_icon_home_filled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab_icon_home_filled@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Home Filled-75.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_home_filled.imageset/Home Filled-75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_home_filled.imageset/Home Filled-75.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_home_filled.imageset/tab_icon_home_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_home_filled.imageset/tab_icon_home_filled.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_home_filled.imageset/tab_icon_home_filled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_home_filled.imageset/tab_icon_home_filled@2x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_library.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab_icon_library.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab_icon_library@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab_icon_library@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_library.imageset/tab_icon_library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_library.imageset/tab_icon_library.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_library.imageset/tab_icon_library@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_library.imageset/tab_icon_library@2x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_library.imageset/tab_icon_library@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_library.imageset/tab_icon_library@3x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_library_filled.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab_icon_library_filled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab_icon_library_filled@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab_icon_library_filled@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_library_filled.imageset/tab_icon_library_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_library_filled.imageset/tab_icon_library_filled.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_library_filled.imageset/tab_icon_library_filled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_library_filled.imageset/tab_icon_library_filled@2x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_library_filled.imageset/tab_icon_library_filled@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_library_filled.imageset/tab_icon_library_filled@3x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_search.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab_icon_search.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab_icon_search@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab_icon_search@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_search.imageset/tab_icon_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_search.imageset/tab_icon_search.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_search.imageset/tab_icon_search@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_search.imageset/tab_icon_search@2x.png -------------------------------------------------------------------------------- /Quaggify/Assets.xcassets/tab_icon_search.imageset/tab_icon_search@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Assets.xcassets/tab_icon_search.imageset/tab_icon_search@3x.png -------------------------------------------------------------------------------- /Quaggify/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Quaggify/CollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewCell.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CollectionViewCell: UICollectionViewCell { 12 | override var isHighlighted: Bool { 13 | didSet { 14 | alpha = isHighlighted ? 0.5 : 1 15 | } 16 | } 17 | 18 | override init(frame: CGRect) { 19 | super.init(frame: frame) 20 | setupViews() 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | super.init(coder: aDecoder) 25 | setupViews() 26 | } 27 | 28 | override func awakeFromNib() { 29 | super.awakeFromNib() 30 | setupViews() 31 | } 32 | 33 | func setupViews () { 34 | backgroundColor = .clear 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Quaggify/ColorPalette.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPalette.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct ColorPalette { 12 | static let green = UIColor(hexString: "#1DB954") 13 | static let white = UIColor(hexString: "#FFFFFF") 14 | static let black = UIColor(hexString: "#191414") 15 | static let gray = UIColor(hexString: "1a1a1a") 16 | static let lightGray = UIColor(hexString: "828385") 17 | } 18 | -------------------------------------------------------------------------------- /Quaggify/ExternalIds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExternalIds.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct ExternalIds: Mappable { 12 | var isrc: String? 13 | 14 | init?(map: Map) { 15 | 16 | } 17 | 18 | mutating func mapping(map: Map) { 19 | isrc <- map["isrc"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Quaggify/ExternalUrls.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExternalUrl.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct ExternalUrls: Mappable { 12 | var spotify: String? 13 | 14 | init?(map: Map) { 15 | 16 | } 17 | 18 | mutating func mapping(map: Map) { 19 | spotify <- map["spotify"] 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /Quaggify/Followers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Followers.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct Followers: Mappable { 12 | var href: String? 13 | var total: Int? 14 | 15 | init?(map: Map) { 16 | 17 | } 18 | 19 | mutating func mapping(map: Map) { 20 | href <- map["href"] 21 | total <- map["total"] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Quaggify/Font.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Font.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Font { 12 | fileprivate static let montSerratRegularName = "Montserrat Regular" 13 | fileprivate static let montSerratBoldName = "Montserrat Bold" 14 | 15 | static func montSerratRegular (size: CGFloat) -> UIFont? { 16 | return UIFont(name: Font.montSerratRegularName, size: size) ?? UIFont.systemFont(ofSize: size) 17 | } 18 | static func montSerratBold (size: CGFloat) -> UIFont? { 19 | return UIFont(name: Font.montSerratBoldName, size: size) ?? UIFont.systemFont(ofSize: size, weight: UIFont.Weight(rawValue: 0.4)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Quaggify/Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct Image: Mappable { 12 | 13 | var height: Int? 14 | var url: String? 15 | var width: Int? 16 | 17 | init?(map: Map) { 18 | 19 | } 20 | 21 | mutating func mapping(map: Map) { 22 | height <- map["height"] 23 | url <- map["url"] 24 | width <- map["width"] 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Quaggify/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleURLTypes 20 | 21 | 22 | CFBundleTypeRole 23 | Editor 24 | CFBundleURLName 25 | com.quaggie.quaggify 26 | CFBundleURLSchemes 27 | 28 | quaggify 29 | 30 | 31 | 32 | CFBundleVersion 33 | 1 34 | LSApplicationQueriesSchemes 35 | 36 | spotify 37 | 38 | LSRequiresIPhoneOS 39 | 40 | NSAppTransportSecurity 41 | 42 | NSAllowsArbitraryLoads 43 | 44 | 45 | UIAppFonts 46 | 47 | Montserrat-Regular.ttf 48 | Montserrat-Bold.ttf 49 | 50 | UILaunchStoryboardName 51 | LaunchScreen 52 | UIRequiredDeviceCapabilities 53 | 54 | armv7 55 | 56 | UISupportedInterfaceOrientations 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationLandscapeLeft 60 | UIInterfaceOrientationLandscapeRight 61 | 62 | UISupportedInterfaceOrientations~ipad 63 | 64 | UIInterfaceOrientationPortrait 65 | UIInterfaceOrientationPortraitUpsideDown 66 | UIInterfaceOrientationLandscapeLeft 67 | UIInterfaceOrientationLandscapeRight 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Quaggify/LoadingFooterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingFooterView.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 04/04/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LoadingFooterView: UICollectionReusableView { 12 | 13 | let activityIndicator: UIActivityIndicatorView = { 14 | let ai = UIActivityIndicatorView(activityIndicatorStyle: .white) 15 | ai.hidesWhenStopped = true 16 | ai.startAnimating() 17 | return ai 18 | }() 19 | 20 | var isLoading = true { 21 | didSet { 22 | if isLoading { 23 | activityIndicator.startAnimating() 24 | } else { 25 | activityIndicator.stopAnimating() 26 | } 27 | } 28 | } 29 | 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | setupViews() 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | } 39 | 40 | // MARK: Layout 41 | extension LoadingFooterView { 42 | func setupViews () { 43 | backgroundColor = .clear 44 | 45 | addSubview(activityIndicator) 46 | activityIndicator.anchorCenterSuperview() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Quaggify/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 02/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LoginViewController: ViewController { 12 | 13 | override var preferredStatusBarStyle: UIStatusBarStyle { 14 | return .lightContent 15 | } 16 | 17 | let titleLabel: UILabel = { 18 | let label = UILabel() 19 | label.text = "Quaggify" 20 | label.textColor = ColorPalette.white 21 | label.font = Font.montSerratRegular(size: 36) 22 | label.textAlignment = .center 23 | return label 24 | }() 25 | 26 | lazy var loginButton: UIButton = { 27 | let button = UIButton(type: .system) 28 | button.tintColor = .white 29 | button.setTitle("Login with Spotify", for: .normal) 30 | button.titleLabel?.font = Font.montSerratBold(size: 16) 31 | button.addTarget(self, action: #selector(login), for: .touchUpInside) 32 | return button 33 | }() 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | setupViews() 38 | } 39 | 40 | @objc func login () { 41 | SpotifyService.shared.login() 42 | } 43 | 44 | override func setupViews() { 45 | super.setupViews() 46 | view.backgroundColor = ColorPalette.black 47 | 48 | view.addSubview(titleLabel) 49 | view.addSubview(loginButton) 50 | 51 | titleLabel.anchorCenterYToSuperview() 52 | titleLabel.anchor(nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, topConstant: 0, leftConstant: 8, bottomConstant: 0, rightConstant: 8, widthConstant: 0, heightConstant: 40) 53 | 54 | loginButton.anchor(nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 32) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Quaggify/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /Quaggify/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/Quaggify/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /Quaggify/NavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationController.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NavigationController: UINavigationController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | navigationBar.barTintColor = ColorPalette.gray 15 | navigationBar.shadowImage = UIImage() 16 | navigationBar.barStyle = .blackTranslucent 17 | navigationBar.tintColor = .white 18 | 19 | if let titleFont = Font.montSerratRegular(size: 16) { 20 | navigationBar.titleTextAttributes = [NSAttributedStringKey.font: titleFont] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Quaggify/Notification+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notification+Extension.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 02/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Notification.Name { 12 | static let onRecentSearchesRemove = Notification.Name("onRecentSearchesRemove") 13 | static let onUserPlaylistUpdate = Notification.Name("onUserPlaylistUpdate") 14 | } 15 | -------------------------------------------------------------------------------- /Quaggify/Playlist.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Playlist.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct Playlist: Mappable { 12 | 13 | var isCollaborative: Bool? 14 | var externalUrls: ExternalUrls? 15 | var href: String? 16 | var id: String? 17 | var images: [Image]? 18 | var name: String? 19 | var owner: User? 20 | var isPublic: Bool? 21 | var snapshotId: String? 22 | var tracks: PlaylistTracks? 23 | var type: SpotifyObjectType? 24 | var uri: String? 25 | 26 | init?(map: Map) { 27 | 28 | } 29 | 30 | mutating func mapping(map: Map) { 31 | isCollaborative <- map["collaborative"] 32 | externalUrls <- map["external_urls"] 33 | href <- map["href"] 34 | id <- map["id"] 35 | images <- map["images"] 36 | name <- map["name"] 37 | owner <- map["owner"] 38 | isPublic <- map["public"] 39 | snapshotId <- map["snapshot_id"] 40 | tracks <- map["tracks"] 41 | type <- map["type"] 42 | uri <- map["uri"] 43 | } 44 | } 45 | 46 | extension Playlist: Equatable { 47 | static func ==(lhs: Playlist, rhs: Playlist) -> Bool { 48 | return lhs.id == rhs.id 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Quaggify/PlaylistCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaylistCell.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 01/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PlaylistCell: CollectionViewCell { 12 | 13 | var omitName = false 14 | 15 | var playlist: Playlist? { 16 | didSet { 17 | guard let playlist = playlist else { 18 | return 19 | } 20 | if let smallerImage = playlist.images?[safe: 1], let imgUrlString = smallerImage.url, let url = URL(string: imgUrlString) { 21 | imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 22 | } else if let smallerImage = playlist.images?[safe: 0], let imgUrlString = smallerImage.url, let url = URL(string: imgUrlString) { 23 | imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 24 | } else { 25 | imageView.image = #imageLiteral(resourceName: "placeholder") 26 | } 27 | if let playlistName = playlist.name { 28 | titleLabel.text = playlistName 29 | } else { 30 | titleLabel.text = "Unkown Playlist" 31 | } 32 | if let id = playlist.owner?.id, let totalTracks = playlist.tracks?.total { 33 | subTitleLabel.isHidden = false 34 | if omitName { 35 | subTitleLabel.text = "\(totalTracks) \(totalTracks == 1 ? "song" : "songs")".uppercased() 36 | } else { 37 | subTitleLabel.text = "\(id) ・ \(totalTracks) \(totalTracks == 1 ? "song" : "songs")".uppercased() 38 | } 39 | } else { 40 | subTitleLabel.isHidden = true 41 | } 42 | } 43 | } 44 | 45 | let imageView: UIImageView = { 46 | let iv = UIImageView() 47 | iv.contentMode = .scaleAspectFill 48 | iv.clipsToBounds = true 49 | return iv 50 | }() 51 | 52 | let stackView: UIStackView = { 53 | let sv = UIStackView() 54 | sv.axis = .vertical 55 | sv.alignment = .fill 56 | sv.distribution = .fillEqually 57 | sv.backgroundColor = .clear 58 | sv.spacing = 4 59 | return sv 60 | }() 61 | 62 | let titleLabel: UILabel = { 63 | let label = UILabel() 64 | label.textColor = ColorPalette.white 65 | label.font = Font.montSerratBold(size: 16) 66 | label.textAlignment = .left 67 | label.translatesAutoresizingMaskIntoConstraints = false 68 | return label 69 | }() 70 | 71 | let subTitleLabel: UILabel = { 72 | let label = UILabel() 73 | label.textColor = ColorPalette.lightGray 74 | label.font = Font.montSerratRegular(size: 12) 75 | label.textAlignment = .left 76 | label.translatesAutoresizingMaskIntoConstraints = false 77 | return label 78 | }() 79 | 80 | override func setupViews() { 81 | super.setupViews() 82 | 83 | addSubview(imageView) 84 | addSubview(stackView) 85 | 86 | stackView.addArrangedSubview(titleLabel) 87 | stackView.addArrangedSubview(subTitleLabel) 88 | 89 | imageView.anchorCenterYToSuperview() 90 | imageView.anchor(nil, left: leftAnchor, bottom: nil, right: nil, topConstant: 0, leftConstant: 8, bottomConstant: 0, rightConstant: 0, widthConstant: 64, heightConstant: 64) 91 | 92 | stackView.anchor(imageView.topAnchor, left: imageView.rightAnchor, bottom: imageView.bottomAnchor, right: rightAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 8, rightConstant: 8, widthConstant: 0, heightConstant: 0) 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Quaggify/PlaylistHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaylistHeaderView.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 06/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PlaylistHeaderView: UICollectionReusableView { 12 | var playlist: Playlist? { 13 | didSet { 14 | guard let playlist = playlist else { 15 | return 16 | } 17 | if let ownerName = playlist.owner?.id { 18 | ownerNameLabel.text = "Playlist By \(ownerName)".uppercased() 19 | } else { 20 | ownerNameLabel.text = "Playlist By" 21 | } 22 | if let img = playlist.images?[safe: 0], let imgUrlString = img.url, let url = URL(string: imgUrlString) { 23 | playlistImageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "placeholder"), options: [.transition(.fade(0.2))]) 24 | } else { 25 | playlistImageView.image = #imageLiteral(resourceName: "placeholder") 26 | } 27 | if let playlistName = playlist.name { 28 | playlistNameLabel.text = playlistName 29 | } 30 | } 31 | } 32 | 33 | let playlistImageView: UIImageView = { 34 | let iv = UIImageView() 35 | iv.contentMode = .scaleAspectFill 36 | iv.clipsToBounds = true 37 | return iv 38 | }() 39 | 40 | let ownerNameLabel: UILabel = { 41 | let label = UILabel() 42 | label.backgroundColor = .clear 43 | label.textColor = ColorPalette.white 44 | label.textAlignment = .left 45 | label.font = Font.montSerratRegular(size: 16) 46 | return label 47 | }() 48 | 49 | let playlistNameLabel: UILabel = { 50 | let label = UILabel() 51 | label.backgroundColor = .clear 52 | label.textColor = ColorPalette.white 53 | label.textAlignment = .center 54 | label.numberOfLines = 2 55 | label.lineBreakMode = .byWordWrapping 56 | label.font = Font.montSerratBold(size:26) 57 | return label 58 | }() 59 | 60 | override init (frame: CGRect) { 61 | super.init(frame: frame) 62 | setupViews() 63 | } 64 | 65 | required init?(coder aDecoder: NSCoder) { 66 | fatalError("init(coder:) has not been implemented") 67 | } 68 | 69 | private let ownerNameHeight: CGFloat = 50 70 | private let playlistImgViewSize = CGSize(width: 150, height: 150) 71 | 72 | private func setupViews () { 73 | backgroundColor = .clear 74 | addSubview(ownerNameLabel) 75 | addSubview(playlistImageView) 76 | addSubview(playlistNameLabel) 77 | 78 | ownerNameLabel.anchor(topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 0, rightConstant: 8, widthConstant: 0, heightConstant: ownerNameHeight) 79 | 80 | playlistImageView.anchorCenterXToSuperview() 81 | playlistImageView.anchor(ownerNameLabel.bottomAnchor, left: nil, bottom: nil, right: nil, topConstant: 16, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: playlistImgViewSize.width, heightConstant: playlistImgViewSize.height) 82 | playlistNameLabel.anchor(playlistImageView.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 8, rightConstant: 8, widthConstant: 0, heightConstant: 0) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Quaggify/PlaylistTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaylistTracksResponse.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 06/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ObjectMapper 11 | 12 | struct PlaylistTrack: Mappable { 13 | var track: Track? 14 | 15 | init?(map: Map) { 16 | 17 | } 18 | 19 | mutating func mapping(map: Map) { 20 | track <- map["track"] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Quaggify/PlaylistTracks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaylistTracks.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct PlaylistTracks: Mappable { 12 | var href: String? 13 | var total: Int? 14 | 15 | init?(map: Map) { 16 | 17 | } 18 | 19 | mutating func mapping(map: Map) { 20 | href <- map["href"] 21 | total <- map["total"] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Quaggify/Quaggify.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Quaggify/Quaggify.xcdatamodeld/Quaggify.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Quaggify/RecentSearches.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecentSearches.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 01/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct RecentSearches { 12 | 13 | static let shared = RecentSearches() 14 | private init () {} 15 | 16 | private var defaults = UserDefaults.standard 17 | 18 | private let SEARCHES_KEY = "RECENT_SEARCHES" 19 | 20 | var items: [String] { 21 | return defaults.stringArray(forKey: SEARCHES_KEY) ?? [] 22 | } 23 | 24 | func add (search: String) { 25 | let trimmedSearch = search.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() 26 | var searches = defaults.stringArray(forKey: SEARCHES_KEY) 27 | // Veryfing so it won't repeat on the history 28 | if let s = searches, !s.contains(trimmedSearch) { 29 | searches?.insert(trimmedSearch, at: 0) 30 | defaults.setValue([trimmedSearch], forKey: SEARCHES_KEY) 31 | } 32 | defaults.setValue(searches ?? [trimmedSearch], forKey: SEARCHES_KEY) 33 | 34 | sync() 35 | } 36 | 37 | func remove (search: String) { 38 | var searches = defaults.stringArray(forKey: SEARCHES_KEY) 39 | searches?.enumerated().forEach { (index, item) in 40 | if item == search, searches?[safe: index] != nil { 41 | searches?.remove(at: index) 42 | } 43 | } 44 | defaults.setValue(searches, forKey: SEARCHES_KEY) 45 | 46 | sync() 47 | notifyOnRemove() 48 | } 49 | 50 | func removeAll () { 51 | var searches = defaults.stringArray(forKey: SEARCHES_KEY) 52 | searches?.removeAll() 53 | 54 | defaults.setValue(searches, forKey: SEARCHES_KEY) 55 | 56 | sync() 57 | notifyOnRemove() 58 | } 59 | 60 | func sync () { 61 | defaults.synchronize() 62 | } 63 | 64 | private func notifyOnRemove () { 65 | NotificationCenter.default.post(name: .onRecentSearchesRemove, object: nil) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Quaggify/RecentSearchesCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecentSearchesCell.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 01/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RecentSearchesCell: CollectionViewCell { 12 | 13 | var title: String? { 14 | didSet { 15 | guard let title = title else { 16 | return 17 | } 18 | titleLabel.text = title 19 | } 20 | } 21 | 22 | var titleLabel: UILabel = { 23 | let label = UILabel() 24 | label.textColor = ColorPalette.white 25 | label.font = Font.montSerratBold(size: 14) 26 | label.textAlignment = .left 27 | return label 28 | }() 29 | 30 | lazy var removeImageView: UIImageView = { 31 | let iv = UIImageView() 32 | iv.contentMode = .scaleAspectFit 33 | iv.clipsToBounds = true 34 | iv.image = #imageLiteral(resourceName: "icon_remove").withRenderingMode(.alwaysTemplate) 35 | iv.tintColor = ColorPalette.lightGray 36 | return iv 37 | }() 38 | 39 | lazy var removeButton: UIButton = { 40 | let button = UIButton(type: .system) 41 | button.backgroundColor = .clear 42 | button.setImage(#imageLiteral(resourceName: "icon_remove").withRenderingMode(.alwaysTemplate), for: .normal) 43 | button.imageView?.contentMode = .scaleAspectFit 44 | button.tintColor = ColorPalette.white 45 | button.addTarget(self, action: #selector(removeRecentSearch), for: .touchUpInside) 46 | return button 47 | }() 48 | 49 | override func setupViews() { 50 | addSubview(titleLabel) 51 | addSubview(removeButton) 52 | 53 | titleLabel.anchor(topAnchor, left: leftAnchor, bottom: bottomAnchor, right: removeButton.leftAnchor, topConstant: 8, leftConstant: 8, bottomConstant: 8, rightConstant: 0, widthConstant: 0, heightConstant: 0) 54 | removeButton.anchor(topAnchor, left: nil, bottom: bottomAnchor, right: rightAnchor, topConstant: 0, leftConstant: 8, bottomConstant: 0, rightConstant: 0, widthConstant: 30, heightConstant: 0) 55 | } 56 | 57 | @objc func removeRecentSearch () { 58 | if let title = title { 59 | RecentSearches.shared.remove(search: title) 60 | } 61 | } 62 | 63 | func removeAllRecentSearches () { 64 | RecentSearches.shared.removeAll() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Quaggify/RefreshTokenResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshTokenResponse.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 02/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct RefreshTokenResponse: Mappable { 12 | 13 | var accessToken: String? 14 | var expiresIn: Int? 15 | var refreshToken: String? 16 | var tokenType: String? 17 | 18 | init?(map: Map) { 19 | 20 | } 21 | 22 | mutating func mapping(map: Map) { 23 | accessToken <- map["access_token"] 24 | expiresIn <- map["expires_in"] 25 | refreshToken <- map["refresh_token"] 26 | tokenType <- map["token_type"] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Quaggify/ScrollDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollDelegate.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 06/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ScrollDelegate: class { 12 | func scrollToTop() 13 | } 14 | -------------------------------------------------------------------------------- /Quaggify/SearchHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchHeaderView.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SearchHeaderView: UICollectionReusableView { 12 | var title = "" { 13 | didSet { 14 | titleLabel.text = title 15 | } 16 | } 17 | 18 | let titleLabel: UILabel = { 19 | let label = UILabel() 20 | label.backgroundColor = .clear 21 | label.textColor = ColorPalette.white 22 | label.textAlignment = .center 23 | label.font = Font.montSerratBold(size: 18) 24 | return label 25 | }() 26 | 27 | override init (frame: CGRect) { 28 | super.init(frame: frame) 29 | setupViews() 30 | } 31 | 32 | required init?(coder aDecoder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | private func setupViews () { 37 | addSubview(titleLabel) 38 | titleLabel.fillSuperview() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Quaggify/SeeAllCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SeeAllCell.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 02/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SeeAllCell: CollectionViewCell { 12 | 13 | var title: String? { 14 | didSet { 15 | if let title = title { 16 | titleLabel.text = title 17 | } 18 | } 19 | } 20 | 21 | private let titleLabel: UILabel = { 22 | let label = UILabel() 23 | label.backgroundColor = .clear 24 | label.textColor = ColorPalette.white 25 | label.textAlignment = .left 26 | label.font = Font.montSerratRegular(size: 16) 27 | return label 28 | }() 29 | 30 | override func setupViews() { 31 | super.setupViews() 32 | 33 | addSubview(titleLabel) 34 | titleLabel.anchor(topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, topConstant: 0, leftConstant: 8, bottomConstant: 0, rightConstant: 8, widthConstant: 0, heightConstant: 0) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Quaggify/SpotifyFooter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifyFooter.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 02/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ObjectMapper 11 | 12 | struct SpotifyFooter { 13 | var type: T? 14 | var title: String? 15 | } 16 | -------------------------------------------------------------------------------- /Quaggify/SpotifyObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifyObject.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct SpotifyObject: Mappable { 12 | var href: String? 13 | var limit: Int? 14 | var next: String? 15 | var offset: Int? 16 | var previous: String? 17 | var total: Int? 18 | var items: [T]? 19 | 20 | init?(map: Map) { 21 | 22 | } 23 | 24 | mutating func mapping(map: Map) { 25 | href <- map["href"] 26 | limit <- map["limit"] 27 | next <- map["next"] 28 | offset <- map["offset"] 29 | previous <- map["previous"] 30 | total <- map["total"] 31 | items <- map["items"] 32 | } 33 | } 34 | 35 | extension SpotifyObject: Equatable { 36 | static func ==(lhs: SpotifyObject, rhs: SpotifyObject) -> Bool { 37 | return lhs.href == rhs.href && 38 | lhs.limit == rhs.limit && 39 | lhs.next == rhs.next && 40 | lhs.offset == rhs.offset && 41 | lhs.previous == rhs.previous && 42 | lhs.total == rhs.total && 43 | lhs.items?.count == rhs.items?.count 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Quaggify/SpotifyObjectType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifyObjectType.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 01/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum SpotifyObjectType: String { 12 | case album 13 | case artist 14 | case track 15 | case playlist 16 | case user 17 | } 18 | -------------------------------------------------------------------------------- /Quaggify/SpotifySearchResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifySearchResponse.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct SpotifySearchResponse: Mappable { 12 | var albums: SpotifyObject? 13 | var artists: SpotifyObject? 14 | var tracks: SpotifyObject? 15 | var playlists: SpotifyObject? 16 | 17 | var numberOfSections: Int { 18 | var total = 0 19 | 20 | if let items = albums?.items, items.count > 0 { 21 | total += 1 22 | if albums?.next != nil { 23 | total += 1 24 | } 25 | } 26 | if let items = artists?.items, items.count > 0 { 27 | total += 1 28 | if artists?.next != nil { 29 | total += 1 30 | } 31 | } 32 | if let items = tracks?.items, items.count > 0 { 33 | total += 1 34 | if tracks?.next != nil { 35 | total += 1 36 | } 37 | } 38 | if let items = playlists?.items, items.count > 0 { 39 | total += 1 40 | if playlists?.next != nil { 41 | total += 1 42 | } 43 | } 44 | 45 | return total 46 | } 47 | 48 | init?(map: Map) { 49 | 50 | } 51 | 52 | mutating func mapping(map: Map) { 53 | albums <- map["albums"] 54 | artists <- map["artists"] 55 | tracks <- map["tracks"] 56 | playlists <- map["playlists"] 57 | } 58 | } 59 | 60 | extension SpotifySearchResponse: Equatable { 61 | static func ==(lhs: SpotifySearchResponse, rhs: SpotifySearchResponse) -> Bool { 62 | return lhs.albums == rhs.albums && 63 | lhs.artists == rhs.artists && 64 | lhs.playlists == rhs.playlists && 65 | lhs.tracks == rhs.tracks 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Quaggify/String+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extension.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 03/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | extension String: Error {} 13 | 14 | extension String: LocalizedError { 15 | public var errorDescription: String? { return self } 16 | } 17 | -------------------------------------------------------------------------------- /Quaggify/TabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarController.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TabBarController: UITabBarController { 12 | 13 | var previousViewController: UIViewController? 14 | 15 | var didLogin = false 16 | 17 | // MARK: Lifecycle 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | delegate = self 22 | 23 | UITabBar.appearance().tintColor = ColorPalette.white 24 | UITabBar.appearance().isTranslucent = false 25 | UITabBar.appearance().barTintColor = ColorPalette.gray 26 | 27 | // Fetching updated user 28 | if !didLogin { 29 | if let user = User.getFromDefaults() { 30 | User.current = user 31 | } 32 | API.fetchCurrentUser { (user, error) in 33 | if let user = user { 34 | User.current = user 35 | User.current.saveToDefaults() 36 | } 37 | } 38 | } 39 | } 40 | 41 | override func viewWillAppear(_ animated: Bool) { 42 | super.viewWillAppear(animated) 43 | 44 | let homeViewController = NavigationController(rootViewController: HomeViewController()) 45 | let homeIcon = #imageLiteral(resourceName: "tab_icon_home").withRenderingMode(.alwaysTemplate) 46 | let homeIconFilled = #imageLiteral(resourceName: "tab_icon_home_filled").withRenderingMode(.alwaysTemplate) 47 | homeViewController.tabBarItem = UITabBarItem(title: "Home", image: homeIcon, selectedImage: homeIconFilled) 48 | homeViewController.tabBarItem.tag = 0 49 | 50 | if previousViewController == nil { 51 | previousViewController = homeViewController 52 | } 53 | 54 | let searchViewController = NavigationController(rootViewController: SearchViewController()) 55 | let searchIcon = #imageLiteral(resourceName: "tab_icon_search").withRenderingMode(.alwaysTemplate) 56 | searchViewController.tabBarItem = UITabBarItem(title: "Search", image: searchIcon, tag: 1) 57 | 58 | let libraryViewController = NavigationController(rootViewController: LibraryViewController()) 59 | let libraryIcon = #imageLiteral(resourceName: "tab_icon_library").withRenderingMode(.alwaysTemplate) 60 | let libraryIconFilled = #imageLiteral(resourceName: "tab_icon_library_filled").withRenderingMode(.alwaysTemplate) 61 | libraryViewController.tabBarItem = UITabBarItem(title: "Your Library", image: libraryIcon, selectedImage: libraryIconFilled) 62 | libraryViewController.tabBarItem.tag = 2 63 | 64 | viewControllers = [homeViewController, searchViewController, libraryViewController] 65 | } 66 | 67 | } 68 | 69 | extension TabBarController: UITabBarControllerDelegate { 70 | func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { 71 | if previousViewController == viewController { 72 | if let navController = viewController as? NavigationController { 73 | if let homeVC = navController.topViewController as? HomeViewController { 74 | homeVC.scrollToTop() 75 | } 76 | if let searchVC = navController.topViewController as? SearchViewController { 77 | searchVC.scrollToTop() 78 | } 79 | if let libraryVC = navController.topViewController as? LibraryViewController { 80 | libraryVC.scrollToTop() 81 | } 82 | } 83 | } 84 | previousViewController = viewController 85 | } 86 | } 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Quaggify/Track.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Track.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct Track: Mappable { 12 | 13 | var album: Album? 14 | var artists: [Artist]? 15 | var availableMarkets: [String]? 16 | var discNumber: Int? 17 | var durationMS: Int? 18 | var isExplicit: Bool? 19 | var externalIds: ExternalIds? 20 | var externalUrls: ExternalUrls? 21 | var href: String? 22 | var id: String? 23 | var name: String? 24 | var popularity: Int? 25 | var previewUrl: String? 26 | var trackNumber: Int? 27 | var type: SpotifyObjectType? 28 | var uri: String? 29 | 30 | init?(map: Map) { 31 | 32 | } 33 | 34 | mutating func mapping(map: Map) { 35 | album <- map["album"] 36 | artists <- map["artists"] 37 | availableMarkets <- map["available_markets"] 38 | discNumber <- map["disc_number"] 39 | durationMS <- map["duration_ms"] 40 | isExplicit <- map["explicit"] 41 | externalIds <- map["external_ids"] 42 | externalUrls <- map["external_urls"] 43 | href <- map["href"] 44 | id <- map["id"] 45 | name <- map["name"] 46 | popularity <- map["popularity"] 47 | previewUrl <- map["preview_url"] 48 | trackNumber <- map["track_number"] 49 | type <- map["type"] 50 | uri <- map["uri"] 51 | } 52 | } 53 | 54 | 55 | extension Track: Equatable { 56 | static func ==(lhs: Track, rhs: Track) -> Bool { 57 | return lhs.id == rhs.id 58 | } 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /Quaggify/UIApplication+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+Extension.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 01/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIApplication { 12 | class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? { 13 | if let nav = base as? UINavigationController { 14 | return topViewController(base: nav.visibleViewController) 15 | } 16 | if let tab = base as? UITabBarController { 17 | if let selected = tab.selectedViewController { 18 | return topViewController(base: selected) 19 | } 20 | } 21 | if let presented = base?.presentedViewController { 22 | return topViewController(base: presented) 23 | } 24 | return base 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Quaggify/UICollectionReusableView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionReusableView+Extension.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 04/04/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UICollectionReusableView { 12 | static var identifier: String { 13 | return String(describing: self) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Quaggify/UIColor+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Extension.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | convenience init(hexString: String) { 13 | var cString: String = hexString.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines).uppercased() 14 | 15 | if (cString.hasPrefix("#")) { 16 | cString = (cString as NSString).substring(from: 1) 17 | } 18 | 19 | if (cString.count != 6) { 20 | self.init(white: 0.5, alpha: 1.0) 21 | } else { 22 | let rString: String = (cString as NSString).substring(to: 2) 23 | let gString = ((cString as NSString).substring(from: 2) as NSString).substring(to: 2) 24 | let bString = ((cString as NSString).substring(from: 4) as NSString).substring(to: 2) 25 | 26 | var r: CUnsignedInt = 0, g: CUnsignedInt = 0, b: CUnsignedInt = 0; 27 | Scanner(string: rString).scanHexInt32(&r) 28 | Scanner(string: gString).scanHexInt32(&g) 29 | Scanner(string: bString).scanHexInt32(&b) 30 | 31 | self.init(red: CGFloat(r) / CGFloat(255.0), green: CGFloat(g) / CGFloat(255.0), blue: CGFloat(b) / CGFloat(255.0), alpha: CGFloat(1)) 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Quaggify/UINavigationBar+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationBar+Extension.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 03/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UINavigationBar { 12 | func makeTransparent() { 13 | setBackgroundImage(UIImage(), for: UIBarMetrics.default) 14 | shadowImage = UIImage() 15 | isTranslucent = true 16 | backgroundColor = UIColor.clear 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Quaggify/UIView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Extension.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | public func addConstraintsWithFormat(_ format: String, views: UIView...) { 13 | 14 | var viewsDictionary = [String: UIView]() 15 | for (index, view) in views.enumerated() { 16 | let key = "v\(index)" 17 | viewsDictionary[key] = view 18 | view.translatesAutoresizingMaskIntoConstraints = false 19 | } 20 | 21 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary)) 22 | } 23 | 24 | public func fillSuperview() { 25 | translatesAutoresizingMaskIntoConstraints = false 26 | if let superview = superview { 27 | leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true 28 | rightAnchor.constraint(equalTo: superview.rightAnchor).isActive = true 29 | topAnchor.constraint(equalTo: superview.topAnchor).isActive = true 30 | bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true 31 | } 32 | } 33 | 34 | public func anchor(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) { 35 | translatesAutoresizingMaskIntoConstraints = false 36 | 37 | _ = anchorWithReturnAnchors(top, left: left, bottom: bottom, right: right, topConstant: topConstant, leftConstant: leftConstant, bottomConstant: bottomConstant, rightConstant: rightConstant, widthConstant: widthConstant, heightConstant: heightConstant) 38 | } 39 | 40 | public func anchorWithReturnAnchors(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] { 41 | translatesAutoresizingMaskIntoConstraints = false 42 | 43 | var anchors = [NSLayoutConstraint]() 44 | 45 | if let top = top { 46 | anchors.append(topAnchor.constraint(equalTo: top, constant: topConstant)) 47 | } 48 | 49 | if let left = left { 50 | anchors.append(leftAnchor.constraint(equalTo: left, constant: leftConstant)) 51 | } 52 | 53 | if let bottom = bottom { 54 | anchors.append(bottomAnchor.constraint(equalTo: bottom, constant: -bottomConstant)) 55 | } 56 | 57 | if let right = right { 58 | anchors.append(rightAnchor.constraint(equalTo: right, constant: -rightConstant)) 59 | } 60 | 61 | if widthConstant > 0 { 62 | anchors.append(widthAnchor.constraint(equalToConstant: widthConstant)) 63 | } 64 | 65 | if heightConstant > 0 { 66 | anchors.append(heightAnchor.constraint(equalToConstant: heightConstant)) 67 | } 68 | 69 | anchors.forEach({$0.isActive = true}) 70 | 71 | return anchors 72 | } 73 | 74 | public func anchorCenterXToSuperview(constant: CGFloat = 0) { 75 | translatesAutoresizingMaskIntoConstraints = false 76 | if let anchor = superview?.centerXAnchor { 77 | centerXAnchor.constraint(equalTo: anchor, constant: constant).isActive = true 78 | } 79 | } 80 | 81 | public func anchorCenterYToSuperview(constant: CGFloat = 0) { 82 | translatesAutoresizingMaskIntoConstraints = false 83 | if let anchor = superview?.centerYAnchor { 84 | centerYAnchor.constraint(equalTo: anchor, constant: constant).isActive = true 85 | } 86 | } 87 | 88 | public func anchorCenterSuperview() { 89 | anchorCenterXToSuperview() 90 | anchorCenterYToSuperview() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Quaggify/URL+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Extension.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 02/02/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | func queryItemValueFor (key: String) -> String? { 13 | guard 14 | let components = URLComponents(url: self, resolvingAgainstBaseURL: false), 15 | let queryItems = components.queryItems 16 | else { 17 | return nil 18 | } 19 | 20 | return queryItems.first(where: { $0.name == key })?.value 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Quaggify/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import ObjectMapper 10 | 11 | struct User: Mappable { 12 | 13 | var country: String? 14 | var displayName: String? 15 | var email: String? 16 | var externalUrls: ExternalUrls? 17 | var followers: Followers? 18 | var href: String? 19 | var id: String? 20 | var images: [Image]? 21 | var product: String? 22 | var type: String? 23 | var uri: String? 24 | 25 | static var current = User() 26 | private init () {} 27 | 28 | func saveToDefaults () { 29 | let defaults = UserDefaults.standard 30 | 31 | defaults.set(id, forKey: "currentUserId") 32 | 33 | defaults.synchronize() 34 | } 35 | 36 | static func getFromDefaults () -> User? { 37 | if let id = UserDefaults.standard.object(forKey: "currentUserId") { 38 | return User(JSON: ["id": id]) 39 | } 40 | 41 | return nil 42 | } 43 | 44 | init?(map: Map) { 45 | 46 | } 47 | 48 | mutating func mapping(map: Map) { 49 | country <- map["country"] 50 | displayName <- map["display_name"] 51 | email <- map["email"] 52 | externalUrls <- map["external_urls"] 53 | followers <- map["followers"] 54 | href <- map["href"] 55 | id <- map["id"] 56 | images <- map["images"] 57 | product <- map["product"] 58 | type <- map["type"] 59 | uri <- map["uri"] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Quaggify/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Quaggify 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | setupViews() 16 | } 17 | 18 | func setupViews () { 19 | view.backgroundColor = ColorPalette.black 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /QuaggifyTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /QuaggifyTests/QuaggifyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuaggifyTests.swift 3 | // QuaggifyTests 4 | // 5 | // Created by Jonathan Bijos on 31/01/17. 6 | // Copyright © 2017 Quaggie. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Quaggify 11 | @testable import ObjectMapper 12 | 13 | class QuaggifyTests: XCTestCase { 14 | 15 | typealias JSON = [String: Any] 16 | 17 | class FakeSpotifyService: SpotifyService { 18 | override func fetchSearchResults(query: String, completion: @escaping (SpotifySearchResponse?, Error?) -> Void) { 19 | 20 | var albumJson: JSON = [ 21 | "href": "https://api.spotify.com/v1/search?query=\(query)&type=album&market=US&offset=0&limit=1", 22 | ] 23 | let items: JSON = [ 24 | "href": "https://api.spotify.com/v1/albums/7l0L2YHlQwAyI4QyZTIWGS", 25 | "id": "7l0L2YHlQwAyI4QyZTIWGS", 26 | ] 27 | albumJson["items"] = items 28 | 29 | let album = SpotifyObject(JSON: albumJson) 30 | 31 | if let album = album { 32 | let spotifySearchResponse = SpotifySearchResponse(JSON: ["albums": album]) 33 | completion(spotifySearchResponse, nil) 34 | } else { 35 | completion(nil, NSError(domain: "No search error", code: 111, userInfo: nil)) 36 | } 37 | } 38 | } 39 | 40 | func testSpotifySearch () { 41 | let promise = expectation(description: "Search for behemoth on spotify") 42 | let mockSearchResponse = SpotifySearchResponse(JSON: ["Teste": "123"]) 43 | 44 | API.fetchSearchResults(query: "behemoth", service: FakeSpotifyService.shared) { (spotifySearchResponse, error) in 45 | 46 | guard let mockSearchResponse = mockSearchResponse, 47 | let spotifySearchResponse = spotifySearchResponse else { 48 | XCTFail("Failed to unwrap response") 49 | promise.fulfill() 50 | return 51 | } 52 | XCTAssertNotEqual(mockSearchResponse, spotifySearchResponse) 53 | promise.fulfill() 54 | } 55 | waitForExpectations(timeout: 10) { error in 56 | if let error = error { 57 | XCTFail("waitForExpectations errored: \(error)") 58 | } 59 | } 60 | } 61 | 62 | } 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quaggify 2 | A Spotify clone made in Swift 3 and consuming the Spotify API. 3 | ![overview](./assets/overview1.png) 4 | 5 | ![overview2](./assets/overview2.png) 6 | 7 | ## What can the app do? 8 | 9 | - Login with your Spotify account 10 | - Home tab lists all new releases 11 | - Search for an album/artist/track/playlist 12 | - Create new playlist 13 | - Modify your current playlists 14 | - Add/Remove tracks from your playlists 15 | - Infinite scroll everything! 16 | 17 | ## Dependencies 18 | Cocoapods was the chosen dependency manager 19 | 20 | - [Alamofire](https://github.com/Alamofire/Alamofire) Networking 21 | - [ObjectMapper](https://github.com/Hearst-DD/ObjectMapper) JSON mapping 22 | - [AlamofireObjectMapper](https://github.com/tristanhimmelman/AlamofireObjectMapper) Simplifying the model layer 23 | - [ReachabilitySwift](https://github.com/ashleymills/Reachability.swift) Internet connection verification 24 | - [Kingfisher](https://github.com/onevcat/Kingfisher) Loading images from the server 25 | 26 | ## Building 27 | 28 | ### Clone the repository 29 | ``` 30 | $ git clone https://github.com/Quaggie/Quaggify.git 31 | ``` 32 | 33 | ### Build the dependencies 34 | ``` 35 | $ pod install 36 | ``` 37 | 38 | ### Setup your spotify CLIENT_ID and CLIENT_SECRET in SpotifyService.swift 39 | ``` swift 40 | ... 41 | class SpotifyService: NSObject { 42 | let CLIENT_ID = "{ INSERT CLIENT_ID HERE }" 43 | let CLIENT_SECRET = "{ INSERT CLIENT_SECRET HERE }" 44 | let REDIRECT_URI = "quaggify://authorization" 45 | ... 46 | ``` 47 | 48 | Now just run `Quaggify.xcworkspace` (: 49 | -------------------------------------------------------------------------------- /assets/overview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/assets/overview1.png -------------------------------------------------------------------------------- /assets/overview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quaggie/Quaggify/2e75ebec11727d273dd92e293643d170436cd9c0/assets/overview2.png --------------------------------------------------------------------------------