├── .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 | 
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 |
--------------------------------------------------------------------------------