├── .gitignore ├── .DS_Store ├── Tinder ├── Assets.xcassets │ ├── Contents.json │ ├── 34.imageset │ │ ├── 34.jpeg │ │ └── Contents.json │ ├── app_icon.imageset │ │ ├── app_icon.png │ │ └── Contents.json │ ├── info_icon.imageset │ │ ├── info_icon.png │ │ └── Contents.json │ ├── itsamatch.imageset │ │ ├── itsamatch@2x.png │ │ └── Contents.json │ ├── like_circle.imageset │ │ ├── like_circle@2x.png │ │ └── Contents.json │ ├── boost_circle.imageset │ │ ├── boost_circle@2x.png │ │ └── Contents.json │ ├── dismiss_circle.imageset │ │ ├── dismiss_circle@2x.png │ │ └── Contents.json │ ├── refresh_circle.imageset │ │ ├── refresh_circle@2x.png │ │ └── Contents.json │ ├── photo_placeholder.imageset │ │ ├── photo_placeholder.jpg │ │ └── Contents.json │ ├── top_left_profile.imageset │ │ ├── top_left_profile@2x.png │ │ └── Contents.json │ ├── dismiss_down_arrow.imageset │ │ ├── dismiss_down_arrow.png │ │ └── Contents.json │ ├── super_like_circle.imageset │ │ ├── super_like_circle@2x.png │ │ └── Contents.json │ ├── top_right_messages.imageset │ │ ├── top_right_messages@2x.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Controllers │ ├── Bindable.swift │ ├── PhotoController.swift │ ├── LoginViewModel.swift │ ├── RegistrationViewModel.swift │ ├── SwipePhotosController.swift │ ├── UserDetailsController.swift │ ├── LoginController.swift │ ├── HomeController.swift │ ├── RegistrationController.swift │ └── SettingsController.swift ├── Model │ ├── Advertiser.swift │ └── User.swift ├── Extensions │ ├── Firebase+Utils.swift │ └── Extensions+UIView.swift ├── Views │ ├── SendMessageButton.swift │ ├── CustomTextField.swift │ ├── SettingsCell.swift │ ├── TopNavigationStackView.swift │ ├── KeepSwipingButton.swift │ ├── HomeBottomControlsStackView.swift │ ├── AgeRangeCell.swift │ ├── CardView.swift │ └── MatchView.swift ├── ViewModels │ └── CardViewModel.swift ├── GoogleService-Info.plist ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard └── AppDelegate.swift ├── Tinder.xcodeproj ├── xcuserdata │ ├── bekzod.xcuserdatad │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ └── adienchoi.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── Tinder.xcworkspace ├── xcuserdata │ ├── bekzod.xcuserdatad │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ └── adienchoi.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Podfile ├── README.md └── Podfile.lock /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/.DS_Store -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/34.imageset/34.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/34.imageset/34.jpeg -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/app_icon.imageset/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/app_icon.imageset/app_icon.png -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/info_icon.imageset/info_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/info_icon.imageset/info_icon.png -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/itsamatch.imageset/itsamatch@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/itsamatch.imageset/itsamatch@2x.png -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/like_circle.imageset/like_circle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/like_circle.imageset/like_circle@2x.png -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/boost_circle.imageset/boost_circle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/boost_circle.imageset/boost_circle@2x.png -------------------------------------------------------------------------------- /Tinder.xcodeproj/xcuserdata/bekzod.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Tinder.xcworkspace/xcuserdata/bekzod.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/dismiss_circle.imageset/dismiss_circle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/dismiss_circle.imageset/dismiss_circle@2x.png -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/refresh_circle.imageset/refresh_circle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/refresh_circle.imageset/refresh_circle@2x.png -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/photo_placeholder.imageset/photo_placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/photo_placeholder.imageset/photo_placeholder.jpg -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/top_left_profile.imageset/top_left_profile@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/top_left_profile.imageset/top_left_profile@2x.png -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/dismiss_down_arrow.imageset/dismiss_down_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/dismiss_down_arrow.imageset/dismiss_down_arrow.png -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/super_like_circle.imageset/super_like_circle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/super_like_circle.imageset/super_like_circle@2x.png -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/top_right_messages.imageset/top_right_messages@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder/Assets.xcassets/top_right_messages.imageset/top_right_messages@2x.png -------------------------------------------------------------------------------- /Tinder.xcworkspace/xcuserdata/adienchoi.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekzodrakhmatof/Tinder-Clone/HEAD/Tinder.xcworkspace/xcuserdata/adienchoi.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Tinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tinder.xcworkspace/xcuserdata/adienchoi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Tinder.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tinder.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tinder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/34.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "34.jpeg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/app_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "app_icon.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/info_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "info_icon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/itsamatch.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "itsamatch@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/boost_circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "boost_circle@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/like_circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "like_circle@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/dismiss_circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "dismiss_circle@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/photo_placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "photo_placeholder.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/refresh_circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "refresh_circle@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/dismiss_down_arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "dismiss_down_arrow.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/super_like_circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "super_like_circle@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/top_left_profile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "top_left_profile@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder/Assets.xcassets/top_right_messages.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "top_right_messages@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Tinder.xcodeproj/xcuserdata/bekzod.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Tinder.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Tinder.xcodeproj/xcuserdata/adienchoi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Tinder.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 23 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Tinder/Controllers/Bindable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bindable.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 27/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Bindable { 12 | 13 | var value: T? { 14 | didSet { 15 | 16 | observer?(value) 17 | } 18 | } 19 | 20 | var observer: ((T?) -> ())? 21 | 22 | func bind(observer: @escaping (T?) -> ()) { 23 | 24 | self.observer = observer 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | target 'Tinder' do 2 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 3 | use_frameworks! 4 | inhibit_all_warnings! 5 | 6 | # Pods for Tinder 7 | pod 'Firebase/Firestore' 8 | pod 'Firebase/Auth' 9 | pod 'Firebase/Storage' 10 | pod 'Firebase/Core' 11 | pod 'SDWebImage' 12 | pod 'JGProgressHUD' 13 | 14 | end 15 | 16 | post_install do |installer| 17 | installer.pods_project.targets.each do |target| 18 | target.build_configurations.each do |config| 19 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.1' 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tinder Clone 2 | 3 | This is a simple Tinder Clone app for iOS devices. 4 | It is written in Swift 4.2. 5 | 6 | Used: Open Source Code from CocoaPods 7 | 8 | Firebase SDK - Save user information and fetch. 9 | JGProgressHUD - To show progress view while working on Firebase upload and download processing. 10 | SDWebImage - Caching images when image is downloaded. 11 | 12 | If you want to try you can clone the project or simply download. 13 | Don't forget to update pod files or install. 14 | 15 | ![untitled design](https://user-images.githubusercontent.com/23249828/52316034-fa56ca00-29fc-11e9-81b9-3f1db4ecd662.png) 16 | -------------------------------------------------------------------------------- /Tinder/Model/Advertiser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Advertiser.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 26/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Advertiser: ProducesCardViewModel { 12 | 13 | let title: String 14 | let brandName: String 15 | let posterPhotoName: String 16 | 17 | func toCardViewModel() -> CardViewModel { 18 | 19 | let attributedSting = NSMutableAttributedString(string: title, attributes: [.font: UIFont.systemFont(ofSize: 34, weight: .heavy)]) 20 | 21 | attributedSting.append(NSAttributedString(string: "\n\(brandName)", attributes: [.font: UIFont.systemFont(ofSize: 24, weight: .bold)])) 22 | 23 | return CardViewModel(uid: "", imageNames: [posterPhotoName], attributedString: attributedSting, textAlignment: .center) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tinder/Extensions/Firebase+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Firebase+Utils.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 03/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import Firebase 10 | 11 | extension Firestore { 12 | 13 | func fetchCurrentUser(completion: @escaping (User?, Error?) -> ()) { 14 | 15 | guard let uid = Auth.auth().currentUser?.uid else { return } 16 | Firestore.firestore().collection("users").document(uid).getDocument { (snapshot, error) in 17 | 18 | if let error = error { 19 | completion(nil, error) 20 | return 21 | } 22 | 23 | // fetched our user here 24 | guard let dictionary = snapshot?.data() else { return } 25 | let user = User(dictionary: dictionary) 26 | completion(user, nil) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tinder/Controllers/PhotoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoController.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 05/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PhotoController: UIViewController { 12 | 13 | let imageView = UIImageView(image: #imageLiteral(resourceName: "photo_placeholder")) 14 | 15 | init(imageUrl: String) { 16 | 17 | if let url = URL(string: imageUrl) { 18 | imageView.sd_setImage(with: url) 19 | } 20 | super.init(nibName: nil, bundle: nil) 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | view.addSubview(imageView) 31 | imageView.fillSuperview() 32 | imageView.clipsToBounds = true 33 | imageView.contentMode = .scaleAspectFill 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tinder/Views/SendMessageButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SendMessageButton.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 06/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SendMessageButton: UIButton { 12 | 13 | override func draw(_ rect: CGRect) { 14 | super.draw(rect) 15 | 16 | let gradientLayer = CAGradientLayer() 17 | let leftColor = #colorLiteral(red: 0.9897946715, green: 0.1206176206, blue: 0.4514576197, alpha: 1) 18 | let rightColor = #colorLiteral(red: 0.9874548316, green: 0.3935527802, blue: 0.3240509331, alpha: 1) 19 | gradientLayer.colors = [leftColor.cgColor, rightColor.cgColor] 20 | gradientLayer.startPoint = CGPoint(x: 0, y: 0.5) 21 | gradientLayer.endPoint = CGPoint(x: 1, y: 0.5) 22 | 23 | self.layer.insertSublayer(gradientLayer, at: 0) 24 | 25 | layer.cornerRadius = rect.height / 2 26 | clipsToBounds = true 27 | 28 | gradientLayer.frame = rect 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tinder/Controllers/LoginViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewModel.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 03/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import Firebase 10 | 11 | class LoginViewModel { 12 | 13 | var isLogedIn = Bindable() 14 | var isFormValid = Bindable() 15 | 16 | var email: String? { 17 | didSet { 18 | checkFromValidity() 19 | } 20 | } 21 | 22 | var password: String? { 23 | didSet { 24 | checkFromValidity() 25 | } 26 | } 27 | 28 | fileprivate func checkFromValidity() { 29 | let isValid = email?.isEmpty == false && password?.isEmpty == false 30 | isFormValid.value = isValid 31 | } 32 | 33 | func performLogin(completion: @escaping (Error?) -> ()) { 34 | 35 | guard let email = email, let password = password else { return } 36 | isLogedIn.value = true 37 | Auth.auth().signIn(withEmail: email, password: password) { (result, error) in 38 | completion(error) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tinder/Views/CustomTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTextField.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 27/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomTextField: UITextField { 12 | 13 | let padding: CGFloat 14 | let height: CGFloat 15 | 16 | init(padding: CGFloat, height: CGFloat) { 17 | 18 | self.padding = padding 19 | self.height = height 20 | super.init(frame: .zero) 21 | 22 | backgroundColor = .white 23 | 24 | layer.cornerRadius = height / 2 25 | } 26 | 27 | override func textRect(forBounds bounds: CGRect) -> CGRect { 28 | 29 | return bounds.insetBy(dx: padding, dy: 0) 30 | } 31 | 32 | override func editingRect(forBounds bounds: CGRect) -> CGRect { 33 | 34 | return bounds.insetBy(dx: padding, dy: 0) 35 | } 36 | 37 | override var intrinsicContentSize: CGSize { 38 | 39 | return .init(width: 0, height: height) 40 | } 41 | 42 | required init?(coder aDecoder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tinder/Views/SettingsCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsCell.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 03/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SettingsCell: UITableViewCell { 12 | 13 | class SettingstextField: UITextField { 14 | 15 | override func textRect(forBounds bounds: CGRect) -> CGRect { 16 | return bounds.insetBy(dx: 24, dy: 0) 17 | } 18 | 19 | override func editingRect(forBounds bounds: CGRect) -> CGRect { 20 | return bounds.insetBy(dx: 24, dy: 0) 21 | } 22 | 23 | override var intrinsicContentSize: CGSize { 24 | return .init(width: 0, height: 44) 25 | } 26 | } 27 | 28 | let textField: SettingstextField = { 29 | let textField = SettingstextField() 30 | textField.placeholder = "Enter name" 31 | return textField 32 | }() 33 | 34 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 35 | super.init(style: style, reuseIdentifier: reuseIdentifier) 36 | 37 | addSubview(textField) 38 | textField.fillSuperview() 39 | } 40 | 41 | required init?(coder aDecoder: NSCoder) { 42 | fatalError() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tinder/ViewModels/CardViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardViewModel.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 26/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ProducesCardViewModel { 12 | 13 | func toCardViewModel() -> CardViewModel 14 | } 15 | 16 | 17 | class CardViewModel { 18 | 19 | let uid: String 20 | let imageUrls: [String] 21 | let attributedString: NSAttributedString 22 | let textAlignment: NSTextAlignment 23 | 24 | init(uid: String, imageNames: [String], attributedString: NSAttributedString, textAlignment: NSTextAlignment) { 25 | 26 | self.uid = uid 27 | self.imageUrls = imageNames 28 | self.attributedString = attributedString 29 | self.textAlignment = textAlignment 30 | } 31 | 32 | fileprivate var imageIndex = 0 { 33 | 34 | didSet { 35 | 36 | let imageUrl = imageUrls[imageIndex] 37 | imageIndexObserver?(imageIndex, imageUrl) 38 | } 39 | } 40 | 41 | // Reactive programming 42 | var imageIndexObserver: ((Int, String?) -> ())? 43 | 44 | func advaceToNextPhoto() { 45 | 46 | imageIndex = min(imageIndex + 1, imageUrls.count - 1) 47 | } 48 | 49 | func goToPreviousPhoto() { 50 | 51 | imageIndex = max(0, imageIndex - 1) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tinder/Views/TopNavigationStackView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopNavigationStackView.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 26/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TopNavigationStackView: UIStackView { 12 | 13 | let settingsButton = UIButton(type: .system) 14 | let messageButton = UIButton(type: .system) 15 | let fireImageView = UIImageView(image: #imageLiteral(resourceName: "app_icon")) 16 | 17 | override init(frame: CGRect) { 18 | super.init(frame: frame) 19 | 20 | distribution = .equalCentering 21 | heightAnchor.constraint(equalToConstant: 80).isActive = true 22 | 23 | fireImageView.contentMode = .scaleAspectFit 24 | settingsButton.setImage(#imageLiteral(resourceName: "top_left_profile").withRenderingMode(.alwaysOriginal), for: .normal) 25 | messageButton.setImage(#imageLiteral(resourceName: "top_right_messages").withRenderingMode(.alwaysOriginal), for: .normal) 26 | 27 | [settingsButton, UIView(), fireImageView, UIView(), messageButton].forEach { (view) in 28 | 29 | addArrangedSubview(view) 30 | } 31 | 32 | isLayoutMarginsRelativeArrangement = true 33 | layoutMargins = .init(top: 0, left: 16, bottom: 0, right: 16) 34 | } 35 | 36 | required init(coder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tinder/Views/KeepSwipingButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeepSwipingButton.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 06/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class KeepSwipingButton: UIButton { 12 | 13 | override func draw(_ rect: CGRect) { 14 | super.draw(rect) 15 | 16 | let gradientLayer = CAGradientLayer() 17 | let leftColor = #colorLiteral(red: 0.9897946715, green: 0.1206176206, blue: 0.4514576197, alpha: 1) 18 | let rightColor = #colorLiteral(red: 0.9874548316, green: 0.3935527802, blue: 0.3240509331, alpha: 1) 19 | gradientLayer.colors = [leftColor.cgColor, rightColor.cgColor] 20 | gradientLayer.startPoint = CGPoint(x: 0, y: 0.5) 21 | gradientLayer.endPoint = CGPoint(x: 1, y: 0.5) 22 | 23 | let cornerRadius = rect.height / 2 24 | let maskLayer = CAShapeLayer() 25 | let maskPath = CGMutablePath() 26 | 27 | maskPath.addPath(UIBezierPath(roundedRect: rect.insetBy(dx: 2, dy: 2), cornerRadius: cornerRadius).cgPath) 28 | maskPath.addPath(UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath) 29 | maskLayer.path = maskPath 30 | maskLayer.fillRule = .evenOdd 31 | gradientLayer.mask = maskLayer 32 | 33 | self.layer.insertSublayer(gradientLayer, at: 0) 34 | 35 | layer.cornerRadius = cornerRadius 36 | clipsToBounds = true 37 | 38 | gradientLayer.frame = rect 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tinder/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AD_UNIT_ID_FOR_BANNER_TEST 6 | ca-app-pub-3940256099942544/2934735716 7 | AD_UNIT_ID_FOR_INTERSTITIAL_TEST 8 | ca-app-pub-3940256099942544/4411468910 9 | CLIENT_ID 10 | 156700296828-t9nmkmcq03j7jf2f7pv62nl2kntakf8t.apps.googleusercontent.com 11 | REVERSED_CLIENT_ID 12 | com.googleusercontent.apps.156700296828-t9nmkmcq03j7jf2f7pv62nl2kntakf8t 13 | API_KEY 14 | AIzaSyBg9nAx0ScYgn6Dw7wyYc8SbGRf1PFz2s4 15 | GCM_SENDER_ID 16 | 156700296828 17 | PLIST_VERSION 18 | 1 19 | BUNDLE_ID 20 | com.bekzodrakhmatov.Tinder 21 | PROJECT_ID 22 | tinder-clone-28618 23 | STORAGE_BUCKET 24 | tinder-clone-28618.appspot.com 25 | IS_ADS_ENABLED 26 | 27 | IS_ANALYTICS_ENABLED 28 | 29 | IS_APPINVITE_ENABLED 30 | 31 | IS_GCM_ENABLED 32 | 33 | IS_SIGNIN_ENABLED 34 | 35 | GOOGLE_APP_ID 36 | 1:156700296828:ios:97baeb6b58528dc9 37 | DATABASE_URL 38 | https://tinder-clone-28618.firebaseio.com 39 | 40 | -------------------------------------------------------------------------------- /Tinder/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Tinder/Views/HomeBottomControlsStackView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeBottomControlsStackView.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 26/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HomeBottomControlsStackView: UIStackView { 12 | 13 | static func createButton(image: UIImage) -> UIButton { 14 | 15 | let button = UIButton(type: .system) 16 | button.setImage(image.withRenderingMode(.alwaysOriginal), for: .normal) 17 | button.imageView?.contentMode = .scaleToFill 18 | return button 19 | } 20 | 21 | let refreshButton = createButton(image: #imageLiteral(resourceName: "refresh_circle")) 22 | let dislikeButton = createButton(image: #imageLiteral(resourceName: "dismiss_circle")) 23 | let superLikeButton = createButton(image: #imageLiteral(resourceName: "super_like_circle")) 24 | let likeButton = createButton(image: #imageLiteral(resourceName: "like_circle")) 25 | let specialButton = createButton(image: #imageLiteral(resourceName: "boost_circle")) 26 | 27 | override init(frame: CGRect) { 28 | super.init(frame: frame) 29 | 30 | distribution = .fillEqually 31 | heightAnchor.constraint(equalToConstant: 120).isActive = true 32 | 33 | [refreshButton, dislikeButton, superLikeButton, likeButton, specialButton].forEach { (button) in 34 | 35 | self.addArrangedSubview(button) 36 | } 37 | } 38 | 39 | required init(coder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tinder/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 | -------------------------------------------------------------------------------- /Tinder/Base.lproj/Main.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 | -------------------------------------------------------------------------------- /Tinder/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 | } -------------------------------------------------------------------------------- /Tinder/Model/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 26/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct User: ProducesCardViewModel { 12 | 13 | var name: String? 14 | var age: Int? 15 | var profession: String? 16 | var imageUrl1: String? 17 | var imageUrl2: String? 18 | var imageUrl3: String? 19 | var uid: String? 20 | 21 | var minSeekingAge: Int? 22 | var maxSeekingAge: Int? 23 | 24 | init(dictionary: [String: Any]) { 25 | 26 | // Initialize our user here 27 | self.age = dictionary["age"] as? Int 28 | self.profession = dictionary["profession"] as? String 29 | self.name = dictionary["fullName"] as? String ?? "" 30 | self.imageUrl1 = dictionary["imageUrl1"] as? String 31 | self.imageUrl2 = dictionary["imageUrl2"] as? String 32 | self.imageUrl3 = dictionary["imageUrl3"] as? String 33 | self.uid = dictionary["uid"] as? String ?? "" 34 | self.minSeekingAge = dictionary["minSeekingAge"] as? Int 35 | self.maxSeekingAge = dictionary["maxSeekingAge"] as? Int 36 | } 37 | 38 | func toCardViewModel() -> CardViewModel { 39 | 40 | let attributedText = NSMutableAttributedString(string: name ?? "", attributes: [.font: UIFont.systemFont(ofSize: 32, weight: .heavy)]) 41 | 42 | let ageString = age != nil ? "\(age!)" : "N\\A" 43 | attributedText.append(NSAttributedString(string: " \(ageString)", attributes: [.font: UIFont.systemFont(ofSize: 24, weight: .regular)])) 44 | 45 | let professionString = self.profession != nil ? self.profession! : "Not available" 46 | attributedText.append(NSAttributedString(string: "\n\(professionString)", attributes: [.font: UIFont.systemFont(ofSize: 20, weight: .regular)])) 47 | 48 | var imageUrls = [String]() 49 | 50 | if let url = imageUrl1 { imageUrls.append(url) } 51 | if let url = imageUrl2 { imageUrls.append(url) } 52 | if let url = imageUrl3 { imageUrls.append(url) } 53 | 54 | return CardViewModel(uid: self.uid ?? "", imageNames: imageUrls, attributedString: attributedText, textAlignment: .left) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tinder/Views/AgeRangeCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AgeRangeCell.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 03/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AgeRangeCell: UITableViewCell { 12 | 13 | let minSlider: UISlider = { 14 | 15 | let slider = UISlider() 16 | slider.minimumValue = 18 17 | slider.maximumValue = 100 18 | return slider 19 | }() 20 | 21 | let maxSlider: UISlider = { 22 | 23 | let slider = UISlider() 24 | slider.minimumValue = 18 25 | slider.maximumValue = 100 26 | return slider 27 | }() 28 | 29 | let minLabel: UILabel = { 30 | 31 | let label = UILabel() 32 | label.text = "Min: 18" 33 | return label 34 | }() 35 | 36 | let maxLabel: UILabel = { 37 | 38 | let label = UILabel() 39 | label.text = "Max: 88" 40 | return label 41 | }() 42 | 43 | class AgeRangeLabel: UILabel { 44 | 45 | override var intrinsicContentSize: CGSize { 46 | return .init(width: 80, height: 0) 47 | } 48 | } 49 | 50 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 51 | super.init(style: style, reuseIdentifier: reuseIdentifier) 52 | let padding: CGFloat = 16 53 | let topStackView = UIStackView(arrangedSubviews: [minLabel, minSlider]) 54 | topStackView.spacing = padding 55 | 56 | let bottomStackView = UIStackView(arrangedSubviews: [maxLabel, maxSlider]) 57 | bottomStackView.spacing = padding 58 | 59 | let overallStackView = UIStackView(arrangedSubviews: [ 60 | 61 | topStackView, 62 | bottomStackView 63 | ]) 64 | 65 | overallStackView.axis = .vertical 66 | overallStackView.distribution = .fillEqually 67 | overallStackView.spacing = padding 68 | addSubview(overallStackView) 69 | overallStackView.anchor(top: topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor, padding: .init(top: padding, left: padding, bottom: -padding, right: -padding)) 70 | } 71 | 72 | required init?(coder aDecoder: NSCoder) { 73 | fatalError("init(coder:) has not been implemented") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Tinder/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 23/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 19 | 20 | FirebaseApp.configure() 21 | 22 | window = UIWindow() 23 | window?.makeKeyAndVisible() 24 | window?.rootViewController = HomeController() 25 | 26 | return true 27 | } 28 | 29 | func applicationWillResignActive(_ application: UIApplication) { 30 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 31 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 32 | } 33 | 34 | func applicationDidEnterBackground(_ application: UIApplication) { 35 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 36 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 37 | } 38 | 39 | func applicationWillEnterForeground(_ application: UIApplication) { 40 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 41 | } 42 | 43 | func applicationDidBecomeActive(_ application: UIApplication) { 44 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 45 | } 46 | 47 | func applicationWillTerminate(_ application: UIApplication) { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Tinder/Controllers/RegistrationViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegistrationViewModel.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 27/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | 12 | class RegistrationViewModel { 13 | 14 | var bindableImage = Bindable() 15 | var bindableIsFormValid = Bindable() 16 | var bindableIsRegistering = Bindable() 17 | 18 | var fullName: String? { didSet { checkForValidity() } } 19 | var email: String? { didSet { checkForValidity() } } 20 | var password: String? { didSet { checkForValidity() } } 21 | 22 | func checkForValidity() { 23 | 24 | let isFormValid = fullName?.isEmpty == false && email?.isEmpty == false && password?.isEmpty == false && bindableImage.value != nil 25 | bindableIsFormValid.value = isFormValid 26 | } 27 | 28 | func perfromRegistration(completion: @escaping (Error?) -> ()) { 29 | 30 | guard let email = email, let password = password else { return } 31 | 32 | bindableIsRegistering.value = true 33 | 34 | Auth.auth().createUser(withEmail: email, password: password) { (result, error) in 35 | 36 | if let error = error { 37 | 38 | completion(error) 39 | return 40 | } 41 | 42 | self.saveImageToFirebase(completion: completion) 43 | 44 | } 45 | } 46 | 47 | fileprivate func saveImageToFirebase(completion: @escaping (Error?) -> ()) { 48 | 49 | // You can only upload 50 | let fileName = UUID().uuidString 51 | let reference = Storage.storage().reference(withPath: "/images/\(fileName).jpg") 52 | let imageData = self.bindableImage.value?.jpegData(compressionQuality: 0.75) ?? Data() 53 | reference.putData(imageData, metadata: nil, completion: { (_, err) in 54 | 55 | if let err = err { 56 | completion(err) 57 | return 58 | } 59 | 60 | reference.downloadURL(completion: { (url, error) in 61 | 62 | if let error = error { 63 | completion(error) 64 | return 65 | } 66 | 67 | let imageUrl = url?.absoluteString ?? "" 68 | 69 | self.bindableIsRegistering.value = false 70 | self.saveInfoToFirestore(imageURL: imageUrl, completion: completion) 71 | }) 72 | }) 73 | } 74 | 75 | fileprivate func saveInfoToFirestore(imageURL: String, completion: @escaping (Error?) -> ()) { 76 | 77 | let uid = Auth.auth().currentUser?.uid ?? "" 78 | 79 | let documentData: [String: Any] = [ 80 | "fullName": fullName ?? "", 81 | "uid": uid, 82 | "imageUrl1": imageURL, 83 | "age": SettingsController.defaultMinSeekingAge, 84 | "minSeekingAge": SettingsController.defaultMinSeekingAge, 85 | "maxSeekingAge": SettingsController.defaultMaxSeekingAge 86 | ] 87 | 88 | Firestore.firestore().collection("users").document(uid).setData(documentData) { (error) in 89 | 90 | if let error = error { 91 | 92 | completion(error) 93 | return 94 | } 95 | 96 | completion(nil) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Tinder/Extensions/Extensions+UIView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+UIView.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 26/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | static func RGBColor(red: CGFloat, green: CGFloat, blue: CGFloat) -> UIColor { 14 | 15 | return UIColor(red: red/255, green: green/255, blue: blue/255, alpha: 1) 16 | } 17 | } 18 | 19 | struct AnchoredConstraints { 20 | 21 | var top, leading, bottom, trailing, width, height: NSLayoutConstraint? 22 | } 23 | 24 | extension UIView { 25 | 26 | @discardableResult 27 | func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -> AnchoredConstraints { 28 | 29 | translatesAutoresizingMaskIntoConstraints = false 30 | 31 | var anchoredConstraints = AnchoredConstraints() 32 | 33 | if let top = top { 34 | 35 | anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top) 36 | } 37 | 38 | if let leading = leading { 39 | 40 | anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left) 41 | } 42 | 43 | if let bottom = bottom { 44 | 45 | anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: padding.bottom) 46 | } 47 | 48 | if let trailing = trailing { 49 | 50 | anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: padding.right) 51 | } 52 | 53 | 54 | if size.width != 0 { 55 | 56 | anchoredConstraints.width = widthAnchor.constraint(equalToConstant: size.width) 57 | } 58 | 59 | if size.height != 0 { 60 | 61 | anchoredConstraints.height = heightAnchor.constraint(equalToConstant: size.height) 62 | } 63 | 64 | [anchoredConstraints.top, anchoredConstraints.leading, anchoredConstraints.bottom, anchoredConstraints.trailing, anchoredConstraints.width, anchoredConstraints.height].forEach{ $0?.isActive = true } 65 | 66 | return anchoredConstraints 67 | } 68 | 69 | func fillSuperview(padding: UIEdgeInsets = .zero) { 70 | 71 | translatesAutoresizingMaskIntoConstraints = false 72 | 73 | if let superviewTopAnchor = superview?.topAnchor { 74 | 75 | topAnchor.constraint(equalTo: superviewTopAnchor, constant: padding.top).isActive = true 76 | } 77 | 78 | if let superviewBottomAnchor = superview?.bottomAnchor { 79 | 80 | bottomAnchor.constraint(equalTo: superviewBottomAnchor, constant: padding.bottom).isActive = true 81 | } 82 | 83 | if let superviewLeadingAnchor = superview?.leadingAnchor { 84 | 85 | leadingAnchor.constraint(equalTo: superviewLeadingAnchor, constant: padding.left).isActive = true 86 | } 87 | 88 | if let superviewTrailingAnchor = superview?.trailingAnchor { 89 | 90 | trailingAnchor.constraint(equalTo: superviewTrailingAnchor, constant: padding.right).isActive = true 91 | } 92 | } 93 | 94 | func centerInSuperview(size: CGSize = .zero) { 95 | 96 | translatesAutoresizingMaskIntoConstraints = false 97 | 98 | if let superviewCenterXAnchor = superview?.centerXAnchor { 99 | 100 | centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true 101 | } 102 | 103 | if let superviewCenterYAnchor = superview?.centerYAnchor { 104 | 105 | centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true 106 | } 107 | 108 | if size.width != 0 { 109 | 110 | widthAnchor.constraint(equalToConstant: size.width).isActive = true 111 | } 112 | 113 | if size.height != 0 { 114 | 115 | heightAnchor.constraint(equalToConstant: size.height).isActive = true 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Tinder/Controllers/SwipePhotosController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwipePhotosController.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 04/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SwipePhotosController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate { 12 | 13 | var controllers = [UIViewController]() 14 | 15 | var cardViewModel: CardViewModel! { 16 | didSet { 17 | controllers = cardViewModel.imageUrls.map({ (imageUrl) -> UIViewController in 18 | let photoController = PhotoController(imageUrl: imageUrl) 19 | return photoController 20 | }) 21 | 22 | setViewControllers([controllers.first!], direction: .forward, animated: false, completion: nil) 23 | 24 | setupBarViews() 25 | } 26 | } 27 | 28 | fileprivate let barsStackView = UIStackView(arrangedSubviews: []) 29 | fileprivate let deselctedBarColor = UIColor(white: 0, alpha: 0.1) 30 | 31 | fileprivate func setupBarViews() { 32 | 33 | cardViewModel.imageUrls.forEach { (_) in 34 | 35 | let barView = UIView() 36 | barView.backgroundColor = deselctedBarColor 37 | barView.layer.cornerRadius = 2 38 | barsStackView.addArrangedSubview(barView) 39 | } 40 | 41 | barsStackView.arrangedSubviews.first?.backgroundColor = .white 42 | barsStackView.spacing = 4 43 | barsStackView.distribution = .fillEqually 44 | 45 | view.addSubview(barsStackView) 46 | 47 | var paddingTop:CGFloat = 8 48 | 49 | if !isCardViewMode { 50 | 51 | paddingTop += UIApplication.shared.statusBarFrame.height 52 | } 53 | 54 | barsStackView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: paddingTop, left: 8, bottom: 0, right: -8), size: .init(width: 0, height: 4)) 55 | } 56 | 57 | fileprivate let isCardViewMode: Bool 58 | 59 | init(isCardViewMode: Bool = false) { 60 | 61 | self.isCardViewMode = isCardViewMode 62 | super.init(transitionStyle: .scroll, navigationOrientation: .horizontal) 63 | } 64 | 65 | required init?(coder: NSCoder) { 66 | fatalError("init(coder:) has not been implemented") 67 | } 68 | 69 | override func viewDidLoad() { 70 | super.viewDidLoad() 71 | 72 | dataSource = self 73 | delegate = self 74 | view.backgroundColor = .white 75 | 76 | if isCardViewMode { 77 | disableSwipingAbility() 78 | } 79 | 80 | view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleCardTap))) 81 | } 82 | 83 | @objc fileprivate func handleCardTap(gesture: UITapGestureRecognizer) { 84 | 85 | let currentViewController = viewControllers!.first! 86 | if let index = controllers.firstIndex(of: currentViewController) { 87 | 88 | barsStackView.arrangedSubviews.forEach({$0.backgroundColor = deselctedBarColor}) 89 | 90 | if gesture.location(in: self.view).x > view.frame.width / 2 { 91 | 92 | let nextIndex = min(index + 1, controllers.count - 1) 93 | let nextController = controllers[nextIndex] 94 | setViewControllers([nextController], direction: .forward, animated: false) 95 | 96 | barsStackView.arrangedSubviews[nextIndex].backgroundColor = .white 97 | } else { 98 | let previousIndex = max(0, index - 1) 99 | let previousController = controllers[previousIndex] 100 | setViewControllers([previousController], direction: .forward, animated: false) 101 | 102 | barsStackView.arrangedSubviews[previousIndex].backgroundColor = .white 103 | } 104 | } 105 | } 106 | 107 | fileprivate func disableSwipingAbility() { 108 | view.subviews.forEach { (view) in 109 | if let view = view as? UIScrollView { 110 | view.isScrollEnabled = false 111 | } 112 | } 113 | } 114 | 115 | func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { 116 | 117 | let currentPhotoController = viewControllers?.first 118 | if let index = controllers.firstIndex(where: {$0 == currentPhotoController}) { 119 | barsStackView.arrangedSubviews.forEach({$0.backgroundColor = deselctedBarColor}) 120 | barsStackView.arrangedSubviews[index].backgroundColor = .white 121 | } 122 | } 123 | 124 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { 125 | 126 | let index = self.controllers.firstIndex(where: {$0 == viewController}) ?? 0 127 | 128 | if index == controllers.count - 1 { return nil } 129 | return controllers[index + 1] 130 | } 131 | 132 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { 133 | 134 | let index = self.controllers.firstIndex(where: {$0 == viewController}) ?? 0 135 | 136 | if index == 0 { return nil } 137 | return controllers[index - 1] 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /Tinder/Controllers/UserDetailsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDetailsController.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 04/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UserDetailsController: UIViewController, UIScrollViewDelegate { 12 | 13 | var cardViewModel: CardViewModel! { 14 | didSet { 15 | infoLabel.attributedText = cardViewModel.attributedString 16 | swipingPhotosController.cardViewModel = cardViewModel 17 | } 18 | } 19 | 20 | lazy var scrollView: UIScrollView = { 21 | let scrollView = UIScrollView() 22 | scrollView.alwaysBounceVertical = true 23 | scrollView.contentInsetAdjustmentBehavior = .never 24 | scrollView.delegate = self 25 | return scrollView 26 | }() 27 | 28 | let swipingPhotosController = SwipePhotosController() 29 | 30 | let infoLabel: UILabel = { 31 | let label = UILabel() 32 | label.text = "User name 30\nDoctor\nSome bio text below" 33 | label.numberOfLines = 0 34 | return label 35 | }() 36 | 37 | lazy var dismissButton: UIButton = { 38 | let button = UIButton(type: .system) 39 | button.setImage(#imageLiteral(resourceName: "34").withRenderingMode(.alwaysOriginal), for: .normal) 40 | button.addTarget(self, action: #selector(handleDismissButton), for: .touchUpInside) 41 | return button 42 | }() 43 | 44 | lazy var dislikeButton = self.createButton(image: #imageLiteral(resourceName: "dismiss_circle").withRenderingMode(.alwaysOriginal), selector: #selector(handleDislikeButton)) 45 | 46 | lazy var superLikeButton = self.createButton(image: #imageLiteral(resourceName: "super_like_circle").withRenderingMode(.alwaysOriginal), selector: #selector(handleSuperLikeButton)) 47 | 48 | lazy var likeButton = self.createButton(image: #imageLiteral(resourceName: "like_circle").withRenderingMode(.alwaysOriginal), selector: #selector(handleLikeButton)) 49 | 50 | fileprivate func createButton(image: UIImage, selector: Selector) -> UIButton { 51 | let button = UIButton(type: .system) 52 | button.setImage(image, for: .normal) 53 | button.imageView?.contentMode = .scaleAspectFill 54 | button.addTarget(self, action: selector, for: .touchUpInside) 55 | return button 56 | } 57 | 58 | @objc fileprivate func handleDislikeButton() { 59 | 60 | } 61 | 62 | @objc fileprivate func handleSuperLikeButton() { 63 | 64 | } 65 | 66 | @objc fileprivate func handleLikeButton() { 67 | 68 | } 69 | 70 | override func viewDidLoad() { 71 | super.viewDidLoad() 72 | view.backgroundColor = .white 73 | 74 | setupLayout() 75 | setupVisualBlurEffectView() 76 | setupBottomControls() 77 | } 78 | 79 | fileprivate func setupBottomControls() { 80 | 81 | let stackView = UIStackView(arrangedSubviews: [dislikeButton, superLikeButton, likeButton]) 82 | stackView.distribution = .fillEqually 83 | stackView.spacing = -32 84 | 85 | view.addSubview(stackView) 86 | stackView.anchor(top: nil, leading: nil, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: nil, padding: .init(top: 0, left: 0, bottom: -16, right: 0), size: .init(width: 300, height: 80)) 87 | stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 88 | } 89 | 90 | fileprivate func setupVisualBlurEffectView() { 91 | 92 | let blurEddect = UIBlurEffect(style: .regular) 93 | let visualEffectView = UIVisualEffectView(effect: blurEddect) 94 | 95 | view.addSubview(visualEffectView) 96 | visualEffectView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.topAnchor, trailing: view.trailingAnchor) 97 | } 98 | 99 | fileprivate func setupLayout() { 100 | 101 | view.addSubview(scrollView) 102 | scrollView.fillSuperview() 103 | 104 | let swipingView = swipingPhotosController.view! 105 | scrollView.addSubview(swipingView) 106 | 107 | 108 | scrollView.addSubview(infoLabel) 109 | infoLabel.anchor(top: swipingView.bottomAnchor, leading: scrollView.leadingAnchor, bottom: nil, trailing: scrollView.trailingAnchor, padding: .init(top: 16, left: 16, bottom: -16, right: -16)) 110 | 111 | scrollView.addSubview(dismissButton) 112 | dismissButton.anchor(top: swipingView.bottomAnchor, leading: nil, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: -25, left: 0, bottom: 0, right: -25), size: .init(width: 50, height: 50)) 113 | } 114 | 115 | fileprivate let extraSwipeHeight: CGFloat = 80 116 | 117 | override func viewWillLayoutSubviews() { 118 | super.viewWillLayoutSubviews() 119 | let swipingView = swipingPhotosController.view! 120 | swipingView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width + extraSwipeHeight) 121 | } 122 | 123 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 124 | 125 | let changeY = -scrollView.contentOffset.y 126 | var width = view.frame.width + changeY * 2 127 | width = max(view.frame.width, width) 128 | 129 | let imageView = swipingPhotosController.view! 130 | imageView.frame = CGRect(x: min(0, -changeY), y: min(0, -changeY), width: width, height: width + extraSwipeHeight) 131 | } 132 | 133 | @objc fileprivate func handleDismissButton() { 134 | 135 | self.dismiss(animated: true, completion: nil) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Tinder/Views/CardView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardView.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 26/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SDWebImage 11 | 12 | protocol CardViewDelegate { 13 | 14 | func didTapMoreInfo(cardViewModel: CardViewModel) 15 | func didRemoveCard(cardView: CardView) 16 | } 17 | 18 | class CardView: UIView { 19 | 20 | var nextCardView: CardView? 21 | 22 | var cardViewDelegate: CardViewDelegate? 23 | 24 | var cardViewModel: CardViewModel! { 25 | 26 | didSet { 27 | 28 | swipingPhotosController.cardViewModel = self.cardViewModel 29 | 30 | informationLabel.attributedText = cardViewModel.attributedString 31 | informationLabel.textAlignment = cardViewModel.textAlignment 32 | 33 | } 34 | } 35 | 36 | fileprivate let swipingPhotosController = SwipePhotosController(isCardViewMode: true) 37 | fileprivate let informationLabel = UILabel() 38 | let gradientLayer = CAGradientLayer() 39 | 40 | 41 | // Configuration 42 | fileprivate let treshold: CGFloat = 120 43 | 44 | override init(frame: CGRect) { 45 | super.init(frame: frame) 46 | 47 | setupLayout() 48 | 49 | let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(gesture:))) 50 | addGestureRecognizer(panGesture) 51 | 52 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(gesture:))) 53 | addGestureRecognizer(tapGesture) 54 | } 55 | 56 | lazy var moreInfoButton: UIButton = { 57 | let button = UIButton(type: .system) 58 | button.setImage(#imageLiteral(resourceName: "info_icon").withRenderingMode(.alwaysOriginal), for: .normal) 59 | button.addTarget(self, action: #selector(handelMoreInfoButton), for: .touchUpInside) 60 | return button 61 | }() 62 | 63 | @objc fileprivate func handelMoreInfoButton() { 64 | 65 | // Use delegate 66 | cardViewDelegate?.didTapMoreInfo(cardViewModel: self.cardViewModel) 67 | } 68 | 69 | fileprivate func setupLayout() { 70 | 71 | layer.cornerRadius = 10 72 | clipsToBounds = true 73 | 74 | let swipingPhotosView = swipingPhotosController.view! 75 | addSubview(swipingPhotosView) 76 | swipingPhotosView.fillSuperview() 77 | 78 | // Add gradian layer 79 | setupGradianLayer() 80 | 81 | addSubview(informationLabel) 82 | informationLabel.textColor = .white 83 | informationLabel.numberOfLines = 0 84 | 85 | informationLabel.anchor(top: nil, leading: leadingAnchor, bottom: bottomAnchor, trailing: trailingAnchor, padding: .init(top: 0, left: 16, bottom: -16, right: 16)) 86 | 87 | addSubview(moreInfoButton) 88 | moreInfoButton.anchor(top: nil, leading: nil, bottom: bottomAnchor, trailing: trailingAnchor, padding: .init(top: 0, left: 0, bottom: -16, right: -16), size: .init(width: 44, height: 44)) 89 | } 90 | 91 | fileprivate func setupGradianLayer() { 92 | 93 | gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor] 94 | gradientLayer.locations = [0.5, 1.1] 95 | 96 | layer.addSublayer(gradientLayer) 97 | } 98 | 99 | override func layoutSubviews() { 100 | 101 | gradientLayer.frame = self.frame 102 | 103 | } 104 | 105 | fileprivate let barDeselectedColor = UIColor(white: 0, alpha: 0.1) 106 | 107 | @objc fileprivate func handleTap(gesture: UITapGestureRecognizer) { 108 | 109 | 110 | let tapLocation = gesture.location(in: nil) 111 | let shouldAdvanceNextPhoto = tapLocation.x > frame.width / 2 ? true : false 112 | 113 | if shouldAdvanceNextPhoto { 114 | 115 | cardViewModel.advaceToNextPhoto() 116 | } else { 117 | 118 | cardViewModel.goToPreviousPhoto() 119 | } 120 | } 121 | 122 | @objc fileprivate func handlePan(gesture: UIPanGestureRecognizer) { 123 | 124 | switch gesture.state { 125 | case .began: 126 | superview?.subviews.forEach({ (subview) in 127 | subview.layer.removeAllAnimations() 128 | }) 129 | case .changed: 130 | handleChanged(gesture) 131 | case .ended: 132 | handleEnded(gesture) 133 | default: 134 | () 135 | } 136 | } 137 | 138 | fileprivate func handleChanged(_ gesture: UIPanGestureRecognizer) { 139 | 140 | // Rotation 141 | let translation = gesture.translation(in: nil) 142 | let degrees: CGFloat = translation.x / 20 143 | let angle = degrees * .pi / 180 144 | 145 | let rotationalTransformation = CGAffineTransform(rotationAngle: angle) 146 | self.transform = rotationalTransformation.translatedBy(x: translation.x, y: translation.y) 147 | } 148 | 149 | fileprivate func handleEnded(_ gesture: UIPanGestureRecognizer) { 150 | 151 | let translationDirection: CGFloat = gesture.translation(in: nil).x > 0 ? 1 : -1 152 | let shouldDismissCard = gesture.translation(in: nil).x > treshold || gesture.translation(in: nil).x < -treshold 153 | 154 | if shouldDismissCard { 155 | guard let homeController = self.cardViewDelegate as? HomeController else { return } 156 | if translationDirection == 1 { 157 | homeController.handleLikeButton() 158 | } else { 159 | homeController.handleDislikeButton() 160 | } 161 | } else{ 162 | UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.1, options: .curveEaseOut, animations: { 163 | self.transform = .identity 164 | }) 165 | } 166 | } 167 | 168 | required init?(coder aDecoder: NSCoder) { 169 | fatalError("init(coder:) has not been implemented") 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Tinder/Controllers/LoginController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginController.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 03/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JGProgressHUD 11 | 12 | protocol LoginControllerDelegate { 13 | func didFinishLoggingIn() 14 | } 15 | 16 | class LoginController: UIViewController { 17 | 18 | var loginDelegate: LoginControllerDelegate? 19 | 20 | lazy var emailTextField: UITextField = { 21 | let textField = CustomTextField(padding: 22, height: 44) 22 | textField.placeholder = "Enger email" 23 | textField.keyboardType = .emailAddress 24 | textField.addTarget(self, action: #selector(handleTextField), for: .editingChanged) 25 | return textField 26 | }() 27 | 28 | lazy var passwordTextField: UITextField = { 29 | let textField = CustomTextField(padding: 22, height: 44) 30 | textField.placeholder = "Enter password" 31 | textField.isSecureTextEntry = true 32 | textField.addTarget(self, action: #selector(handleTextField), for: .editingChanged) 33 | return textField 34 | }() 35 | 36 | lazy var loginButton: UIButton = { 37 | let button = UIButton(type: .system) 38 | button.setTitle("Login", for: .normal) 39 | button.setTitleColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), for: .normal) 40 | button.setTitleColor(#colorLiteral(red: 0.3459398746, green: 0.340980351, blue: 0.3452142477, alpha: 1), for: .disabled) 41 | button.backgroundColor = #colorLiteral(red: 0.6714041233, green: 0.6664924026, blue: 0.6706650853, alpha: 1) 42 | button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .heavy) 43 | button.isEnabled = false 44 | button.heightAnchor.constraint(equalToConstant: 44).isActive = true 45 | button.layer.cornerRadius = 22 46 | button.clipsToBounds = true 47 | button.addTarget(self, action: #selector(handleLoginButton), for: .touchUpInside) 48 | 49 | return button 50 | }() 51 | 52 | lazy var backToRegisterButton: UIButton = { 53 | let button = UIButton() 54 | button.setTitle("Go Back", for: .normal) 55 | button.setTitleColor(.white, for: .normal) 56 | button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) 57 | button.addTarget(self, action: #selector(handleBackButton), for: .touchUpInside) 58 | return button 59 | }() 60 | 61 | fileprivate let loginViewModel = LoginViewModel() 62 | fileprivate let loginHUD = JGProgressHUD(style: .dark) 63 | 64 | fileprivate func setupTapGesture() { 65 | 66 | view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap))) 67 | } 68 | 69 | @objc fileprivate func handleTap() { 70 | 71 | self.view.endEditing(true) 72 | } 73 | 74 | @objc fileprivate func handleTextField(textField: UITextField) { 75 | 76 | if textField == emailTextField { 77 | 78 | loginViewModel.email = textField.text 79 | } else { 80 | loginViewModel.password = textField.text 81 | } 82 | } 83 | 84 | @objc fileprivate func handleLoginButton() { 85 | 86 | loginViewModel.performLogin { (error) in 87 | self.loginHUD.dismiss() 88 | if let error = error { 89 | print("Failed to login: \(error)") 90 | } 91 | 92 | self.dismiss(animated: true, completion: { 93 | self.loginDelegate?.didFinishLoggingIn() 94 | }) 95 | } 96 | } 97 | 98 | @objc fileprivate func handleBackButton() { 99 | 100 | navigationController?.popViewController(animated: true) 101 | } 102 | 103 | lazy var verticalStackView: UIStackView = { 104 | let stacView = UIStackView(arrangedSubviews: [ 105 | emailTextField, 106 | passwordTextField, 107 | loginButton 108 | ]) 109 | stacView.axis = .vertical 110 | stacView.spacing = 8 111 | return stacView 112 | }() 113 | 114 | override func viewDidLoad() { 115 | super.viewDidLoad() 116 | 117 | setupGradientLayer() 118 | setupLayout() 119 | setupTapGesture() 120 | setupBindables() 121 | } 122 | 123 | fileprivate func setupBindables() { 124 | loginViewModel.isFormValid.bind { [unowned self] (isFormValid) in 125 | 126 | guard let ifFormValid = isFormValid else { return } 127 | self.loginButton.isEnabled = ifFormValid 128 | self.loginButton.backgroundColor = ifFormValid ? #colorLiteral(red: 0.8252845407, green: 0, blue: 0.3240153193, alpha: 1) : .lightGray 129 | } 130 | 131 | loginViewModel.isLogedIn.bind { [unowned self] (isRegistering) in 132 | 133 | if isRegistering == true { 134 | self.loginHUD.textLabel.text = "Registering..." 135 | self.loginHUD.show(in: self.view) 136 | } else { 137 | self.loginHUD.dismiss() 138 | } 139 | } 140 | } 141 | 142 | let gradientLayer = CAGradientLayer() 143 | 144 | override func viewWillLayoutSubviews() { 145 | super.viewWillLayoutSubviews() 146 | gradientLayer.frame = view.bounds 147 | } 148 | 149 | fileprivate func setupGradientLayer() { 150 | 151 | let topColor = #colorLiteral(red: 0.9880711436, green: 0.3838337064, blue: 0.3728808165, alpha: 1) 152 | let bottomColor = #colorLiteral(red: 0.8920591474, green: 0.1065689698, blue: 0.4587435722, alpha: 1) 153 | 154 | gradientLayer.colors = [topColor.cgColor, bottomColor.cgColor] 155 | gradientLayer.locations = [0, 1] 156 | 157 | view.layer.addSublayer(gradientLayer) 158 | 159 | gradientLayer.frame = view.bounds 160 | } 161 | 162 | fileprivate func setupLayout() { 163 | 164 | navigationController?.isNavigationBarHidden = true 165 | view.addSubview(verticalStackView) 166 | verticalStackView.anchor(top: nil, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 0, left: 50, bottom: 0, right: -50)) 167 | verticalStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 168 | 169 | view.addSubview(backToRegisterButton) 170 | backToRegisterButton.anchor(top: nil, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.trailingAnchor) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Tinder/Views/MatchView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MatchView.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 05/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | 12 | class MatchView: UIView { 13 | 14 | var currentUser: User! { 15 | didSet { 16 | 17 | } 18 | } 19 | 20 | var cardUID: String! { 21 | didSet { 22 | 23 | // Fetch card UID information 24 | Firestore.firestore().collection("users").document(cardUID).getDocument { (snapshot, error) in 25 | 26 | if let error = error { 27 | print("Error Failed to fetch card user: \(error)") 28 | return 29 | } 30 | 31 | guard let dictionary = snapshot?.data() else { return } 32 | let user = User(dictionary: dictionary) 33 | guard let url = URL(string: user.imageUrl1 ?? "") else { return } 34 | self.cardUserImageView.sd_setImage(with: url) 35 | 36 | guard let currentUserImageUrl = URL(string: self.currentUser.imageUrl1 ?? "") else { return } 37 | self.currentImageView.sd_setImage(with: currentUserImageUrl, completed: { (_, _, _, _) in 38 | self.setupAnimation() 39 | }) 40 | 41 | self.descriptionLabel.text = "You and \(user.name ?? "") have been liked each other." 42 | } 43 | } 44 | } 45 | 46 | fileprivate let viusalEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) 47 | 48 | fileprivate let itsAMatchImageView: UIImageView = { 49 | let imageView = UIImageView(image: #imageLiteral(resourceName: "itsamatch")) 50 | imageView.contentMode = .scaleAspectFill 51 | return imageView 52 | }() 53 | 54 | fileprivate let descriptionLabel: UILabel = { 55 | let label = UILabel() 56 | label.text = "You and X have been liked each other." 57 | label.textAlignment = .center 58 | label.textColor = .white 59 | label.font = UIFont.systemFont(ofSize: 20) 60 | label.numberOfLines = 0 61 | return label 62 | }() 63 | 64 | fileprivate let currentImageView: UIImageView = { 65 | let imageView = UIImageView(image: #imageLiteral(resourceName: "photo_placeholder")) 66 | imageView.contentMode = .scaleAspectFill 67 | imageView.clipsToBounds = true 68 | imageView.layer.borderWidth = 2 69 | imageView.layer.borderColor = UIColor.white.cgColor 70 | return imageView 71 | }() 72 | 73 | fileprivate let cardUserImageView: UIImageView = { 74 | let imageView = UIImageView(image: #imageLiteral(resourceName: "photo_placeholder")) 75 | imageView.contentMode = .scaleAspectFill 76 | imageView.clipsToBounds = true 77 | imageView.layer.borderWidth = 2 78 | imageView.layer.borderColor = UIColor.white.cgColor 79 | return imageView 80 | }() 81 | 82 | fileprivate let sendMessageButton: UIButton = { 83 | let button = SendMessageButton(type: .system) 84 | button.setTitle("SEND MESSAGE", for: .normal) 85 | button.setTitleColor(.white, for: .normal) 86 | return button 87 | }() 88 | 89 | fileprivate let keepSwipingButton: UIButton = { 90 | let button = KeepSwipingButton(type: .system) 91 | button.setTitle("Keep swiping", for: .normal) 92 | button.setTitleColor(.white, for: .normal) 93 | return button 94 | }() 95 | 96 | override init(frame: CGRect) { 97 | super.init(frame: frame) 98 | 99 | setupBlurView() 100 | setupLayout() 101 | } 102 | 103 | fileprivate func setupAnimation() { 104 | 105 | views.forEach({$0.alpha = 1}) 106 | 107 | // Starting positions 108 | let angle = 30 * CGFloat.pi / 180 109 | 110 | currentImageView.transform = CGAffineTransform(rotationAngle: -angle).concatenating(CGAffineTransform(translationX: 200, y: 0)) 111 | cardUserImageView.transform = CGAffineTransform(rotationAngle: angle).concatenating(CGAffineTransform(translationX: -200, y: 0)) 112 | 113 | sendMessageButton.transform = CGAffineTransform(translationX: -500, y: 0) 114 | keepSwipingButton.transform = CGAffineTransform(translationX: 500, y: 0) 115 | 116 | // Key Frame animation 117 | UIView.animateKeyframes(withDuration: 1.3, delay: 0, options: .calculationModeCubic, animations: { 118 | 119 | //Animation translation back to original position 120 | UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5, animations: { 121 | self.currentImageView.transform = CGAffineTransform(rotationAngle: angle) 122 | self.cardUserImageView.transform = CGAffineTransform(rotationAngle: -angle) 123 | }) 124 | 125 | //Animation rotation 126 | UIView.addKeyframe(withRelativeStartTime: 0.6, relativeDuration: 0.5, animations: { 127 | 128 | self.currentImageView.transform = .identity 129 | self.cardUserImageView.transform = .identity 130 | 131 | 132 | }) 133 | 134 | }) { (_) in 135 | 136 | } 137 | 138 | UIView.animate(withDuration: 0.75, delay: 0.6 * 1.3, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.1, options: .curveEaseOut, animations: { 139 | self.sendMessageButton.transform = .identity 140 | self.keepSwipingButton.transform = .identity 141 | }) 142 | } 143 | 144 | lazy var views = [ 145 | 146 | itsAMatchImageView, 147 | descriptionLabel, 148 | currentImageView, 149 | cardUserImageView, 150 | sendMessageButton, 151 | keepSwipingButton 152 | ] 153 | 154 | fileprivate func setupLayout() { 155 | 156 | views.forEach { (view) in 157 | addSubview(view) 158 | view.alpha = 0 159 | } 160 | 161 | let imageWith: CGFloat = 140 162 | 163 | itsAMatchImageView.anchor(top: nil, leading: nil, bottom: descriptionLabel.topAnchor, trailing: nil, padding: .init(top: 0, left: 0, bottom: -16, right: 0), size: .init(width: 300, height: 80)) 164 | itsAMatchImageView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true 165 | 166 | descriptionLabel.anchor(top: nil, leading: self.leadingAnchor, bottom: currentImageView.topAnchor, trailing: self.trailingAnchor, padding: .init(top: 0, left: 0, bottom: -32, right: 0), size: .init(width: 0, height: 50)) 167 | 168 | currentImageView.anchor(top: nil, leading: nil, bottom: nil, trailing: centerXAnchor, padding: .init(top: 0, left: 0, bottom: 0, right: -16), size: .init(width: imageWith, height: imageWith)) 169 | currentImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true 170 | currentImageView.layer.cornerRadius = imageWith / 2 171 | 172 | cardUserImageView.anchor(top: nil, leading: centerXAnchor, bottom: nil, trailing: nil, padding: .init(top: 0, left: 16, bottom: 0, right: 0), size: .init(width: imageWith, height: imageWith)) 173 | cardUserImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true 174 | cardUserImageView.layer.cornerRadius = imageWith / 2 175 | 176 | sendMessageButton.anchor(top: currentImageView.bottomAnchor, leading: leadingAnchor, bottom: nil, trailing: trailingAnchor, padding: .init(top: 32, left: 48, bottom: 0, right: -48), size: .init(width: 0, height: 60)) 177 | 178 | keepSwipingButton.anchor(top: sendMessageButton.bottomAnchor, leading: sendMessageButton.leadingAnchor, bottom: nil, trailing: sendMessageButton.trailingAnchor, padding: .init(top: 16, left: 0, bottom: 0, right: 0), size: .init(width: 0, height: 60)) 179 | } 180 | 181 | fileprivate func setupBlurView() { 182 | 183 | viusalEffectView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss))) 184 | addSubview(viusalEffectView) 185 | viusalEffectView.fillSuperview() 186 | viusalEffectView.alpha = 0 187 | 188 | UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: { 189 | self.viusalEffectView.alpha = 1 190 | }) { (_) in 191 | 192 | } 193 | } 194 | 195 | @objc fileprivate func handleDismiss() { 196 | 197 | UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: { 198 | self.alpha = 0 199 | }) { (_) in 200 | self.removeFromSuperview() 201 | } 202 | } 203 | 204 | required init?(coder aDecoder: NSCoder) { 205 | fatalError("init(coder:) has not been implemented") 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Tinder/Controllers/HomeController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeController.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 23/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | import FirebaseFirestore 12 | import JGProgressHUD 13 | 14 | class HomeController: UIViewController, SettingsControllerDelegate, LoginControllerDelegate, CardViewDelegate { 15 | 16 | let topStackView = TopNavigationStackView() 17 | let cardsDeckView = UIView() 18 | let bottomControls = HomeBottomControlsStackView() 19 | 20 | var cardViewModels = [CardViewModel]() 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | topStackView.settingsButton.addTarget(self, action: #selector(handleSettingsButton), for: .touchUpInside) 26 | bottomControls.refreshButton.addTarget(self, action: #selector(handleRefreshButton), for: .touchUpInside) 27 | bottomControls.likeButton.addTarget(self, action: #selector(handleLikeButton), for: .touchUpInside) 28 | bottomControls.dislikeButton.addTarget(self, action: #selector(handleDislikeButton), for: .touchUpInside) 29 | setupLayout() 30 | fetchCurrentUser() 31 | } 32 | 33 | override func viewDidAppear(_ animated: Bool) { 34 | super.viewDidAppear(animated) 35 | 36 | if Auth.auth().currentUser == nil { 37 | 38 | let registrationController = RegistrationController() 39 | registrationController.loginControllerDelegate = self 40 | let navigationController = UINavigationController(rootViewController: registrationController) 41 | present(navigationController, animated: true, completion: nil) 42 | } 43 | } 44 | 45 | func didFinishLoggingIn() { 46 | 47 | fetchCurrentUser() 48 | } 49 | 50 | fileprivate var user: User? 51 | 52 | fileprivate func fetchCurrentUser() { 53 | 54 | cardsDeckView.subviews.forEach({$0.removeFromSuperview()}) 55 | Firestore.firestore().fetchCurrentUser { (user, error) in 56 | 57 | if let error = error { 58 | print("Error, \(error)") 59 | return 60 | } 61 | 62 | self.user = user 63 | 64 | self.fetchSwipes() 65 | } 66 | } 67 | 68 | var swipes = [String: Int]() 69 | 70 | fileprivate func fetchSwipes() { 71 | 72 | guard let uid = Auth.auth().currentUser?.uid else { return } 73 | Firestore.firestore().collection("swipes").document(uid).getDocument { (snapshot, error) in 74 | if let error = error { 75 | print("Failed to fetch sipes info for currently logged in user: ",error) 76 | return 77 | } 78 | 79 | guard let data = snapshot?.data() as? [String: Int] else { return } 80 | self.swipes = data 81 | self.fetchUsersFromFirebase() 82 | } 83 | } 84 | 85 | @objc fileprivate func handleRefreshButton() { 86 | 87 | cardsDeckView.subviews.forEach({$0.removeFromSuperview()}) 88 | fetchUsersFromFirebase() 89 | } 90 | 91 | var lastFetchedUser: User? 92 | 93 | fileprivate func fetchUsersFromFirebase() { 94 | 95 | let minAge = user?.minSeekingAge ?? SettingsController.defaultMinSeekingAge 96 | let maxAge = user?.maxSeekingAge ?? SettingsController.defaultMaxSeekingAge 97 | 98 | let hud = JGProgressHUD(style: .dark) 99 | hud.textLabel.text = "Fetching Users" 100 | hud.show(in: view) 101 | 102 | let query = Firestore.firestore().collection("users").whereField("age", isGreaterThan: minAge - 1).whereField("age", isLessThan: maxAge + 1) 103 | // let query = Firestore.firestore().collection("users") 104 | 105 | topCardView = nil 106 | 107 | query.getDocuments { (snapshot, error) in 108 | 109 | hud.dismiss() 110 | 111 | if let error = error { 112 | print("Error: \(error)") 113 | return 114 | } 115 | 116 | var prevoiusCardView: CardView? 117 | 118 | snapshot?.documents.forEach({ (documentSnapshot) in 119 | 120 | let userDictionary = documentSnapshot.data() 121 | let user = User(dictionary: userDictionary) 122 | 123 | let isNotCurrentUser = user.uid != Auth.auth().currentUser?.uid 124 | // let hasSwipedBefore = self.swipes[user.uid!] == nil 125 | let hasSwipedBefore = true 126 | if isNotCurrentUser && hasSwipedBefore { 127 | 128 | let cardView = self.setupCardFromUser(user: user) 129 | 130 | prevoiusCardView?.nextCardView = cardView 131 | prevoiusCardView = cardView 132 | 133 | if self.topCardView == nil { 134 | 135 | self.topCardView = cardView 136 | } 137 | } 138 | }) 139 | } 140 | } 141 | 142 | var topCardView: CardView? 143 | 144 | @objc func handleLikeButton() { 145 | 146 | saveSwipeToFirestore(didLike: 1) 147 | performSwipeAnimation(translation: 700, angle: 15) 148 | 149 | } 150 | 151 | @objc func handleDislikeButton() { 152 | 153 | saveSwipeToFirestore(didLike: 0) 154 | performSwipeAnimation(translation: -700, angle: -10) 155 | 156 | } 157 | 158 | fileprivate func saveSwipeToFirestore(didLike: Int) { 159 | guard let uid = Auth.auth().currentUser?.uid else { return } 160 | 161 | guard let cardUID = topCardView?.cardViewModel.uid else { return } 162 | 163 | let documentData = [cardUID: didLike] 164 | 165 | Firestore.firestore().collection("swipes").document(uid).getDocument { (snapshot, err) in 166 | if let err = err { 167 | print("Failed to fetch swipe document:", err) 168 | return 169 | } 170 | 171 | if snapshot?.exists == true { 172 | Firestore.firestore().collection("swipes").document(uid).updateData(documentData) { (err) in 173 | if let err = err { 174 | print("Failed to save swipe data:", err) 175 | return 176 | } 177 | 178 | if didLike == 1 { 179 | self.checkIfMatchExists(cardUID: cardUID) 180 | } 181 | } 182 | } else { 183 | Firestore.firestore().collection("swipes").document(uid).setData(documentData) { (err) in 184 | if let err = err { 185 | print("Failed to save swipe data:", err) 186 | return 187 | } 188 | 189 | if didLike == 1 { 190 | self.checkIfMatchExists(cardUID: cardUID) 191 | } 192 | } 193 | } 194 | } 195 | } 196 | 197 | fileprivate func checkIfMatchExists(cardUID: String) { 198 | 199 | Firestore.firestore().collection("swipes").document(cardUID).getDocument { (snapshot, err) in 200 | if let err = err { 201 | print("Failed to fetch document for card user:", err) 202 | return 203 | } 204 | 205 | guard let data = snapshot?.data() else { return } 206 | print(data) 207 | 208 | guard let uid = Auth.auth().currentUser?.uid else { return } 209 | 210 | let hasMatched = data[uid] as? Int == 1 211 | if hasMatched { 212 | 213 | self.presentMatchView(cardUID: cardUID) 214 | } 215 | } 216 | } 217 | 218 | fileprivate func presentMatchView(cardUID: String) { 219 | 220 | let matchView = MatchView() 221 | matchView.currentUser = self.user 222 | matchView.cardUID = cardUID 223 | view.addSubview(matchView) 224 | matchView.fillSuperview() 225 | } 226 | 227 | func didRemoveCard(cardView: CardView) { 228 | 229 | self.topCardView?.removeFromSuperview() 230 | self.topCardView = self.topCardView?.nextCardView 231 | } 232 | 233 | fileprivate func performSwipeAnimation(translation: CGFloat, angle: CGFloat) { 234 | 235 | let duration = 0.5 236 | let translationAnimation = CABasicAnimation(keyPath: "position.x") 237 | translationAnimation.toValue = translation 238 | translationAnimation.duration = duration 239 | translationAnimation.fillMode = .forwards 240 | translationAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut) 241 | translationAnimation.isRemovedOnCompletion = false 242 | 243 | let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") 244 | rotationAnimation.toValue = angle * CGFloat.pi / 180 245 | rotationAnimation.duration = duration 246 | 247 | let coardView = topCardView 248 | topCardView = coardView?.nextCardView 249 | 250 | CATransaction.setCompletionBlock { 251 | coardView?.removeFromSuperview() 252 | } 253 | 254 | coardView?.layer.add(translationAnimation, forKey: "translation") 255 | coardView?.layer.add(rotationAnimation, forKey: "rotation") 256 | 257 | CATransaction.commit() 258 | } 259 | 260 | fileprivate func setupCardFromUser(user: User) -> CardView { 261 | 262 | let cardView = CardView(frame: .zero) 263 | cardView.cardViewModel = user.toCardViewModel() 264 | cardView.cardViewDelegate = self 265 | cardsDeckView.addSubview(cardView) 266 | cardsDeckView.sendSubviewToBack(cardView) 267 | cardView.fillSuperview() 268 | 269 | return cardView 270 | } 271 | 272 | func didTapMoreInfo(cardViewModel: CardViewModel) { 273 | 274 | let userDetailsController = UserDetailsController() 275 | userDetailsController.cardViewModel = cardViewModel 276 | present(userDetailsController, animated: true, completion: nil) 277 | } 278 | 279 | @objc fileprivate func handleSettingsButton() { 280 | 281 | let settingsController = SettingsController() 282 | settingsController.settingDelegate = self 283 | let navigationController = UINavigationController(rootViewController: settingsController) 284 | present(navigationController, animated: true, completion: nil) 285 | 286 | } 287 | 288 | func didSaveSettings() { 289 | 290 | fetchCurrentUser() 291 | } 292 | 293 | //MARK: - Setup File Private Methods 294 | fileprivate func setupLayout() { 295 | 296 | view.backgroundColor = .white 297 | 298 | let overallStackView = UIStackView(arrangedSubviews: [topStackView, cardsDeckView, bottomControls]) 299 | overallStackView.axis = .vertical 300 | view.addSubview(overallStackView) 301 | 302 | overallStackView.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.trailingAnchor) 303 | 304 | overallStackView.isLayoutMarginsRelativeArrangement = true 305 | overallStackView.layoutMargins = .init(top: 0, left: 12, bottom: 0, right: 12) 306 | 307 | overallStackView.bringSubviewToFront(cardsDeckView) 308 | } 309 | 310 | fileprivate func setupFirestoreUserCards() { 311 | 312 | cardViewModels.forEach { (cardViewModel) in 313 | 314 | let cardView = CardView(frame: .zero) 315 | cardView.cardViewModel = cardViewModel 316 | cardsDeckView.addSubview(cardView) 317 | cardView.fillSuperview() 318 | } 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /Tinder/Controllers/RegistrationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegistrationController.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 27/01/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | import JGProgressHUD 12 | 13 | class RegistrationController: UIViewController { 14 | 15 | var loginControllerDelegate: LoginControllerDelegate? 16 | 17 | // UI Components 18 | lazy var selectPhotoButton: UIButton = { 19 | 20 | let button = UIButton(type: .system) 21 | button.setTitle("Select Photo", for: .normal) 22 | button.titleLabel?.font = UIFont.systemFont(ofSize: 32, weight: .heavy) 23 | button.backgroundColor = .white 24 | button.setTitleColor(.black, for: .normal) 25 | button.heightAnchor.constraint(equalToConstant: 275).isActive = true 26 | button.layer.cornerRadius = 16 27 | button.clipsToBounds = true 28 | button.addTarget(self, action: #selector(handleSelectPhoto), for: .touchUpInside) 29 | button.imageView?.contentMode = .scaleAspectFill 30 | return button 31 | }() 32 | 33 | lazy var fullNameTextField: CustomTextField = { 34 | 35 | let textField = CustomTextField(padding: 16, height: 44) 36 | textField.placeholder = "Enter full name" 37 | textField.addTarget(self, action: #selector(handleTextChange(_:)), for: .editingChanged) 38 | 39 | return textField 40 | }() 41 | 42 | lazy var emailTextField: CustomTextField = { 43 | 44 | let textField = CustomTextField(padding: 16, height: 44) 45 | textField.placeholder = "Enter email" 46 | textField.keyboardType = .emailAddress 47 | textField.addTarget(self, action: #selector(handleTextChange(_:)), for: .editingChanged) 48 | 49 | return textField 50 | }() 51 | 52 | lazy var passwordTextField: CustomTextField = { 53 | 54 | let textField = CustomTextField(padding: 16, height: 44) 55 | textField.placeholder = "Enter password" 56 | textField.backgroundColor = .white 57 | textField.isSecureTextEntry = true 58 | textField.addTarget(self, action: #selector(handleTextChange(_:)), for: .editingChanged) 59 | 60 | return textField 61 | }() 62 | 63 | lazy var registerButton: UIButton = { 64 | 65 | let button = UIButton(type: .system) 66 | button.setTitle("Register", for: .normal) 67 | button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .heavy) 68 | button.setTitleColor(#colorLiteral(red: 0.3459398746, green: 0.340980351, blue: 0.3452142477, alpha: 1), for: .disabled) 69 | button.setTitleColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), for: .normal) 70 | button.backgroundColor = #colorLiteral(red: 0.6714041233, green: 0.6664924026, blue: 0.6706650853, alpha: 1) 71 | button.heightAnchor.constraint(equalToConstant: 44).isActive = true 72 | button.clipsToBounds = true 73 | button.layer.cornerRadius = 22 74 | button.isEnabled = false 75 | button.addTarget(self, action: #selector(handleRegisterButton), for: .touchUpInside) 76 | return button 77 | }() 78 | 79 | lazy var goToLoginButton: UIButton = { 80 | let button = UIButton(type: .system) 81 | button.setTitle("Go to Login", for: .normal) 82 | button.setTitleColor(.white, for: .normal) 83 | button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .heavy) 84 | button.addTarget(self, action: #selector(handleGoToLogin), for: .touchUpInside) 85 | return button 86 | }() 87 | 88 | override func viewDidLoad() { 89 | super.viewDidLoad() 90 | 91 | setupNotificationObservers() 92 | setupGradientLayer() 93 | setupLayout() 94 | setupTapGesture() 95 | setupRegistrationViewModelObservers() 96 | } 97 | 98 | @objc fileprivate func handleGoToLogin() { 99 | 100 | let loginController = LoginController() 101 | loginController.loginDelegate = loginControllerDelegate 102 | navigationController?.pushViewController(loginController, animated: true) 103 | } 104 | 105 | @objc fileprivate func handleSelectPhoto() { 106 | 107 | let imagePickerController = UIImagePickerController() 108 | imagePickerController.delegate = self 109 | present(imagePickerController, animated: true, completion: nil) 110 | } 111 | 112 | let registeringHUD = JGProgressHUD(style: .dark) 113 | 114 | @objc fileprivate func handleRegisterButton() { 115 | 116 | self.handleTap() 117 | 118 | registrationViewModel.perfromRegistration { [weak self] (error) in 119 | 120 | if let error = error { 121 | 122 | self?.showHUDWithError(error: error) 123 | return 124 | } 125 | 126 | print("Finished registering") 127 | self?.dismiss(animated: true, completion: { 128 | self?.loginControllerDelegate?.didFinishLoggingIn() 129 | }) 130 | } 131 | } 132 | 133 | fileprivate func showHUDWithError(error: Error) { 134 | 135 | registeringHUD.dismiss(animated: true) 136 | let hud = JGProgressHUD(style: .dark) 137 | hud.textLabel.text = "Failed registration" 138 | hud.detailTextLabel.text = error.localizedDescription 139 | hud.show(in: self.view) 140 | hud.dismiss(afterDelay: 4) 141 | } 142 | 143 | let registrationViewModel = RegistrationViewModel() 144 | 145 | //MARK: - Register Model View Bindable 146 | fileprivate func setupRegistrationViewModelObservers() { 147 | 148 | registrationViewModel.bindableIsFormValid.bind { [unowned self] (isFormValid) in 149 | guard let isFormValid = isFormValid else { return } 150 | self.registerButton.isEnabled = isFormValid 151 | if isFormValid { 152 | 153 | self.registerButton.backgroundColor = #colorLiteral(red: 0.8111879826, green: 0.1042452082, blue: 0.3321437836, alpha: 1) 154 | 155 | } else { 156 | 157 | self.registerButton.backgroundColor = #colorLiteral(red: 0.6714041233, green: 0.6664924026, blue: 0.6706650853, alpha: 1) 158 | } 159 | } 160 | 161 | registrationViewModel.bindableImage.bind { [unowned self] (image) in 162 | 163 | self.selectPhotoButton.setImage(image?.withRenderingMode(.alwaysOriginal), for: .normal) 164 | } 165 | 166 | registrationViewModel.bindableIsRegistering.bind { [unowned self] (isRegistering) in 167 | 168 | if isRegistering == true { 169 | 170 | self.registeringHUD.textLabel.text = "Register" 171 | self.registeringHUD.show(in: self.view) 172 | } else { 173 | self.registeringHUD.dismiss(animated: true) 174 | } 175 | } 176 | } 177 | 178 | @objc fileprivate func handleTextChange(_ textField: UITextField) { 179 | 180 | if textField == fullNameTextField { 181 | 182 | registrationViewModel.fullName = textField.text 183 | 184 | } else if textField == emailTextField { 185 | 186 | registrationViewModel.email = textField.text 187 | 188 | } else { 189 | 190 | registrationViewModel.password = textField.text 191 | } 192 | } 193 | 194 | fileprivate func setupTapGesture() { 195 | 196 | view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap))) 197 | } 198 | 199 | @objc fileprivate func handleTap() { 200 | 201 | self.view.endEditing(true) 202 | } 203 | 204 | fileprivate func setupNotificationObservers() { 205 | 206 | NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) 207 | NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) 208 | } 209 | 210 | @objc fileprivate func handleKeyboardHide(_ notification: Notification) { 211 | 212 | UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: { 213 | 214 | self.view.transform = .identity 215 | 216 | }, completion: nil) 217 | } 218 | 219 | @objc fileprivate func handleKeyboardShow(_ notification: Notification) { 220 | 221 | guard let value = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return } 222 | 223 | let keybaordFrame = value.cgRectValue 224 | let bottomSpace = view.frame.height - overllStackView.frame.origin.y - overllStackView.frame.height 225 | let differnce = keybaordFrame.height - bottomSpace 226 | 227 | self.view.transform = CGAffineTransform(translationX: 0, y: -differnce - 8) 228 | } 229 | 230 | let gradientLayer = CAGradientLayer() 231 | 232 | fileprivate func setupGradientLayer() { 233 | 234 | let topColor = #colorLiteral(red: 0.9880711436, green: 0.3838337064, blue: 0.3728808165, alpha: 1) 235 | let bottomColor = #colorLiteral(red: 0.8920591474, green: 0.1065689698, blue: 0.4587435722, alpha: 1) 236 | 237 | gradientLayer.colors = [topColor.cgColor, bottomColor.cgColor] 238 | gradientLayer.locations = [0, 1] 239 | 240 | view.layer.addSublayer(gradientLayer) 241 | 242 | gradientLayer.frame = view.bounds 243 | } 244 | 245 | override func viewWillLayoutSubviews() { 246 | super.viewWillLayoutSubviews() 247 | 248 | gradientLayer.frame = view.bounds 249 | 250 | } 251 | 252 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 253 | 254 | if self.traitCollection.verticalSizeClass == .compact { 255 | 256 | overllStackView.axis = .horizontal 257 | } else { 258 | 259 | overllStackView.axis = .vertical 260 | } 261 | } 262 | 263 | lazy var verticalStackView: UIStackView = { 264 | 265 | let stackView = UIStackView(arrangedSubviews: [ 266 | fullNameTextField, 267 | emailTextField, 268 | passwordTextField, 269 | registerButton 270 | ]) 271 | stackView.axis = .vertical 272 | stackView.distribution = .fillEqually 273 | stackView.spacing = 24 274 | return stackView 275 | }() 276 | 277 | lazy var overllStackView = UIStackView(arrangedSubviews: [ 278 | selectPhotoButton, 279 | verticalStackView 280 | ]) 281 | 282 | fileprivate func setupLayout() { 283 | 284 | navigationController?.isNavigationBarHidden = true 285 | view.addSubview(overllStackView) 286 | overllStackView.axis = .horizontal 287 | overllStackView.spacing = 24 288 | 289 | selectPhotoButton.widthAnchor.constraint(equalToConstant: 275).isActive = true 290 | 291 | overllStackView.anchor(top: nil, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 0, left: 50, bottom: 0, right: -50)) 292 | overllStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 293 | 294 | view.addSubview(goToLoginButton) 295 | goToLoginButton.anchor(top: nil, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.trailingAnchor) 296 | } 297 | } 298 | 299 | extension RegistrationController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 300 | 301 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 302 | 303 | dismiss(animated: true, completion: nil) 304 | } 305 | 306 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 307 | 308 | let image = info[.originalImage] as? UIImage 309 | registrationViewModel.bindableImage.value = image 310 | registrationViewModel.checkForValidity() 311 | 312 | dismiss(animated: true, completion: nil) 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /Tinder/Controllers/SettingsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsController.swift 3 | // Tinder 4 | // 5 | // Created by Bekzod Rakhmatov on 03/02/2019. 6 | // Copyright © 2019 BekzodRakhmatov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Firebase 11 | import JGProgressHUD 12 | import SDWebImage 13 | 14 | protocol SettingsControllerDelegate { 15 | func didSaveSettings() 16 | } 17 | 18 | class CustomerImagePickerController: UIImagePickerController { 19 | 20 | var imageButton: UIButton? 21 | 22 | } 23 | 24 | class SettingsController: UITableViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 25 | 26 | var settingDelegate: SettingsControllerDelegate? 27 | 28 | lazy var image1Button = createButton(selector: #selector(handleSelectPhoto)) 29 | lazy var image2Button = createButton(selector: #selector(handleSelectPhoto)) 30 | lazy var image3Button = createButton(selector: #selector(handleSelectPhoto)) 31 | 32 | @objc func handleSelectPhoto(button: UIButton) { 33 | 34 | let imagePickerController = CustomerImagePickerController() 35 | imagePickerController.delegate = self 36 | imagePickerController.imageButton = button 37 | present(imagePickerController, animated: true, completion: nil) 38 | } 39 | 40 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 41 | 42 | let selectedImage = info[.originalImage] as? UIImage 43 | let imageButton = (picker as? CustomerImagePickerController)?.imageButton 44 | imageButton?.setImage(selectedImage?.withRenderingMode(.alwaysOriginal), for: .normal) 45 | 46 | let fileName = UUID().uuidString 47 | let reference = Storage.storage().reference(withPath: "/images/\(fileName)") 48 | guard let uploadDate = selectedImage?.jpegData(compressionQuality: 0.75) else { return } 49 | 50 | let hud = JGProgressHUD(style: .dark) 51 | hud.textLabel.text = "Uploading image..." 52 | hud.show(in: view) 53 | reference.putData(uploadDate, metadata: nil) { (nil, error) in 54 | 55 | if let error = error { 56 | 57 | hud.dismiss() 58 | print("Failed to upload to Storage: \(error)") 59 | return 60 | } 61 | 62 | reference.downloadURL(completion: { (url, error) in 63 | 64 | hud.dismiss() 65 | 66 | if let error = error { 67 | 68 | print("Failed to retrieve download url: \(error)") 69 | return 70 | } 71 | 72 | if imageButton == self.image1Button { 73 | 74 | self.user?.imageUrl1 = url?.absoluteString 75 | 76 | } else if imageButton == self.image2Button { 77 | 78 | self.user?.imageUrl2 = url?.absoluteString 79 | 80 | } else { 81 | 82 | self.user?.imageUrl3 = url?.absoluteString 83 | } 84 | }) 85 | } 86 | 87 | dismiss(animated: true, completion: nil) 88 | } 89 | 90 | func createButton(selector: Selector) -> UIButton { 91 | 92 | let button = UIButton(type: .system) 93 | button.setTitle("Select Photo", for: .normal) 94 | button.backgroundColor = .white 95 | button.layer.cornerRadius = 8 96 | button.clipsToBounds = true 97 | button.imageView?.contentMode = .scaleAspectFill 98 | button.addTarget(self, action: selector, for: .touchUpInside) 99 | return button 100 | } 101 | 102 | override func viewDidLoad() { 103 | super.viewDidLoad() 104 | 105 | setupNavigationItems() 106 | tableView.backgroundColor = UIColor(white: 0.95, alpha: 1) 107 | tableView.tableFooterView = UIView() 108 | tableView.keyboardDismissMode = .interactive 109 | 110 | fetchCurrentUser() 111 | } 112 | 113 | var user: User? 114 | 115 | fileprivate func fetchCurrentUser() { 116 | 117 | Firestore.firestore().fetchCurrentUser { (user, error) in 118 | if let error = error { 119 | print("Failed to fetch user:", error) 120 | return 121 | } 122 | self.user = user 123 | self.loadUserPhotos() 124 | self.tableView.reloadData() 125 | } 126 | } 127 | 128 | fileprivate func loadUserPhotos() { 129 | 130 | if let imageUrl = user?.imageUrl1, let url = URL(string: imageUrl) { 131 | 132 | SDWebImageManager.shared.loadImage(with: url, options: .continueInBackground, progress: nil) { (image, _, _, _, _, _) in 133 | self.image1Button.setImage(image?.withRenderingMode(.alwaysOriginal), for: .normal) 134 | } 135 | } 136 | 137 | if let imageUrl = user?.imageUrl2, let url = URL(string: imageUrl) { 138 | 139 | SDWebImageManager.shared.loadImage(with: url, options: .continueInBackground, progress: nil) { (image, _, _, _, _, _) in 140 | self.image2Button.setImage(image?.withRenderingMode(.alwaysOriginal), for: .normal) 141 | } 142 | } 143 | 144 | if let imageUrl = user?.imageUrl3, let url = URL(string: imageUrl) { 145 | 146 | SDWebImageManager.shared.loadImage(with: url, options: .continueInBackground, progress: nil) { (image, _, _, _, _, _) in 147 | self.image3Button.setImage(image?.withRenderingMode(.alwaysOriginal), for: .normal) 148 | } 149 | } 150 | } 151 | 152 | lazy var headerView: UIView = { 153 | 154 | let headerView = UIView() 155 | headerView.addSubview(image1Button) 156 | let padding: CGFloat = 16 157 | image1Button.anchor(top: headerView.topAnchor, leading: headerView.leadingAnchor, bottom: headerView.bottomAnchor, trailing: nil, padding: .init(top: padding, left: padding, bottom: -padding, right: 0)) 158 | image1Button.widthAnchor.constraint(equalTo: headerView.widthAnchor, multiplier: 0.45).isActive = true 159 | 160 | let stackView = UIStackView(arrangedSubviews: [image2Button, image3Button]) 161 | stackView.axis = .vertical 162 | stackView.distribution = .fillEqually 163 | stackView.spacing = padding 164 | 165 | headerView.addSubview(stackView) 166 | stackView.anchor(top: headerView.topAnchor, leading: image1Button.trailingAnchor, bottom: headerView.bottomAnchor, trailing: headerView.trailingAnchor, padding: .init(top: padding, left: padding, bottom: -padding, right: -padding)) 167 | return headerView 168 | }() 169 | 170 | class HeaderLabel: UILabel { 171 | 172 | override func drawText(in rect: CGRect) { 173 | super.drawText(in: rect.insetBy(dx: 16, dy: 0)) 174 | } 175 | } 176 | 177 | override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 178 | 179 | if section == 0 { 180 | 181 | return headerView 182 | 183 | } else { 184 | 185 | let headerLabel = HeaderLabel() 186 | switch section { 187 | case 1: 188 | headerLabel.text = "Name" 189 | break 190 | case 2: 191 | headerLabel.text = "Profession" 192 | break 193 | case 3: 194 | headerLabel.text = "Age" 195 | break 196 | case 4: 197 | headerLabel.text = "Bio" 198 | break 199 | default: 200 | headerLabel.text = "Seeking Age Range" 201 | } 202 | headerLabel.font = UIFont.boldSystemFont(ofSize: 14) 203 | return headerLabel 204 | } 205 | } 206 | 207 | override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 208 | 209 | if section == 0 { 210 | 211 | return 300 212 | 213 | } else { 214 | 215 | return 40 216 | } 217 | } 218 | 219 | override func numberOfSections(in tableView: UITableView) -> Int { 220 | 221 | return 6 222 | } 223 | 224 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 225 | 226 | return section == 0 ? 0 : 1 227 | } 228 | 229 | static let defaultMinSeekingAge = 18 230 | static let defaultMaxSeekingAge = 50 231 | 232 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 233 | 234 | if indexPath.section == 5 { 235 | 236 | let ageRangeCell = AgeRangeCell(style: .default, reuseIdentifier: nil) 237 | 238 | ageRangeCell.minSlider.addTarget(self, action: #selector(handleMinSliderChange), for: .valueChanged) 239 | ageRangeCell.maxSlider.addTarget(self, action: #selector(handleMaxSliderChange), for: .valueChanged) 240 | 241 | let minAge = user?.minSeekingAge ?? SettingsController.defaultMinSeekingAge 242 | let maxAge = user?.maxSeekingAge ?? SettingsController.defaultMaxSeekingAge 243 | 244 | ageRangeCell.minLabel.text = "Min: \(minAge)" 245 | ageRangeCell.maxLabel.text = "Max: \(maxAge)" 246 | 247 | ageRangeCell.minSlider.value = Float(CGFloat(minAge)) 248 | ageRangeCell.maxSlider.value = Float(CGFloat(maxAge)) 249 | return ageRangeCell 250 | } 251 | 252 | let cell = SettingsCell(style: .default, reuseIdentifier: nil) 253 | switch indexPath.section { 254 | case 1: 255 | cell.textField.placeholder = "Enter Name" 256 | cell.textField.text = user?.name 257 | cell.textField.addTarget(self, action: #selector(handleNameChange), for: .editingChanged) 258 | break 259 | case 2: 260 | cell.textField.placeholder = "Enter Profession" 261 | cell.textField.text = user?.profession 262 | cell.textField.addTarget(self, action: #selector(handleProfessionChange), for: .editingChanged) 263 | break 264 | case 3: 265 | cell.textField.placeholder = "Enter Age" 266 | if let age = user?.age { 267 | cell.textField.text = String(age) 268 | } 269 | cell.textField.addTarget(self, action: #selector(handleAgeChange), for: .editingChanged) 270 | break 271 | default: 272 | cell.textField.placeholder = "Enter Bio" 273 | } 274 | return cell 275 | } 276 | 277 | @objc fileprivate func handleMinSliderChange(slider: UISlider) { 278 | 279 | let indexPath = IndexPath(row: 0, section: 5) 280 | let ageRangeCell = tableView.cellForRow(at: indexPath) as! AgeRangeCell 281 | 282 | if ageRangeCell.minSlider.value > ageRangeCell.maxSlider.value { 283 | 284 | ageRangeCell.maxSlider.value = ageRangeCell.minSlider.value 285 | ageRangeCell.maxLabel.text = "Max: \(Int(slider.value))" 286 | } 287 | ageRangeCell.minLabel.text = "Min: \(Int(slider.value))" 288 | 289 | self.user?.minSeekingAge = Int(slider.value) 290 | } 291 | 292 | @objc fileprivate func handleMaxSliderChange(slider: UISlider) { 293 | 294 | let indexPath = IndexPath(row: 0, section: 5) 295 | let ageRangeCell = tableView.cellForRow(at: indexPath) as! AgeRangeCell 296 | if ageRangeCell.maxSlider.value < ageRangeCell.minSlider.value { 297 | 298 | ageRangeCell.minSlider.value = ageRangeCell.maxSlider.value 299 | ageRangeCell.minLabel.text = "Min: \(Int(slider.value))" 300 | } 301 | ageRangeCell.maxLabel.text = "Max: \(Int(slider.value))" 302 | 303 | self.user?.maxSeekingAge = Int(slider.value) 304 | } 305 | 306 | @objc fileprivate func handleNameChange(textField: UITextField) { 307 | 308 | self.user?.name = textField.text 309 | } 310 | 311 | @objc fileprivate func handleProfessionChange(textField: UITextField) { 312 | 313 | self.user?.profession = textField.text 314 | } 315 | 316 | @objc fileprivate func handleAgeChange(textField: UITextField) { 317 | 318 | self.user?.age = Int(textField.text ?? "") 319 | } 320 | 321 | fileprivate func setupNavigationItems() { 322 | 323 | navigationItem.title = "Settings" 324 | navigationController?.navigationBar.prefersLargeTitles = true 325 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(handleCancelButton)) 326 | navigationItem.rightBarButtonItems = [UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(handleSaveButton)), UIBarButtonItem(title: "Logout", style: .plain, target: self, action: #selector(handleLogoutButton))] 327 | } 328 | 329 | @objc fileprivate func handleCancelButton() { 330 | 331 | dismiss(animated: true, completion: nil) 332 | } 333 | 334 | @objc fileprivate func handleSaveButton() { 335 | 336 | guard let uid = Auth.auth().currentUser?.uid else { return } 337 | 338 | let documentData: [String : Any] = [ 339 | "uid": uid, 340 | "fullName": user?.name ?? "", 341 | "imageUrl1": user?.imageUrl1 ?? "", 342 | "imageUrl2": user?.imageUrl2 ?? "", 343 | "imageUrl3": user?.imageUrl3 ?? "", 344 | "age": user?.age ?? -1, 345 | "profession": user?.profession ?? "", 346 | "minSeekingAge": user?.minSeekingAge ?? -1, 347 | "maxSeekingAge": user?.maxSeekingAge ?? -1 348 | ] 349 | 350 | let hud = JGProgressHUD(style: .dark) 351 | hud.textLabel.text = "Saving settings" 352 | hud.show(in: view) 353 | 354 | Firestore.firestore().collection("users").document(uid).setData(documentData) { (error) in 355 | 356 | hud.dismiss() 357 | 358 | if let error = error { 359 | print("Failed to save user setting: \(error)") 360 | return 361 | } 362 | 363 | self.dismiss(animated: true, completion: { 364 | print("Dismissal complete") 365 | self.settingDelegate?.didSaveSettings() 366 | }) 367 | } 368 | } 369 | 370 | @objc fileprivate func handleLogoutButton() { 371 | 372 | try? Auth.auth().signOut() 373 | dismiss(animated: true) 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - abseil/algorithm (0.20200225.0): 3 | - abseil/algorithm/algorithm (= 0.20200225.0) 4 | - abseil/algorithm/container (= 0.20200225.0) 5 | - abseil/algorithm/algorithm (0.20200225.0): 6 | - abseil/base/config 7 | - abseil/algorithm/container (0.20200225.0): 8 | - abseil/algorithm/algorithm 9 | - abseil/base/core_headers 10 | - abseil/meta/type_traits 11 | - abseil/base (0.20200225.0): 12 | - abseil/base/atomic_hook (= 0.20200225.0) 13 | - abseil/base/base (= 0.20200225.0) 14 | - abseil/base/base_internal (= 0.20200225.0) 15 | - abseil/base/bits (= 0.20200225.0) 16 | - abseil/base/config (= 0.20200225.0) 17 | - abseil/base/core_headers (= 0.20200225.0) 18 | - abseil/base/dynamic_annotations (= 0.20200225.0) 19 | - abseil/base/endian (= 0.20200225.0) 20 | - abseil/base/errno_saver (= 0.20200225.0) 21 | - abseil/base/exponential_biased (= 0.20200225.0) 22 | - abseil/base/log_severity (= 0.20200225.0) 23 | - abseil/base/malloc_internal (= 0.20200225.0) 24 | - abseil/base/periodic_sampler (= 0.20200225.0) 25 | - abseil/base/pretty_function (= 0.20200225.0) 26 | - abseil/base/raw_logging_internal (= 0.20200225.0) 27 | - abseil/base/spinlock_wait (= 0.20200225.0) 28 | - abseil/base/throw_delegate (= 0.20200225.0) 29 | - abseil/base/atomic_hook (0.20200225.0): 30 | - abseil/base/config 31 | - abseil/base/core_headers 32 | - abseil/base/base (0.20200225.0): 33 | - abseil/base/atomic_hook 34 | - abseil/base/base_internal 35 | - abseil/base/config 36 | - abseil/base/core_headers 37 | - abseil/base/dynamic_annotations 38 | - abseil/base/log_severity 39 | - abseil/base/raw_logging_internal 40 | - abseil/base/spinlock_wait 41 | - abseil/meta/type_traits 42 | - abseil/base/base_internal (0.20200225.0): 43 | - abseil/base/config 44 | - abseil/meta/type_traits 45 | - abseil/base/bits (0.20200225.0): 46 | - abseil/base/config 47 | - abseil/base/core_headers 48 | - abseil/base/config (0.20200225.0) 49 | - abseil/base/core_headers (0.20200225.0): 50 | - abseil/base/config 51 | - abseil/base/dynamic_annotations (0.20200225.0) 52 | - abseil/base/endian (0.20200225.0): 53 | - abseil/base/config 54 | - abseil/base/core_headers 55 | - abseil/base/errno_saver (0.20200225.0): 56 | - abseil/base/config 57 | - abseil/base/exponential_biased (0.20200225.0): 58 | - abseil/base/config 59 | - abseil/base/core_headers 60 | - abseil/base/log_severity (0.20200225.0): 61 | - abseil/base/config 62 | - abseil/base/core_headers 63 | - abseil/base/malloc_internal (0.20200225.0): 64 | - abseil/base/base 65 | - abseil/base/base_internal 66 | - abseil/base/config 67 | - abseil/base/core_headers 68 | - abseil/base/dynamic_annotations 69 | - abseil/base/raw_logging_internal 70 | - abseil/base/periodic_sampler (0.20200225.0): 71 | - abseil/base/core_headers 72 | - abseil/base/exponential_biased 73 | - abseil/base/pretty_function (0.20200225.0) 74 | - abseil/base/raw_logging_internal (0.20200225.0): 75 | - abseil/base/atomic_hook 76 | - abseil/base/config 77 | - abseil/base/core_headers 78 | - abseil/base/log_severity 79 | - abseil/base/spinlock_wait (0.20200225.0): 80 | - abseil/base/base_internal 81 | - abseil/base/core_headers 82 | - abseil/base/errno_saver 83 | - abseil/base/throw_delegate (0.20200225.0): 84 | - abseil/base/config 85 | - abseil/base/raw_logging_internal 86 | - abseil/container/common (0.20200225.0): 87 | - abseil/meta/type_traits 88 | - abseil/types/optional 89 | - abseil/container/compressed_tuple (0.20200225.0): 90 | - abseil/utility/utility 91 | - abseil/container/container_memory (0.20200225.0): 92 | - abseil/memory/memory 93 | - abseil/utility/utility 94 | - abseil/container/fixed_array (0.20200225.0): 95 | - abseil/algorithm/algorithm 96 | - abseil/base/core_headers 97 | - abseil/base/dynamic_annotations 98 | - abseil/base/throw_delegate 99 | - abseil/container/compressed_tuple 100 | - abseil/memory/memory 101 | - abseil/container/flat_hash_map (0.20200225.0): 102 | - abseil/algorithm/container 103 | - abseil/container/container_memory 104 | - abseil/container/hash_function_defaults 105 | - abseil/container/raw_hash_map 106 | - abseil/memory/memory 107 | - abseil/container/hash_function_defaults (0.20200225.0): 108 | - abseil/base/config 109 | - abseil/hash/hash 110 | - abseil/strings/strings 111 | - abseil/container/hash_policy_traits (0.20200225.0): 112 | - abseil/meta/type_traits 113 | - abseil/container/hashtable_debug_hooks (0.20200225.0): 114 | - abseil/base/config 115 | - abseil/container/hashtablez_sampler (0.20200225.0): 116 | - abseil/base/base 117 | - abseil/base/core_headers 118 | - abseil/base/exponential_biased 119 | - abseil/container/have_sse 120 | - abseil/debugging/stacktrace 121 | - abseil/memory/memory 122 | - abseil/synchronization/synchronization 123 | - abseil/utility/utility 124 | - abseil/container/have_sse (0.20200225.0) 125 | - abseil/container/inlined_vector (0.20200225.0): 126 | - abseil/algorithm/algorithm 127 | - abseil/base/core_headers 128 | - abseil/base/throw_delegate 129 | - abseil/container/inlined_vector_internal 130 | - abseil/memory/memory 131 | - abseil/container/inlined_vector_internal (0.20200225.0): 132 | - abseil/base/core_headers 133 | - abseil/container/compressed_tuple 134 | - abseil/memory/memory 135 | - abseil/meta/type_traits 136 | - abseil/types/span 137 | - abseil/container/layout (0.20200225.0): 138 | - abseil/base/core_headers 139 | - abseil/meta/type_traits 140 | - abseil/strings/strings 141 | - abseil/types/span 142 | - abseil/utility/utility 143 | - abseil/container/raw_hash_map (0.20200225.0): 144 | - abseil/base/throw_delegate 145 | - abseil/container/container_memory 146 | - abseil/container/raw_hash_set 147 | - abseil/container/raw_hash_set (0.20200225.0): 148 | - abseil/base/bits 149 | - abseil/base/config 150 | - abseil/base/core_headers 151 | - abseil/base/endian 152 | - abseil/container/common 153 | - abseil/container/compressed_tuple 154 | - abseil/container/container_memory 155 | - abseil/container/hash_policy_traits 156 | - abseil/container/hashtable_debug_hooks 157 | - abseil/container/hashtablez_sampler 158 | - abseil/container/have_sse 159 | - abseil/container/layout 160 | - abseil/memory/memory 161 | - abseil/meta/type_traits 162 | - abseil/utility/utility 163 | - abseil/debugging/debugging_internal (0.20200225.0): 164 | - abseil/base/config 165 | - abseil/base/core_headers 166 | - abseil/base/dynamic_annotations 167 | - abseil/base/errno_saver 168 | - abseil/base/raw_logging_internal 169 | - abseil/debugging/demangle_internal (0.20200225.0): 170 | - abseil/base/base 171 | - abseil/base/config 172 | - abseil/base/core_headers 173 | - abseil/debugging/stacktrace (0.20200225.0): 174 | - abseil/base/config 175 | - abseil/base/core_headers 176 | - abseil/debugging/debugging_internal 177 | - abseil/debugging/symbolize (0.20200225.0): 178 | - abseil/base/base 179 | - abseil/base/config 180 | - abseil/base/core_headers 181 | - abseil/base/dynamic_annotations 182 | - abseil/base/malloc_internal 183 | - abseil/base/raw_logging_internal 184 | - abseil/debugging/debugging_internal 185 | - abseil/debugging/demangle_internal 186 | - abseil/hash/city (0.20200225.0): 187 | - abseil/base/config 188 | - abseil/base/core_headers 189 | - abseil/base/endian 190 | - abseil/hash/hash (0.20200225.0): 191 | - abseil/base/core_headers 192 | - abseil/base/endian 193 | - abseil/container/fixed_array 194 | - abseil/hash/city 195 | - abseil/meta/type_traits 196 | - abseil/numeric/int128 197 | - abseil/strings/strings 198 | - abseil/types/optional 199 | - abseil/types/variant 200 | - abseil/utility/utility 201 | - abseil/memory (0.20200225.0): 202 | - abseil/memory/memory (= 0.20200225.0) 203 | - abseil/memory/memory (0.20200225.0): 204 | - abseil/base/core_headers 205 | - abseil/meta/type_traits 206 | - abseil/meta (0.20200225.0): 207 | - abseil/meta/type_traits (= 0.20200225.0) 208 | - abseil/meta/type_traits (0.20200225.0): 209 | - abseil/base/config 210 | - abseil/numeric/int128 (0.20200225.0): 211 | - abseil/base/config 212 | - abseil/base/core_headers 213 | - abseil/strings/internal (0.20200225.0): 214 | - abseil/base/config 215 | - abseil/base/core_headers 216 | - abseil/base/endian 217 | - abseil/base/raw_logging_internal 218 | - abseil/meta/type_traits 219 | - abseil/strings/str_format (0.20200225.0): 220 | - abseil/strings/str_format_internal 221 | - abseil/strings/str_format_internal (0.20200225.0): 222 | - abseil/base/config 223 | - abseil/base/core_headers 224 | - abseil/meta/type_traits 225 | - abseil/numeric/int128 226 | - abseil/strings/strings 227 | - abseil/types/span 228 | - abseil/strings/strings (0.20200225.0): 229 | - abseil/base/base 230 | - abseil/base/bits 231 | - abseil/base/config 232 | - abseil/base/core_headers 233 | - abseil/base/endian 234 | - abseil/base/raw_logging_internal 235 | - abseil/base/throw_delegate 236 | - abseil/memory/memory 237 | - abseil/meta/type_traits 238 | - abseil/numeric/int128 239 | - abseil/strings/internal 240 | - abseil/synchronization/graphcycles_internal (0.20200225.0): 241 | - abseil/base/base 242 | - abseil/base/base_internal 243 | - abseil/base/config 244 | - abseil/base/core_headers 245 | - abseil/base/malloc_internal 246 | - abseil/base/raw_logging_internal 247 | - abseil/synchronization/kernel_timeout_internal (0.20200225.0): 248 | - abseil/base/core_headers 249 | - abseil/base/raw_logging_internal 250 | - abseil/time/time 251 | - abseil/synchronization/synchronization (0.20200225.0): 252 | - abseil/base/atomic_hook 253 | - abseil/base/base 254 | - abseil/base/base_internal 255 | - abseil/base/config 256 | - abseil/base/core_headers 257 | - abseil/base/dynamic_annotations 258 | - abseil/base/malloc_internal 259 | - abseil/base/raw_logging_internal 260 | - abseil/debugging/stacktrace 261 | - abseil/debugging/symbolize 262 | - abseil/synchronization/graphcycles_internal 263 | - abseil/synchronization/kernel_timeout_internal 264 | - abseil/time/time 265 | - abseil/time (0.20200225.0): 266 | - abseil/time/internal (= 0.20200225.0) 267 | - abseil/time/time (= 0.20200225.0) 268 | - abseil/time/internal (0.20200225.0): 269 | - abseil/time/internal/cctz (= 0.20200225.0) 270 | - abseil/time/internal/cctz (0.20200225.0): 271 | - abseil/time/internal/cctz/civil_time (= 0.20200225.0) 272 | - abseil/time/internal/cctz/time_zone (= 0.20200225.0) 273 | - abseil/time/internal/cctz/civil_time (0.20200225.0): 274 | - abseil/base/config 275 | - abseil/time/internal/cctz/time_zone (0.20200225.0): 276 | - abseil/base/config 277 | - abseil/time/internal/cctz/civil_time 278 | - abseil/time/time (0.20200225.0): 279 | - abseil/base/base 280 | - abseil/base/core_headers 281 | - abseil/base/raw_logging_internal 282 | - abseil/numeric/int128 283 | - abseil/strings/strings 284 | - abseil/time/internal/cctz/civil_time 285 | - abseil/time/internal/cctz/time_zone 286 | - abseil/types (0.20200225.0): 287 | - abseil/types/any (= 0.20200225.0) 288 | - abseil/types/bad_any_cast (= 0.20200225.0) 289 | - abseil/types/bad_any_cast_impl (= 0.20200225.0) 290 | - abseil/types/bad_optional_access (= 0.20200225.0) 291 | - abseil/types/bad_variant_access (= 0.20200225.0) 292 | - abseil/types/compare (= 0.20200225.0) 293 | - abseil/types/optional (= 0.20200225.0) 294 | - abseil/types/span (= 0.20200225.0) 295 | - abseil/types/variant (= 0.20200225.0) 296 | - abseil/types/any (0.20200225.0): 297 | - abseil/base/config 298 | - abseil/base/core_headers 299 | - abseil/meta/type_traits 300 | - abseil/types/bad_any_cast 301 | - abseil/utility/utility 302 | - abseil/types/bad_any_cast (0.20200225.0): 303 | - abseil/base/config 304 | - abseil/types/bad_any_cast_impl 305 | - abseil/types/bad_any_cast_impl (0.20200225.0): 306 | - abseil/base/config 307 | - abseil/base/raw_logging_internal 308 | - abseil/types/bad_optional_access (0.20200225.0): 309 | - abseil/base/config 310 | - abseil/base/raw_logging_internal 311 | - abseil/types/bad_variant_access (0.20200225.0): 312 | - abseil/base/config 313 | - abseil/base/raw_logging_internal 314 | - abseil/types/compare (0.20200225.0): 315 | - abseil/base/core_headers 316 | - abseil/meta/type_traits 317 | - abseil/types/optional (0.20200225.0): 318 | - abseil/base/base_internal 319 | - abseil/base/config 320 | - abseil/base/core_headers 321 | - abseil/memory/memory 322 | - abseil/meta/type_traits 323 | - abseil/types/bad_optional_access 324 | - abseil/utility/utility 325 | - abseil/types/span (0.20200225.0): 326 | - abseil/algorithm/algorithm 327 | - abseil/base/core_headers 328 | - abseil/base/throw_delegate 329 | - abseil/meta/type_traits 330 | - abseil/types/variant (0.20200225.0): 331 | - abseil/base/base_internal 332 | - abseil/base/config 333 | - abseil/base/core_headers 334 | - abseil/meta/type_traits 335 | - abseil/types/bad_variant_access 336 | - abseil/utility/utility 337 | - abseil/utility/utility (0.20200225.0): 338 | - abseil/base/base_internal 339 | - abseil/base/config 340 | - abseil/meta/type_traits 341 | - BoringSSL-GRPC (0.0.7): 342 | - BoringSSL-GRPC/Implementation (= 0.0.7) 343 | - BoringSSL-GRPC/Interface (= 0.0.7) 344 | - BoringSSL-GRPC/Implementation (0.0.7): 345 | - BoringSSL-GRPC/Interface (= 0.0.7) 346 | - BoringSSL-GRPC/Interface (0.0.7) 347 | - Firebase/Auth (8.12.1): 348 | - Firebase/CoreOnly 349 | - FirebaseAuth (~> 8.12.0) 350 | - Firebase/Core (8.12.1): 351 | - Firebase/CoreOnly 352 | - FirebaseAnalytics (~> 8.12.0) 353 | - Firebase/CoreOnly (8.12.1): 354 | - FirebaseCore (= 8.12.1) 355 | - Firebase/Firestore (8.12.1): 356 | - Firebase/CoreOnly 357 | - FirebaseFirestore (~> 8.12.1) 358 | - Firebase/Storage (8.12.1): 359 | - Firebase/CoreOnly 360 | - FirebaseStorage (~> 8.12.0) 361 | - FirebaseAnalytics (8.12.0): 362 | - FirebaseAnalytics/AdIdSupport (= 8.12.0) 363 | - FirebaseCore (~> 8.0) 364 | - FirebaseInstallations (~> 8.0) 365 | - GoogleUtilities/AppDelegateSwizzler (~> 7.7) 366 | - GoogleUtilities/MethodSwizzler (~> 7.7) 367 | - GoogleUtilities/Network (~> 7.7) 368 | - "GoogleUtilities/NSData+zlib (~> 7.7)" 369 | - nanopb (~> 2.30908.0) 370 | - FirebaseAnalytics/AdIdSupport (8.12.0): 371 | - FirebaseCore (~> 8.0) 372 | - FirebaseInstallations (~> 8.0) 373 | - GoogleAppMeasurement (= 8.12.0) 374 | - GoogleUtilities/AppDelegateSwizzler (~> 7.7) 375 | - GoogleUtilities/MethodSwizzler (~> 7.7) 376 | - GoogleUtilities/Network (~> 7.7) 377 | - "GoogleUtilities/NSData+zlib (~> 7.7)" 378 | - nanopb (~> 2.30908.0) 379 | - FirebaseAuth (8.12.0): 380 | - FirebaseCore (~> 8.0) 381 | - GoogleUtilities/AppDelegateSwizzler (~> 7.7) 382 | - GoogleUtilities/Environment (~> 7.7) 383 | - GTMSessionFetcher/Core (~> 1.5) 384 | - FirebaseCore (8.12.1): 385 | - FirebaseCoreDiagnostics (~> 8.0) 386 | - GoogleUtilities/Environment (~> 7.7) 387 | - GoogleUtilities/Logger (~> 7.7) 388 | - FirebaseCoreDiagnostics (8.12.0): 389 | - GoogleDataTransport (~> 9.1) 390 | - GoogleUtilities/Environment (~> 7.7) 391 | - GoogleUtilities/Logger (~> 7.7) 392 | - nanopb (~> 2.30908.0) 393 | - FirebaseFirestore (8.12.1): 394 | - abseil/algorithm (= 0.20200225.0) 395 | - abseil/base (= 0.20200225.0) 396 | - abseil/container/flat_hash_map (= 0.20200225.0) 397 | - abseil/memory (= 0.20200225.0) 398 | - abseil/meta (= 0.20200225.0) 399 | - abseil/strings/strings (= 0.20200225.0) 400 | - abseil/time (= 0.20200225.0) 401 | - abseil/types (= 0.20200225.0) 402 | - FirebaseCore (~> 8.0) 403 | - "gRPC-C++ (~> 1.28.0)" 404 | - leveldb-library (~> 1.22) 405 | - nanopb (~> 2.30908.0) 406 | - FirebaseInstallations (8.12.0): 407 | - FirebaseCore (~> 8.0) 408 | - GoogleUtilities/Environment (~> 7.7) 409 | - GoogleUtilities/UserDefaults (~> 7.7) 410 | - PromisesObjC (< 3.0, >= 1.2) 411 | - FirebaseStorage (8.12.0): 412 | - FirebaseCore (~> 8.0) 413 | - GTMSessionFetcher/Core (~> 1.5) 414 | - GoogleAppMeasurement (8.12.0): 415 | - GoogleAppMeasurement/AdIdSupport (= 8.12.0) 416 | - GoogleUtilities/AppDelegateSwizzler (~> 7.7) 417 | - GoogleUtilities/MethodSwizzler (~> 7.7) 418 | - GoogleUtilities/Network (~> 7.7) 419 | - "GoogleUtilities/NSData+zlib (~> 7.7)" 420 | - nanopb (~> 2.30908.0) 421 | - GoogleAppMeasurement/AdIdSupport (8.12.0): 422 | - GoogleAppMeasurement/WithoutAdIdSupport (= 8.12.0) 423 | - GoogleUtilities/AppDelegateSwizzler (~> 7.7) 424 | - GoogleUtilities/MethodSwizzler (~> 7.7) 425 | - GoogleUtilities/Network (~> 7.7) 426 | - "GoogleUtilities/NSData+zlib (~> 7.7)" 427 | - nanopb (~> 2.30908.0) 428 | - GoogleAppMeasurement/WithoutAdIdSupport (8.12.0): 429 | - GoogleUtilities/AppDelegateSwizzler (~> 7.7) 430 | - GoogleUtilities/MethodSwizzler (~> 7.7) 431 | - GoogleUtilities/Network (~> 7.7) 432 | - "GoogleUtilities/NSData+zlib (~> 7.7)" 433 | - nanopb (~> 2.30908.0) 434 | - GoogleDataTransport (9.1.2): 435 | - GoogleUtilities/Environment (~> 7.2) 436 | - nanopb (~> 2.30908.0) 437 | - PromisesObjC (< 3.0, >= 1.2) 438 | - GoogleUtilities/AppDelegateSwizzler (7.7.0): 439 | - GoogleUtilities/Environment 440 | - GoogleUtilities/Logger 441 | - GoogleUtilities/Network 442 | - GoogleUtilities/Environment (7.7.0): 443 | - PromisesObjC (< 3.0, >= 1.2) 444 | - GoogleUtilities/Logger (7.7.0): 445 | - GoogleUtilities/Environment 446 | - GoogleUtilities/MethodSwizzler (7.7.0): 447 | - GoogleUtilities/Logger 448 | - GoogleUtilities/Network (7.7.0): 449 | - GoogleUtilities/Logger 450 | - "GoogleUtilities/NSData+zlib" 451 | - GoogleUtilities/Reachability 452 | - "GoogleUtilities/NSData+zlib (7.7.0)" 453 | - GoogleUtilities/Reachability (7.7.0): 454 | - GoogleUtilities/Logger 455 | - GoogleUtilities/UserDefaults (7.7.0): 456 | - GoogleUtilities/Logger 457 | - "gRPC-C++ (1.28.2)": 458 | - "gRPC-C++/Implementation (= 1.28.2)" 459 | - "gRPC-C++/Interface (= 1.28.2)" 460 | - "gRPC-C++/Implementation (1.28.2)": 461 | - abseil/container/inlined_vector (= 0.20200225.0) 462 | - abseil/memory/memory (= 0.20200225.0) 463 | - abseil/strings/str_format (= 0.20200225.0) 464 | - abseil/strings/strings (= 0.20200225.0) 465 | - abseil/types/optional (= 0.20200225.0) 466 | - "gRPC-C++/Interface (= 1.28.2)" 467 | - gRPC-Core (= 1.28.2) 468 | - "gRPC-C++/Interface (1.28.2)" 469 | - gRPC-Core (1.28.2): 470 | - gRPC-Core/Implementation (= 1.28.2) 471 | - gRPC-Core/Interface (= 1.28.2) 472 | - gRPC-Core/Implementation (1.28.2): 473 | - abseil/container/inlined_vector (= 0.20200225.0) 474 | - abseil/memory/memory (= 0.20200225.0) 475 | - abseil/strings/str_format (= 0.20200225.0) 476 | - abseil/strings/strings (= 0.20200225.0) 477 | - abseil/types/optional (= 0.20200225.0) 478 | - BoringSSL-GRPC (= 0.0.7) 479 | - gRPC-Core/Interface (= 1.28.2) 480 | - gRPC-Core/Interface (1.28.2) 481 | - GTMSessionFetcher/Core (1.7.0) 482 | - JGProgressHUD (2.2) 483 | - leveldb-library (1.22.1) 484 | - nanopb (2.30908.0): 485 | - nanopb/decode (= 2.30908.0) 486 | - nanopb/encode (= 2.30908.0) 487 | - nanopb/decode (2.30908.0) 488 | - nanopb/encode (2.30908.0) 489 | - PromisesObjC (2.0.0) 490 | - SDWebImage (5.12.3): 491 | - SDWebImage/Core (= 5.12.3) 492 | - SDWebImage/Core (5.12.3) 493 | 494 | DEPENDENCIES: 495 | - Firebase/Auth 496 | - Firebase/Core 497 | - Firebase/Firestore 498 | - Firebase/Storage 499 | - JGProgressHUD 500 | - SDWebImage 501 | 502 | SPEC REPOS: 503 | trunk: 504 | - abseil 505 | - BoringSSL-GRPC 506 | - Firebase 507 | - FirebaseAnalytics 508 | - FirebaseAuth 509 | - FirebaseCore 510 | - FirebaseCoreDiagnostics 511 | - FirebaseFirestore 512 | - FirebaseInstallations 513 | - FirebaseStorage 514 | - GoogleAppMeasurement 515 | - GoogleDataTransport 516 | - GoogleUtilities 517 | - "gRPC-C++" 518 | - gRPC-Core 519 | - GTMSessionFetcher 520 | - JGProgressHUD 521 | - leveldb-library 522 | - nanopb 523 | - PromisesObjC 524 | - SDWebImage 525 | 526 | SPEC CHECKSUMS: 527 | abseil: 6c8eb7892aefa08d929b39f9bb108e5367e3228f 528 | BoringSSL-GRPC: 8edf627ee524575e2f8d19d56f068b448eea3879 529 | Firebase: 580d09e8edafc3073ebf09c03fd42e4d80d35fe9 530 | FirebaseAnalytics: bd10d7706ba8d6e01ea2816f8fe9e9b881cb0918 531 | FirebaseAuth: 5250d7bdba35e57cc34ec7ddc525f82b2757f2a0 532 | FirebaseCore: 8138de860a90ca7eec5e324da5788fb0ebf1d93c 533 | FirebaseCoreDiagnostics: 3b40dfadef5b90433a60ae01f01e90fe87aa76aa 534 | FirebaseFirestore: dfeb58916ee3ae0ef2453d4849a683672705920c 535 | FirebaseInstallations: 25764cf322e77f99449395870a65b2bef88e1545 536 | FirebaseStorage: 4b75458c35d8b728e4c1fc1371942997456ab299 537 | GoogleAppMeasurement: ae033c3aad67e68294369373056b4d74cc8ae0d6 538 | GoogleDataTransport: 629c20a4d363167143f30ea78320d5a7eb8bd940 539 | GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 540 | "gRPC-C++": 13d8ccef97d5c3c441b7e3c529ef28ebee86fad2 541 | gRPC-Core: 4afa11bfbedf7cdecd04de535a9e046893404ed5 542 | GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91 543 | JGProgressHUD: d83d7a981b85d11205e19ff8ad5bb9c40571c847 544 | leveldb-library: 50c7b45cbd7bf543c81a468fe557a16ae3db8729 545 | nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 546 | PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 547 | SDWebImage: 53179a2dba77246efa8a9b85f5c5b21f8f43e38f 548 | 549 | PODFILE CHECKSUM: 3311de1eea9051d4c1ec03643563d801e812fd37 550 | 551 | COCOAPODS: 1.11.3 552 | -------------------------------------------------------------------------------- /Tinder.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 36BDCE955A4632EC8CE780DE /* Pods_Tinder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BB375D3B1572CD5709FB57F /* Pods_Tinder.framework */; }; 11 | C31F11FE220A515900871FC1 /* SendMessageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31F11FD220A515900871FC1 /* SendMessageButton.swift */; }; 12 | C31F1200220A547B00871FC1 /* KeepSwipingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31F11FF220A547B00871FC1 /* KeepSwipingButton.swift */; }; 13 | C34D499521FBFF63008E8010 /* Extensions+UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34D499421FBFF63008E8010 /* Extensions+UIView.swift */; }; 14 | C34D499821FC120B008E8010 /* HomeBottomControlsStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34D499721FC120B008E8010 /* HomeBottomControlsStackView.swift */; }; 15 | C34D499A21FC175C008E8010 /* TopNavigationStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34D499921FC175C008E8010 /* TopNavigationStackView.swift */; }; 16 | C34D499C21FC2563008E8010 /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34D499B21FC2563008E8010 /* CardView.swift */; }; 17 | C34D499F21FC3921008E8010 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34D499E21FC3921008E8010 /* User.swift */; }; 18 | C34D49A321FC449A008E8010 /* CardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34D49A221FC449A008E8010 /* CardViewModel.swift */; }; 19 | C34D49A521FC51BD008E8010 /* Advertiser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C34D49A421FC51BD008E8010 /* Advertiser.swift */; }; 20 | C36CFB382206C2E300437F6F /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36CFB372206C2E300437F6F /* SettingsController.swift */; }; 21 | C36CFB3A2206D40900437F6F /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36CFB392206D40900437F6F /* SettingsCell.swift */; }; 22 | C36CFB3C2206F36100437F6F /* AgeRangeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36CFB3B2206F36100437F6F /* AgeRangeCell.swift */; }; 23 | C36CFB3E220709D100437F6F /* Firebase+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36CFB3D220709D100437F6F /* Firebase+Utils.swift */; }; 24 | C36CFB40220714D900437F6F /* LoginController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36CFB3F220714D900437F6F /* LoginController.swift */; }; 25 | C36CFB4222072AB300437F6F /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36CFB4122072AB300437F6F /* LoginViewModel.swift */; }; 26 | C379FC6A21F8A27200DD05C3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C379FC6921F8A27200DD05C3 /* AppDelegate.swift */; }; 27 | C379FC6C21F8A27200DD05C3 /* HomeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C379FC6B21F8A27200DD05C3 /* HomeController.swift */; }; 28 | C379FC6F21F8A27200DD05C3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C379FC6D21F8A27200DD05C3 /* Main.storyboard */; }; 29 | C379FC7121F8A27200DD05C3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C379FC7021F8A27200DD05C3 /* Assets.xcassets */; }; 30 | C379FC7421F8A27200DD05C3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C379FC7221F8A27200DD05C3 /* LaunchScreen.storyboard */; }; 31 | C392CE052209790D0044B10A /* PhotoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C392CE042209790D0044B10A /* PhotoController.swift */; }; 32 | C392CE072209BFD00044B10A /* MatchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C392CE062209BFD00044B10A /* MatchView.swift */; }; 33 | C396853B21FD745800E67F65 /* RegistrationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C396853A21FD745800E67F65 /* RegistrationController.swift */; }; 34 | C396853D21FD7A6C00E67F65 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C396853C21FD7A6C00E67F65 /* CustomTextField.swift */; }; 35 | C396853F21FDA67400E67F65 /* RegistrationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C396853E21FDA67400E67F65 /* RegistrationViewModel.swift */; }; 36 | C396854121FDB4C000E67F65 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C396854021FDB4C000E67F65 /* GoogleService-Info.plist */; }; 37 | C396854321FDC0F400E67F65 /* Bindable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C396854221FDC0F400E67F65 /* Bindable.swift */; }; 38 | C3A2E3C32207DD9B00D95674 /* UserDetailsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A2E3C22207DD9B00D95674 /* UserDetailsController.swift */; }; 39 | C3A2E3C52208488B00D95674 /* SwipePhotosController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A2E3C42208488B00D95674 /* SwipePhotosController.swift */; }; 40 | /* End PBXBuildFile section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 30EF9C7A485B12E94178A83C /* Pods-Tinder.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tinder.release.xcconfig"; path = "Pods/Target Support Files/Pods-Tinder/Pods-Tinder.release.xcconfig"; sourceTree = ""; }; 44 | 9BB375D3B1572CD5709FB57F /* Pods_Tinder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tinder.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | C31F11FD220A515900871FC1 /* SendMessageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageButton.swift; sourceTree = ""; }; 46 | C31F11FF220A547B00871FC1 /* KeepSwipingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeepSwipingButton.swift; sourceTree = ""; }; 47 | C34D499421FBFF63008E8010 /* Extensions+UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extensions+UIView.swift"; sourceTree = ""; }; 48 | C34D499721FC120B008E8010 /* HomeBottomControlsStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeBottomControlsStackView.swift; sourceTree = ""; }; 49 | C34D499921FC175C008E8010 /* TopNavigationStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopNavigationStackView.swift; sourceTree = ""; }; 50 | C34D499B21FC2563008E8010 /* CardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = ""; }; 51 | C34D499E21FC3921008E8010 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 52 | C34D49A221FC449A008E8010 /* CardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardViewModel.swift; sourceTree = ""; }; 53 | C34D49A421FC51BD008E8010 /* Advertiser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advertiser.swift; sourceTree = ""; }; 54 | C36CFB372206C2E300437F6F /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = ""; }; 55 | C36CFB392206D40900437F6F /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; 56 | C36CFB3B2206F36100437F6F /* AgeRangeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgeRangeCell.swift; sourceTree = ""; }; 57 | C36CFB3D220709D100437F6F /* Firebase+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Firebase+Utils.swift"; sourceTree = ""; }; 58 | C36CFB3F220714D900437F6F /* LoginController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginController.swift; sourceTree = ""; }; 59 | C36CFB4122072AB300437F6F /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; 60 | C379FC6621F8A27200DD05C3 /* Tinder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tinder.app; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | C379FC6921F8A27200DD05C3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 62 | C379FC6B21F8A27200DD05C3 /* HomeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeController.swift; sourceTree = ""; }; 63 | C379FC6E21F8A27200DD05C3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 64 | C379FC7021F8A27200DD05C3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 65 | C379FC7321F8A27200DD05C3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 66 | C379FC7521F8A27200DD05C3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | C392CE042209790D0044B10A /* PhotoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoController.swift; sourceTree = ""; }; 68 | C392CE062209BFD00044B10A /* MatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchView.swift; sourceTree = ""; }; 69 | C396853A21FD745800E67F65 /* RegistrationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationController.swift; sourceTree = ""; }; 70 | C396853C21FD7A6C00E67F65 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = ""; }; 71 | C396853E21FDA67400E67F65 /* RegistrationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationViewModel.swift; sourceTree = ""; }; 72 | C396854021FDB4C000E67F65 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 73 | C396854221FDC0F400E67F65 /* Bindable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bindable.swift; sourceTree = ""; }; 74 | C3A2E3C22207DD9B00D95674 /* UserDetailsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsController.swift; sourceTree = ""; }; 75 | C3A2E3C42208488B00D95674 /* SwipePhotosController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipePhotosController.swift; sourceTree = ""; }; 76 | F82A179476F716179C0F672E /* Pods-Tinder.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tinder.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tinder/Pods-Tinder.debug.xcconfig"; sourceTree = ""; }; 77 | /* End PBXFileReference section */ 78 | 79 | /* Begin PBXFrameworksBuildPhase section */ 80 | C379FC6321F8A27200DD05C3 /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | 36BDCE955A4632EC8CE780DE /* Pods_Tinder.framework in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | 77BD2A9B31F4F2A0D3CF2707 /* Frameworks */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 9BB375D3B1572CD5709FB57F /* Pods_Tinder.framework */, 95 | ); 96 | name = Frameworks; 97 | sourceTree = ""; 98 | }; 99 | C34D499321FBFEA1008E8010 /* Extensions */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | C34D499421FBFF63008E8010 /* Extensions+UIView.swift */, 103 | C36CFB3D220709D100437F6F /* Firebase+Utils.swift */, 104 | ); 105 | path = Extensions; 106 | sourceTree = ""; 107 | }; 108 | C34D499621FC11BA008E8010 /* Views */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | C34D499721FC120B008E8010 /* HomeBottomControlsStackView.swift */, 112 | C34D499921FC175C008E8010 /* TopNavigationStackView.swift */, 113 | C34D499B21FC2563008E8010 /* CardView.swift */, 114 | C396853C21FD7A6C00E67F65 /* CustomTextField.swift */, 115 | C36CFB392206D40900437F6F /* SettingsCell.swift */, 116 | C36CFB3B2206F36100437F6F /* AgeRangeCell.swift */, 117 | C392CE062209BFD00044B10A /* MatchView.swift */, 118 | C31F11FD220A515900871FC1 /* SendMessageButton.swift */, 119 | C31F11FF220A547B00871FC1 /* KeepSwipingButton.swift */, 120 | ); 121 | path = Views; 122 | sourceTree = ""; 123 | }; 124 | C34D499D21FC390C008E8010 /* Model */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | C34D499E21FC3921008E8010 /* User.swift */, 128 | C34D49A421FC51BD008E8010 /* Advertiser.swift */, 129 | ); 130 | path = Model; 131 | sourceTree = ""; 132 | }; 133 | C34D49A021FC42C9008E8010 /* Controllers */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | C379FC6B21F8A27200DD05C3 /* HomeController.swift */, 137 | C396853A21FD745800E67F65 /* RegistrationController.swift */, 138 | C396853E21FDA67400E67F65 /* RegistrationViewModel.swift */, 139 | C396854221FDC0F400E67F65 /* Bindable.swift */, 140 | C36CFB372206C2E300437F6F /* SettingsController.swift */, 141 | C36CFB3F220714D900437F6F /* LoginController.swift */, 142 | C36CFB4122072AB300437F6F /* LoginViewModel.swift */, 143 | C3A2E3C22207DD9B00D95674 /* UserDetailsController.swift */, 144 | C3A2E3C42208488B00D95674 /* SwipePhotosController.swift */, 145 | C392CE042209790D0044B10A /* PhotoController.swift */, 146 | ); 147 | path = Controllers; 148 | sourceTree = ""; 149 | }; 150 | C34D49A121FC4479008E8010 /* ViewModels */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | C34D49A221FC449A008E8010 /* CardViewModel.swift */, 154 | ); 155 | path = ViewModels; 156 | sourceTree = ""; 157 | }; 158 | C379FC5D21F8A27200DD05C3 = { 159 | isa = PBXGroup; 160 | children = ( 161 | C379FC6821F8A27200DD05C3 /* Tinder */, 162 | C379FC6721F8A27200DD05C3 /* Products */, 163 | D5B3F88909A258C235F62FF7 /* Pods */, 164 | 77BD2A9B31F4F2A0D3CF2707 /* Frameworks */, 165 | ); 166 | sourceTree = ""; 167 | }; 168 | C379FC6721F8A27200DD05C3 /* Products */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | C379FC6621F8A27200DD05C3 /* Tinder.app */, 172 | ); 173 | name = Products; 174 | sourceTree = ""; 175 | }; 176 | C379FC6821F8A27200DD05C3 /* Tinder */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | C34D49A121FC4479008E8010 /* ViewModels */, 180 | C34D499D21FC390C008E8010 /* Model */, 181 | C34D499621FC11BA008E8010 /* Views */, 182 | C34D49A021FC42C9008E8010 /* Controllers */, 183 | C34D499321FBFEA1008E8010 /* Extensions */, 184 | C379FC6921F8A27200DD05C3 /* AppDelegate.swift */, 185 | C379FC6D21F8A27200DD05C3 /* Main.storyboard */, 186 | C379FC7021F8A27200DD05C3 /* Assets.xcassets */, 187 | C379FC7221F8A27200DD05C3 /* LaunchScreen.storyboard */, 188 | C396854021FDB4C000E67F65 /* GoogleService-Info.plist */, 189 | C379FC7521F8A27200DD05C3 /* Info.plist */, 190 | ); 191 | path = Tinder; 192 | sourceTree = ""; 193 | }; 194 | D5B3F88909A258C235F62FF7 /* Pods */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | F82A179476F716179C0F672E /* Pods-Tinder.debug.xcconfig */, 198 | 30EF9C7A485B12E94178A83C /* Pods-Tinder.release.xcconfig */, 199 | ); 200 | name = Pods; 201 | sourceTree = ""; 202 | }; 203 | /* End PBXGroup section */ 204 | 205 | /* Begin PBXNativeTarget section */ 206 | C379FC6521F8A27200DD05C3 /* Tinder */ = { 207 | isa = PBXNativeTarget; 208 | buildConfigurationList = C379FC7821F8A27200DD05C3 /* Build configuration list for PBXNativeTarget "Tinder" */; 209 | buildPhases = ( 210 | 18CED566EAA30DB2EB8B94B8 /* [CP] Check Pods Manifest.lock */, 211 | C379FC6221F8A27200DD05C3 /* Sources */, 212 | C379FC6321F8A27200DD05C3 /* Frameworks */, 213 | C379FC6421F8A27200DD05C3 /* Resources */, 214 | 7EE0685656C9E141299AE5CD /* [CP] Embed Pods Frameworks */, 215 | ); 216 | buildRules = ( 217 | ); 218 | dependencies = ( 219 | ); 220 | name = Tinder; 221 | productName = Tinder; 222 | productReference = C379FC6621F8A27200DD05C3 /* Tinder.app */; 223 | productType = "com.apple.product-type.application"; 224 | }; 225 | /* End PBXNativeTarget section */ 226 | 227 | /* Begin PBXProject section */ 228 | C379FC5E21F8A27200DD05C3 /* Project object */ = { 229 | isa = PBXProject; 230 | attributes = { 231 | LastSwiftUpdateCheck = 1010; 232 | LastUpgradeCheck = 1340; 233 | ORGANIZATIONNAME = BekzodRakhmatov; 234 | TargetAttributes = { 235 | C379FC6521F8A27200DD05C3 = { 236 | CreatedOnToolsVersion = 10.1; 237 | LastSwiftMigration = 1340; 238 | }; 239 | }; 240 | }; 241 | buildConfigurationList = C379FC6121F8A27200DD05C3 /* Build configuration list for PBXProject "Tinder" */; 242 | compatibilityVersion = "Xcode 9.3"; 243 | developmentRegion = en; 244 | hasScannedForEncodings = 0; 245 | knownRegions = ( 246 | en, 247 | Base, 248 | ); 249 | mainGroup = C379FC5D21F8A27200DD05C3; 250 | productRefGroup = C379FC6721F8A27200DD05C3 /* Products */; 251 | projectDirPath = ""; 252 | projectRoot = ""; 253 | targets = ( 254 | C379FC6521F8A27200DD05C3 /* Tinder */, 255 | ); 256 | }; 257 | /* End PBXProject section */ 258 | 259 | /* Begin PBXResourcesBuildPhase section */ 260 | C379FC6421F8A27200DD05C3 /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | C379FC7421F8A27200DD05C3 /* LaunchScreen.storyboard in Resources */, 265 | C396854121FDB4C000E67F65 /* GoogleService-Info.plist in Resources */, 266 | C379FC7121F8A27200DD05C3 /* Assets.xcassets in Resources */, 267 | C379FC6F21F8A27200DD05C3 /* Main.storyboard in Resources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | /* End PBXResourcesBuildPhase section */ 272 | 273 | /* Begin PBXShellScriptBuildPhase section */ 274 | 18CED566EAA30DB2EB8B94B8 /* [CP] Check Pods Manifest.lock */ = { 275 | isa = PBXShellScriptBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | ); 279 | inputPaths = ( 280 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 281 | "${PODS_ROOT}/Manifest.lock", 282 | ); 283 | name = "[CP] Check Pods Manifest.lock"; 284 | outputPaths = ( 285 | "$(DERIVED_FILE_DIR)/Pods-Tinder-checkManifestLockResult.txt", 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | shellPath = /bin/sh; 289 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 290 | showEnvVarsInLog = 0; 291 | }; 292 | 7EE0685656C9E141299AE5CD /* [CP] Embed Pods Frameworks */ = { 293 | isa = PBXShellScriptBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | ); 297 | inputFileListPaths = ( 298 | "${PODS_ROOT}/Target Support Files/Pods-Tinder/Pods-Tinder-frameworks-${CONFIGURATION}-input-files.xcfilelist", 299 | ); 300 | name = "[CP] Embed Pods Frameworks"; 301 | outputFileListPaths = ( 302 | "${PODS_ROOT}/Target Support Files/Pods-Tinder/Pods-Tinder-frameworks-${CONFIGURATION}-output-files.xcfilelist", 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | shellPath = /bin/sh; 306 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Tinder/Pods-Tinder-frameworks.sh\"\n"; 307 | showEnvVarsInLog = 0; 308 | }; 309 | /* End PBXShellScriptBuildPhase section */ 310 | 311 | /* Begin PBXSourcesBuildPhase section */ 312 | C379FC6221F8A27200DD05C3 /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | C396854321FDC0F400E67F65 /* Bindable.swift in Sources */, 317 | C379FC6C21F8A27200DD05C3 /* HomeController.swift in Sources */, 318 | C396853F21FDA67400E67F65 /* RegistrationViewModel.swift in Sources */, 319 | C396853D21FD7A6C00E67F65 /* CustomTextField.swift in Sources */, 320 | C34D499821FC120B008E8010 /* HomeBottomControlsStackView.swift in Sources */, 321 | C34D49A521FC51BD008E8010 /* Advertiser.swift in Sources */, 322 | C379FC6A21F8A27200DD05C3 /* AppDelegate.swift in Sources */, 323 | C3A2E3C32207DD9B00D95674 /* UserDetailsController.swift in Sources */, 324 | C31F1200220A547B00871FC1 /* KeepSwipingButton.swift in Sources */, 325 | C392CE072209BFD00044B10A /* MatchView.swift in Sources */, 326 | C36CFB3C2206F36100437F6F /* AgeRangeCell.swift in Sources */, 327 | C34D499521FBFF63008E8010 /* Extensions+UIView.swift in Sources */, 328 | C36CFB3E220709D100437F6F /* Firebase+Utils.swift in Sources */, 329 | C34D49A321FC449A008E8010 /* CardViewModel.swift in Sources */, 330 | C34D499C21FC2563008E8010 /* CardView.swift in Sources */, 331 | C36CFB382206C2E300437F6F /* SettingsController.swift in Sources */, 332 | C36CFB40220714D900437F6F /* LoginController.swift in Sources */, 333 | C34D499A21FC175C008E8010 /* TopNavigationStackView.swift in Sources */, 334 | C392CE052209790D0044B10A /* PhotoController.swift in Sources */, 335 | C396853B21FD745800E67F65 /* RegistrationController.swift in Sources */, 336 | C34D499F21FC3921008E8010 /* User.swift in Sources */, 337 | C3A2E3C52208488B00D95674 /* SwipePhotosController.swift in Sources */, 338 | C31F11FE220A515900871FC1 /* SendMessageButton.swift in Sources */, 339 | C36CFB4222072AB300437F6F /* LoginViewModel.swift in Sources */, 340 | C36CFB3A2206D40900437F6F /* SettingsCell.swift in Sources */, 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | }; 344 | /* End PBXSourcesBuildPhase section */ 345 | 346 | /* Begin PBXVariantGroup section */ 347 | C379FC6D21F8A27200DD05C3 /* Main.storyboard */ = { 348 | isa = PBXVariantGroup; 349 | children = ( 350 | C379FC6E21F8A27200DD05C3 /* Base */, 351 | ); 352 | name = Main.storyboard; 353 | sourceTree = ""; 354 | }; 355 | C379FC7221F8A27200DD05C3 /* LaunchScreen.storyboard */ = { 356 | isa = PBXVariantGroup; 357 | children = ( 358 | C379FC7321F8A27200DD05C3 /* Base */, 359 | ); 360 | name = LaunchScreen.storyboard; 361 | sourceTree = ""; 362 | }; 363 | /* End PBXVariantGroup section */ 364 | 365 | /* Begin XCBuildConfiguration section */ 366 | C379FC7621F8A27200DD05C3 /* Debug */ = { 367 | isa = XCBuildConfiguration; 368 | buildSettings = { 369 | ALWAYS_SEARCH_USER_PATHS = NO; 370 | CLANG_ANALYZER_NONNULL = YES; 371 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 372 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 373 | CLANG_CXX_LIBRARY = "libc++"; 374 | CLANG_ENABLE_MODULES = YES; 375 | CLANG_ENABLE_OBJC_ARC = YES; 376 | CLANG_ENABLE_OBJC_WEAK = YES; 377 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 378 | CLANG_WARN_BOOL_CONVERSION = YES; 379 | CLANG_WARN_COMMA = YES; 380 | CLANG_WARN_CONSTANT_CONVERSION = YES; 381 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 382 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 383 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 384 | CLANG_WARN_EMPTY_BODY = YES; 385 | CLANG_WARN_ENUM_CONVERSION = YES; 386 | CLANG_WARN_INFINITE_RECURSION = YES; 387 | CLANG_WARN_INT_CONVERSION = YES; 388 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 390 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 391 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 392 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 393 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 394 | CLANG_WARN_STRICT_PROTOTYPES = YES; 395 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 396 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 397 | CLANG_WARN_UNREACHABLE_CODE = YES; 398 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 399 | CODE_SIGN_IDENTITY = "iPhone Developer"; 400 | COPY_PHASE_STRIP = NO; 401 | DEBUG_INFORMATION_FORMAT = dwarf; 402 | ENABLE_STRICT_OBJC_MSGSEND = YES; 403 | ENABLE_TESTABILITY = YES; 404 | GCC_C_LANGUAGE_STANDARD = gnu11; 405 | GCC_DYNAMIC_NO_PIC = NO; 406 | GCC_NO_COMMON_BLOCKS = YES; 407 | GCC_OPTIMIZATION_LEVEL = 0; 408 | GCC_PREPROCESSOR_DEFINITIONS = ( 409 | "DEBUG=1", 410 | "$(inherited)", 411 | ); 412 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 413 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 414 | GCC_WARN_UNDECLARED_SELECTOR = YES; 415 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 416 | GCC_WARN_UNUSED_FUNCTION = YES; 417 | GCC_WARN_UNUSED_VARIABLE = YES; 418 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 419 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 420 | MTL_FAST_MATH = YES; 421 | ONLY_ACTIVE_ARCH = YES; 422 | SDKROOT = iphoneos; 423 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 424 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 425 | }; 426 | name = Debug; 427 | }; 428 | C379FC7721F8A27200DD05C3 /* Release */ = { 429 | isa = XCBuildConfiguration; 430 | buildSettings = { 431 | ALWAYS_SEARCH_USER_PATHS = NO; 432 | CLANG_ANALYZER_NONNULL = YES; 433 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 434 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 435 | CLANG_CXX_LIBRARY = "libc++"; 436 | CLANG_ENABLE_MODULES = YES; 437 | CLANG_ENABLE_OBJC_ARC = YES; 438 | CLANG_ENABLE_OBJC_WEAK = YES; 439 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 440 | CLANG_WARN_BOOL_CONVERSION = YES; 441 | CLANG_WARN_COMMA = YES; 442 | CLANG_WARN_CONSTANT_CONVERSION = YES; 443 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 444 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 445 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 446 | CLANG_WARN_EMPTY_BODY = YES; 447 | CLANG_WARN_ENUM_CONVERSION = YES; 448 | CLANG_WARN_INFINITE_RECURSION = YES; 449 | CLANG_WARN_INT_CONVERSION = YES; 450 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 451 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 452 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 453 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 454 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 455 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 456 | CLANG_WARN_STRICT_PROTOTYPES = YES; 457 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 458 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 459 | CLANG_WARN_UNREACHABLE_CODE = YES; 460 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 461 | CODE_SIGN_IDENTITY = "iPhone Developer"; 462 | COPY_PHASE_STRIP = NO; 463 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 464 | ENABLE_NS_ASSERTIONS = NO; 465 | ENABLE_STRICT_OBJC_MSGSEND = YES; 466 | GCC_C_LANGUAGE_STANDARD = gnu11; 467 | GCC_NO_COMMON_BLOCKS = YES; 468 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 469 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 470 | GCC_WARN_UNDECLARED_SELECTOR = YES; 471 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 472 | GCC_WARN_UNUSED_FUNCTION = YES; 473 | GCC_WARN_UNUSED_VARIABLE = YES; 474 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 475 | MTL_ENABLE_DEBUG_INFO = NO; 476 | MTL_FAST_MATH = YES; 477 | SDKROOT = iphoneos; 478 | SWIFT_COMPILATION_MODE = wholemodule; 479 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 480 | VALIDATE_PRODUCT = YES; 481 | }; 482 | name = Release; 483 | }; 484 | C379FC7921F8A27200DD05C3 /* Debug */ = { 485 | isa = XCBuildConfiguration; 486 | baseConfigurationReference = F82A179476F716179C0F672E /* Pods-Tinder.debug.xcconfig */; 487 | buildSettings = { 488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 489 | CODE_SIGN_STYLE = Automatic; 490 | DEVELOPMENT_TEAM = ""; 491 | INFOPLIST_FILE = Tinder/Info.plist; 492 | LD_RUNPATH_SEARCH_PATHS = ( 493 | "$(inherited)", 494 | "@executable_path/Frameworks", 495 | ); 496 | PRODUCT_BUNDLE_IDENTIFIER = com.bekzodrakhmatov.Tinder; 497 | PRODUCT_NAME = "$(TARGET_NAME)"; 498 | SWIFT_VERSION = 5.0; 499 | TARGETED_DEVICE_FAMILY = "1,2"; 500 | }; 501 | name = Debug; 502 | }; 503 | C379FC7A21F8A27200DD05C3 /* Release */ = { 504 | isa = XCBuildConfiguration; 505 | baseConfigurationReference = 30EF9C7A485B12E94178A83C /* Pods-Tinder.release.xcconfig */; 506 | buildSettings = { 507 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 508 | CODE_SIGN_STYLE = Automatic; 509 | DEVELOPMENT_TEAM = ""; 510 | INFOPLIST_FILE = Tinder/Info.plist; 511 | LD_RUNPATH_SEARCH_PATHS = ( 512 | "$(inherited)", 513 | "@executable_path/Frameworks", 514 | ); 515 | PRODUCT_BUNDLE_IDENTIFIER = com.bekzodrakhmatov.Tinder; 516 | PRODUCT_NAME = "$(TARGET_NAME)"; 517 | SWIFT_VERSION = 5.0; 518 | TARGETED_DEVICE_FAMILY = "1,2"; 519 | }; 520 | name = Release; 521 | }; 522 | /* End XCBuildConfiguration section */ 523 | 524 | /* Begin XCConfigurationList section */ 525 | C379FC6121F8A27200DD05C3 /* Build configuration list for PBXProject "Tinder" */ = { 526 | isa = XCConfigurationList; 527 | buildConfigurations = ( 528 | C379FC7621F8A27200DD05C3 /* Debug */, 529 | C379FC7721F8A27200DD05C3 /* Release */, 530 | ); 531 | defaultConfigurationIsVisible = 0; 532 | defaultConfigurationName = Release; 533 | }; 534 | C379FC7821F8A27200DD05C3 /* Build configuration list for PBXNativeTarget "Tinder" */ = { 535 | isa = XCConfigurationList; 536 | buildConfigurations = ( 537 | C379FC7921F8A27200DD05C3 /* Debug */, 538 | C379FC7A21F8A27200DD05C3 /* Release */, 539 | ); 540 | defaultConfigurationIsVisible = 0; 541 | defaultConfigurationName = Release; 542 | }; 543 | /* End XCConfigurationList section */ 544 | }; 545 | rootObject = C379FC5E21F8A27200DD05C3 /* Project object */; 546 | } 547 | --------------------------------------------------------------------------------