├── .gitignore
├── FoodDeliveryApp.xcodeproj
├── project.pbxproj
└── xcshareddata
│ └── xcschemes
│ └── FoodDeliveryApp.xcscheme
├── FoodDeliveryApp
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── logo-1024-v2.png
│ │ ├── logo-120-v2.png
│ │ └── logo-180-v2.png
│ ├── Authentication
│ │ ├── Contents.json
│ │ └── Take_Away.imageset
│ │ │ ├── Contents.json
│ │ │ └── Take Away-rafiki.png
│ ├── Contents.json
│ ├── Cutomer
│ │ ├── Contents.json
│ │ ├── Menu
│ │ │ ├── Contents.json
│ │ │ └── jennifer-burk.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── jennifer-burk.jpg
│ │ ├── Profile
│ │ │ ├── Contents.json
│ │ │ ├── ali-jouyandeh-bodgc6H44FA-unsplash.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── ali-jouyandeh-bodgc6H44FA-unsplash.jpg
│ │ │ ├── help-web-button.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── help-web-button.png
│ │ │ ├── saved.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── saved.png
│ │ │ └── soda.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── soda.png
│ │ └── Restaurants
│ │ │ ├── Contents.json
│ │ │ ├── brooke-lark--F_5g8EEHYE-unsplash.imageset
│ │ │ ├── Contents.json
│ │ │ └── brooke-lark--F_5g8EEHYE-unsplash.jpg
│ │ │ └── davide-cantelli-jpkfc5_d-DI-unsplash.imageset
│ │ │ ├── Contents.json
│ │ │ └── davide-cantelli-jpkfc5_d-DI-unsplash.jpg
│ ├── Driver
│ │ ├── Contents.json
│ │ └── Profile
│ │ │ ├── Contents.json
│ │ │ ├── delivered.imageset
│ │ │ ├── Contents.json
│ │ │ └── delivered.png
│ │ │ ├── dollar copy.imageset
│ │ │ ├── Contents.json
│ │ │ └── dollar copy.png
│ │ │ ├── dollar.imageset
│ │ │ ├── Contents.json
│ │ │ └── dollar.png
│ │ │ ├── growth-graph.imageset
│ │ │ ├── Contents.json
│ │ │ └── growth-graph.png
│ │ │ └── take-away.imageset
│ │ │ ├── Contents.json
│ │ │ └── take-away.png
│ ├── Menu Icons
│ │ ├── Contents.json
│ │ ├── account.imageset
│ │ │ ├── Contents.json
│ │ │ └── account.png
│ │ ├── accountWhiteBg.imageset
│ │ │ ├── Contents.json
│ │ │ └── accountWhiteBg.png
│ │ ├── clock (1).imageset
│ │ │ ├── Contents.json
│ │ │ └── clock (1).png
│ │ ├── clock.imageset
│ │ │ ├── Contents.json
│ │ │ └── clock.png
│ │ ├── food-delivery.imageset
│ │ │ ├── Contents.json
│ │ │ └── food-delivery.png
│ │ ├── googlemap.imageset
│ │ │ ├── Contents.json
│ │ │ └── googlemap.jpeg
│ │ ├── home.imageset
│ │ │ ├── Contents.json
│ │ │ └── home.png
│ │ ├── homeWhiteBG.imageset
│ │ │ ├── Contents.json
│ │ │ └── homeWhiteBG.png
│ │ ├── maps-and-flags.imageset
│ │ │ ├── Contents.json
│ │ │ └── maps-and-flags.png
│ │ ├── mapsWhiteBg.imageset
│ │ │ ├── Contents.json
│ │ │ └── mapsWhiteBg.png
│ │ ├── shopping-bag.imageset
│ │ │ ├── Contents.json
│ │ │ └── shopping-bag.png
│ │ ├── shoppingWhiteBg.imageset
│ │ │ ├── Contents.json
│ │ │ └── shoppingWhiteBg.png
│ │ └── visa.imageset
│ │ │ ├── Contents.json
│ │ │ └── visa.png
│ ├── RestaurantImages
│ │ ├── Contents.json
│ │ ├── img01.imageset
│ │ │ ├── Contents.json
│ │ │ └── img01.jpg
│ │ └── img01.jpg
│ ├── Top Icons
│ │ ├── Contents.json
│ │ ├── left.imageset
│ │ │ ├── Contents.json
│ │ │ └── left.png
│ │ └── shopping-cart.imageset
│ │ │ ├── Contents.json
│ │ │ └── shopping-cart.png
│ └── logo-120.imageset
│ │ ├── Contents.json
│ │ └── logo-120.png
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Controllers
│ ├── Customer
│ │ ├── CartViewController.swift
│ │ ├── CustomerMenuViewController.swift
│ │ ├── MealDetailViewController.swift
│ │ ├── OrderViewController.swift
│ │ ├── PaymentViewController.swift
│ │ ├── ProfileViewController.swift
│ │ └── RestaurantViewController.swift
│ ├── Driver
│ │ ├── DeliveryViewController.swift
│ │ ├── DriversOrderViewController.swift
│ │ ├── DriversProfileViewController.swift
│ │ ├── RevenueViewController.swift
│ │ └── RevenueViewController.swift.orig
│ └── LoginViewController.swift
├── Info.plist
├── Manager
│ ├── APIConstants.swift
│ ├── APIManager.swift
│ ├── Constants.swift
│ ├── FBManager.swift
│ ├── Helper.swift
│ └── StringConstants.swift
├── Model
│ ├── Customer
│ │ ├── Cart.swift
│ │ ├── Meal.swift
│ │ ├── Restaurant.swift
│ │ └── User.swift
│ └── Driver
│ │ ├── DriverOrder.swift
│ │ └── Revenue.swift
├── SceneDelegate.swift
├── Utilities
│ ├── FloatingTabBarController.swift
│ └── UIHelper.swift
└── View
│ ├── Customer
│ ├── CartCell.swift
│ ├── MenuCell.swift
│ ├── OrderCell.swift
│ └── RestaurantCell.swift
│ └── Driver
│ ├── DriverRevenueCell.swift
│ └── DriversOrderCell.swift
├── Podfile
├── Podfile.lock
├── README.MD
└── Submission
├── Unit 12 - Build Sprint 3.gif
├── Unit 13 - Final Build Sprint.gif
├── break.png
├── flowchart.PNG
├── gif1-1.gif
├── gif1-2.gif
└── gif2-1.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/macos,swift
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,swift
3 |
4 | ### macOS ###
5 | # General
6 | .DS_Store
7 | .AppleDouble
8 | .LSOverride
9 |
10 | # Icon must end with two \r
11 | Icon
12 |
13 |
14 | # Thumbnails
15 | ._*
16 |
17 | # Files that might appear in the root of a volume
18 | .DocumentRevisions-V100
19 | .fseventsd
20 | .Spotlight-V100
21 | .TemporaryItems
22 | .Trashes
23 | .VolumeIcon.icns
24 | .com.apple.timemachine.donotpresent
25 |
26 | # Directories potentially created on remote AFP share
27 | .AppleDB
28 | .AppleDesktop
29 | Network Trash Folder
30 | Temporary Items
31 | .apdisk
32 |
33 | ### Swift ###
34 | # Xcode
35 | #
36 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
37 |
38 | ## User settings
39 | xcuserdata/
40 |
41 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
42 | *.xcscmblueprint
43 | *.xccheckout
44 |
45 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
46 | build/
47 | DerivedData/
48 | *.moved-aside
49 | *.pbxuser
50 | !default.pbxuser
51 | *.mode1v3
52 | !default.mode1v3
53 | *.mode2v3
54 | !default.mode2v3
55 | *.perspectivev3
56 | !default.perspectivev3
57 |
58 | ## Obj-C/Swift specific
59 | *.hmap
60 |
61 | ## App packaging
62 | *.ipa
63 | *.dSYM.zip
64 | *.dSYM
65 |
66 | ## Playgrounds
67 | timeline.xctimeline
68 | playground.xcworkspace
69 |
70 | # Swift Package Manager
71 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
72 | # Packages/
73 | # Package.pins
74 | # Package.resolved
75 | # *.xcodeproj
76 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
77 | # hence it is not needed unless you have added a package configuration file to your project
78 | # .swiftpm
79 |
80 | .build/
81 |
82 | # CocoaPods
83 | # We recommend against adding the Pods directory to your .gitignore. However
84 | # you should judge for yourself, the pros and cons are mentioned at:
85 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
86 | # Add this line if you want to avoid checking in source code from the Xcode workspace
87 | *.xcworkspace
88 | Pods/
89 |
90 |
91 |
92 | # Carthage
93 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
94 | # Carthage/Checkouts
95 |
96 | Carthage/Build/
97 |
98 | # Accio dependency management
99 | Dependencies/
100 | .accio/
101 |
102 | # fastlane
103 | # It is recommended to not store the screenshots in the git repo.
104 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
105 | # For more information about the recommended setup visit:
106 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
107 |
108 | fastlane/report.xml
109 | fastlane/Preview.html
110 | fastlane/screenshots/**/*.png
111 | fastlane/test_output
112 |
113 | # Code Injection
114 | # After new code Injection tools there's a generated folder /iOSInjectionProject
115 | # https://github.com/johnno1962/injectionforxcode
116 |
117 | iOSInjectionProject/
118 |
119 | # End of https://www.toptal.com/developers/gitignore/api/macos,swift
120 |
--------------------------------------------------------------------------------
/FoodDeliveryApp.xcodeproj/xcshareddata/xcschemes/FoodDeliveryApp.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
67 |
69 |
75 |
76 |
77 |
78 |
80 |
81 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import UIKit
9 | import FBSDKCoreKit
10 |
11 | //
12 | import Stripe
13 |
14 | @main
15 | class AppDelegate: UIResponder, UIApplicationDelegate {
16 |
17 | var window: UIWindow?
18 |
19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
20 | // Override point for customization after application launch.
21 | //Adding Stripe
22 | STPAPIClient.shared.publishableKey = APIConstants.Stripe.PKEY
23 |
24 |
25 | //Facebook Configureation
26 | return ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
27 | //return true
28 | }
29 |
30 |
31 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
32 | return ApplicationDelegate.shared.application(
33 | app,
34 | open: url,
35 | sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
36 | annotation: nil)
37 | }
38 | //
39 | func applicationDidBecomeActive(_ application: UIApplication) {
40 | AppEvents.shared.activateApp()
41 | }
42 |
43 | // MARK: UISceneSession Lifecycle
44 |
45 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
46 | // Called when a new scene session is being created.
47 | // Use this method to select a configuration to create the new scene with.
48 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
49 | }
50 |
51 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
52 | // Called when the user discards a scene session.
53 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
54 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
55 | }
56 |
57 |
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "logo-120-v2.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "60x60"
38 | },
39 | {
40 | "filename" : "logo-180-v2.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "idiom" : "ipad",
47 | "scale" : "1x",
48 | "size" : "20x20"
49 | },
50 | {
51 | "idiom" : "ipad",
52 | "scale" : "2x",
53 | "size" : "20x20"
54 | },
55 | {
56 | "idiom" : "ipad",
57 | "scale" : "1x",
58 | "size" : "29x29"
59 | },
60 | {
61 | "idiom" : "ipad",
62 | "scale" : "2x",
63 | "size" : "29x29"
64 | },
65 | {
66 | "idiom" : "ipad",
67 | "scale" : "1x",
68 | "size" : "40x40"
69 | },
70 | {
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "40x40"
74 | },
75 | {
76 | "idiom" : "ipad",
77 | "scale" : "1x",
78 | "size" : "76x76"
79 | },
80 | {
81 | "idiom" : "ipad",
82 | "scale" : "2x",
83 | "size" : "76x76"
84 | },
85 | {
86 | "idiom" : "ipad",
87 | "scale" : "2x",
88 | "size" : "83.5x83.5"
89 | },
90 | {
91 | "filename" : "logo-1024-v2.png",
92 | "idiom" : "ios-marketing",
93 | "scale" : "1x",
94 | "size" : "1024x1024"
95 | }
96 | ],
97 | "info" : {
98 | "author" : "xcode",
99 | "version" : 1
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/AppIcon.appiconset/logo-1024-v2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/AppIcon.appiconset/logo-1024-v2.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/AppIcon.appiconset/logo-120-v2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/AppIcon.appiconset/logo-120-v2.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/AppIcon.appiconset/logo-180-v2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/AppIcon.appiconset/logo-180-v2.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Authentication/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Authentication/Take_Away.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Take Away-rafiki.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Authentication/Take_Away.imageset/Take Away-rafiki.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Authentication/Take_Away.imageset/Take Away-rafiki.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Menu/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Menu/jennifer-burk.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "jennifer-burk.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Menu/jennifer-burk.imageset/jennifer-burk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Cutomer/Menu/jennifer-burk.imageset/jennifer-burk.jpg
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/ali-jouyandeh-bodgc6H44FA-unsplash.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ali-jouyandeh-bodgc6H44FA-unsplash.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/ali-jouyandeh-bodgc6H44FA-unsplash.imageset/ali-jouyandeh-bodgc6H44FA-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/ali-jouyandeh-bodgc6H44FA-unsplash.imageset/ali-jouyandeh-bodgc6H44FA-unsplash.jpg
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/help-web-button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "help-web-button.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/help-web-button.imageset/help-web-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/help-web-button.imageset/help-web-button.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/saved.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "saved.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/saved.imageset/saved.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/saved.imageset/saved.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/soda.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "soda.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/soda.imageset/soda.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Cutomer/Profile/soda.imageset/soda.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Restaurants/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Restaurants/brooke-lark--F_5g8EEHYE-unsplash.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "brooke-lark--F_5g8EEHYE-unsplash.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Restaurants/brooke-lark--F_5g8EEHYE-unsplash.imageset/brooke-lark--F_5g8EEHYE-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Cutomer/Restaurants/brooke-lark--F_5g8EEHYE-unsplash.imageset/brooke-lark--F_5g8EEHYE-unsplash.jpg
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Restaurants/davide-cantelli-jpkfc5_d-DI-unsplash.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "davide-cantelli-jpkfc5_d-DI-unsplash.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Cutomer/Restaurants/davide-cantelli-jpkfc5_d-DI-unsplash.imageset/davide-cantelli-jpkfc5_d-DI-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Cutomer/Restaurants/davide-cantelli-jpkfc5_d-DI-unsplash.imageset/davide-cantelli-jpkfc5_d-DI-unsplash.jpg
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/delivered.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "delivered.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/delivered.imageset/delivered.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Driver/Profile/delivered.imageset/delivered.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/dollar copy.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "dollar copy.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/dollar copy.imageset/dollar copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Driver/Profile/dollar copy.imageset/dollar copy.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/dollar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "dollar.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/dollar.imageset/dollar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Driver/Profile/dollar.imageset/dollar.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/growth-graph.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "growth-graph.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/growth-graph.imageset/growth-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Driver/Profile/growth-graph.imageset/growth-graph.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/take-away.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "take-away.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Driver/Profile/take-away.imageset/take-away.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Driver/Profile/take-away.imageset/take-away.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/account.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "account.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/account.imageset/account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/account.imageset/account.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/accountWhiteBg.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "accountWhiteBg.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/accountWhiteBg.imageset/accountWhiteBg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/accountWhiteBg.imageset/accountWhiteBg.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/clock (1).imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "clock (1).png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/clock (1).imageset/clock (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/clock (1).imageset/clock (1).png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/clock.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "clock.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/clock.imageset/clock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/clock.imageset/clock.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/food-delivery.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "food-delivery.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/food-delivery.imageset/food-delivery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/food-delivery.imageset/food-delivery.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/googlemap.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "googlemap.jpeg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/googlemap.imageset/googlemap.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/googlemap.imageset/googlemap.jpeg
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/home.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "home.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/home.imageset/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/home.imageset/home.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/homeWhiteBG.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "homeWhiteBG.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/homeWhiteBG.imageset/homeWhiteBG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/homeWhiteBG.imageset/homeWhiteBG.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/maps-and-flags.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "maps-and-flags.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/maps-and-flags.imageset/maps-and-flags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/maps-and-flags.imageset/maps-and-flags.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/mapsWhiteBg.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "mapsWhiteBg.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/mapsWhiteBg.imageset/mapsWhiteBg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/mapsWhiteBg.imageset/mapsWhiteBg.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/shopping-bag.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "shopping-bag.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/shopping-bag.imageset/shopping-bag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/shopping-bag.imageset/shopping-bag.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/shoppingWhiteBg.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "shoppingWhiteBg.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/shoppingWhiteBg.imageset/shoppingWhiteBg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/shoppingWhiteBg.imageset/shoppingWhiteBg.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/visa.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "visa.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Menu Icons/visa.imageset/visa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Menu Icons/visa.imageset/visa.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/RestaurantImages/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/RestaurantImages/img01.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "img01.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/RestaurantImages/img01.imageset/img01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/RestaurantImages/img01.imageset/img01.jpg
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/RestaurantImages/img01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/RestaurantImages/img01.jpg
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Top Icons/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Top Icons/left.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "left.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Top Icons/left.imageset/left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Top Icons/left.imageset/left.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Top Icons/shopping-cart.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "shopping-cart.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/Top Icons/shopping-cart.imageset/shopping-cart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/Top Icons/shopping-cart.imageset/shopping-cart.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/logo-120.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "logo-120.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Assets.xcassets/logo-120.imageset/logo-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/FoodDeliveryApp/Assets.xcassets/logo-120.imageset/logo-120.png
--------------------------------------------------------------------------------
/FoodDeliveryApp/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Customer/CartViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CartViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 | import MapKit
10 | import CoreLocation
11 |
12 | class CartViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, CLLocationManagerDelegate, UITextFieldDelegate {
13 |
14 |
15 | // just added
16 | var meals = [Meal]()
17 |
18 |
19 | //TableView as tbvCart
20 | @IBOutlet weak var tbvCart: UITableView!
21 |
22 | //Cart views
23 | @IBOutlet weak var viewTotal: UIView!
24 | @IBOutlet weak var viewAddress: UIView!
25 | //@IBOutlet weak var viewMap: UIView!
26 | @IBOutlet weak var viewPayment: UIView!
27 |
28 | //Cart labels
29 | @IBOutlet weak var labelTotal: UILabel!
30 | @IBOutlet weak var labelAddress: UITextField!
31 | @IBOutlet weak var labelMap: MKMapView!
32 | @IBOutlet weak var paymentButton: UIButton!
33 |
34 | let emptyCart = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
35 |
36 | //Location
37 | //var locationManager: CLLocationManager!
38 |
39 | override func viewDidAppear(_ animated: Bool) {
40 | loadmeals()
41 | }
42 |
43 | override func viewDidDisappear(_ animated: Bool) {
44 | }
45 |
46 | override func viewDidLoad() {
47 | super.viewDidLoad()
48 | loadmeals()
49 |
50 | self.title = "My Order"
51 | tbvCart.dataSource = self
52 | tbvCart.delegate = self
53 |
54 | paymentButton.layer.cornerRadius = paymentButton.bounds.height/2
55 |
56 | // Do any additional setup after loading the view.
57 | }
58 |
59 | func loadmeals() {
60 | // Empty cart / Items in cart logic
61 | if Cart.currentCart.items.count == 0 {
62 | //empty cart
63 | emptyCart.text = "Your tray is empty. Please select meal."
64 | emptyCart.sizeToFit()
65 | emptyCart.center = self.view.center
66 | emptyCart.textAlignment = NSTextAlignment.center
67 | self.view.addSubview(emptyCart)
68 | } else {
69 | emptyCart.isHidden = true
70 | self.tbvCart.isHidden = false
71 | self.viewTotal.isHidden = false
72 | self.viewAddress.isHidden = false
73 | //self.viewMap.isHidden = false
74 | self.viewPayment.isHidden = false
75 | //self.labelAddress.text = "74-01 Queens Blvd, Queens, NY 11373"
76 | // self.labelAddress.text = "55-01 37th Ave, Queens, NY 11377"
77 | // self.labelAddress.text = "123 Placer Holder ave."
78 |
79 | self.tbvCart.reloadData()
80 | //self.labelTotal.text = "$\(Cart.currentCart.getTotal())0"
81 | self.labelTotal.text = (String(format: "$%.2f", Cart.currentCart.getTotal()))
82 |
83 | }
84 | //Show Current Location
85 | // if CLLocationManager.locationServicesEnabled(){
86 | // locationManager = CLLocationManager()
87 | // locationManager.delegate = self
88 | // locationManager.desiredAccuracy = kCLLocationAccuracyBest
89 | // locationManager.requestAlwaysAuthorization()
90 | // locationManager.startUpdatingLocation()
91 | //
92 | // self.labelMap.showsUserLocation = true
93 | // }
94 |
95 | }
96 |
97 | //Load Images
98 | func loadImage(imageView: UIImageView, urlString: String) {
99 | let imgUrl:URL = URL(string: urlString)!
100 |
101 | URLSession.shared.dataTask(with: imgUrl) {
102 | (data, response, error) in
103 | guard let data = data, error == nil else {return}
104 |
105 | DispatchQueue.main.async(execute: {
106 | imageView.image = UIImage(data: data)
107 | })
108 | }.resume()
109 | }
110 |
111 |
112 |
113 |
114 |
115 | //Add Payment
116 |
117 | @IBAction func addPayment(_ sender: Any) {
118 | if self.labelAddress.text == "" {
119 | let alertController = UIAlertController(title: "No Address", message: "Address is required", preferredStyle: .alert)
120 | let okAction = UIAlertAction(title: "OK", style: .default, handler: {(alert) in
121 | self.labelAddress.becomeFirstResponder()
122 | })
123 | alertController.addAction(okAction)
124 | self.present(alertController, animated: true, completion: nil)
125 | }else {
126 | Cart.currentCart.address = self.labelAddress.text
127 | self.performSegue(withIdentifier: "AddPayment", sender: nil)
128 | }
129 | }
130 |
131 |
132 |
133 | //Location
134 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
135 |
136 | let location = locations.last! as CLLocation
137 |
138 | let center = CLLocationCoordinate2D(
139 | latitude: location.coordinate.latitude,
140 | longitude: location.coordinate.longitude)
141 |
142 | let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
143 |
144 | self.labelMap.setRegion(region, animated: true)
145 | }
146 |
147 |
148 | //Address Location
149 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
150 |
151 | let address = textField.text
152 | let geocoder = CLGeocoder()
153 | Cart.currentCart.address = address
154 |
155 | geocoder.geocodeAddressString(address!) { (placemarks, error) in
156 |
157 | if (error != nil) {
158 | print("Error: ", error as Any)
159 | }
160 |
161 | if let placemark = placemarks?.first {
162 |
163 | let coordinates: CLLocationCoordinate2D = placemark.location!.coordinate
164 |
165 | let region = MKCoordinateRegion(
166 | center: coordinates,
167 | span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
168 | )
169 |
170 | self.labelMap.setRegion(region, animated: true)
171 | //self.locationManager.stopUpdatingLocation()
172 |
173 | // Create a pin
174 | let dropPin = MKPointAnnotation()
175 | dropPin.coordinate = coordinates
176 |
177 | self.labelMap.addAnnotation(dropPin)
178 | }
179 | }
180 |
181 | return true
182 | }
183 |
184 | // func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
185 | // return 100
186 | // }
187 |
188 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
189 | return Cart.currentCart.items.count
190 | //return 5
191 | }
192 |
193 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
194 | let cell = tbvCart.dequeueReusableCell(withIdentifier: "CartCell") as! CartCell
195 |
196 | let cart = Cart.currentCart.items[indexPath.row]
197 | cell.qtyItemLabel.text = "\(cart.qty)"
198 | cell.mealNameLabel.text = cart.meal.name
199 | //cell.priceItemLabel.text = "$\(cart.meal.price! * Float(cart.qty))0"
200 | cell.priceItemLabel.text = (String(format: "$%.2f", cart.meal.price!*Float(cart.qty)))
201 |
202 |
203 | if let image = cart.meal.image {
204 | loadImage(imageView: cell.mealImage , urlString: "\(image)")
205 | }
206 |
207 | print(cart)
208 |
209 | return cell
210 | }
211 |
212 | }
213 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Customer/CustomerMenuViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomerMenuViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class CustomerMenuViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
11 |
12 | var restaurant: Restaurant?
13 | var meals = [Meal]()
14 |
15 |
16 | @IBOutlet weak var tbvMenu: UITableView!
17 |
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | tbvMenu.dataSource = self
23 | tbvMenu.delegate = self
24 |
25 | if let restaurantName = restaurant?.name {
26 | self.navigationItem.title = restaurantName
27 | }
28 |
29 | // Do any additional setup after loading the view.
30 | }
31 | override func viewWillAppear(_ animated: Bool) {
32 |
33 | //Run Fuctions
34 | loadMeals()
35 | }
36 |
37 |
38 | func loadMeals() {
39 | if let restaurantId = restaurant?.id {
40 | APIManager.shared.getMeals(resturantId: restaurantId, completionHandler: {(json) in
41 | if json != nil {
42 | self.meals = []
43 |
44 | if let tempMeals = json?["meals"].array {
45 | for item in tempMeals {
46 | let meal = Meal(json: item)
47 | self.meals.append(meal)
48 | }
49 | self.tbvMenu.reloadData()
50 | }
51 | }
52 | })
53 | }
54 | }
55 |
56 |
57 | //Load Images
58 | func loadImage(imageView: UIImageView, urlString: String) {
59 | let imgUrl:URL = URL(string: urlString)!
60 |
61 | URLSession.shared.dataTask(with: imgUrl) {
62 | (data, response, error) in
63 | guard let data = data, error == nil else {return}
64 |
65 | DispatchQueue.main.async(execute: {
66 | imageView.image = UIImage(data: data)
67 | })
68 | }.resume()
69 | }
70 |
71 |
72 |
73 |
74 |
75 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
76 | return meals.count
77 | }
78 |
79 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
80 | let cell = tableView.dequeueReusableCell(withIdentifier: "MenuCell") as! MenuCell
81 |
82 | let meal = meals[indexPath.row]
83 | cell.mealName.text = meal.name
84 | cell.mealDescription.text = meal.short_description
85 |
86 | if let price = meal.price {
87 | cell.mealPrice.text = "$\(price)0"
88 | }
89 |
90 | if let image = meal.image {
91 | loadImage(imageView: cell.mealImg, urlString: "\(image)")
92 | }
93 |
94 |
95 | return cell
96 | }
97 |
98 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
99 | return 164
100 | }
101 |
102 |
103 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
104 |
105 | if segue.identifier == "MealDetails" {
106 | let controller = segue.destination as! MealDetailViewController
107 | controller.meal = meals[(tbvMenu.indexPathForSelectedRow?.row)!]
108 | controller.restaurant = restaurant
109 |
110 | }
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Customer/MealDetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MealDetailViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import UIKit
9 | import Stripe
10 |
11 | class MealDetailViewController: UIViewController {
12 |
13 | @IBOutlet weak var mealImage: UIImageView!
14 | @IBOutlet weak var mealName: UILabel!
15 | @IBOutlet weak var mealDescription: UILabel!
16 | @IBOutlet weak var lbQty: UILabel!
17 | @IBOutlet weak var lbTotal: UILabel!
18 | @IBOutlet var labelIndividualCost: UILabel!
19 | @IBOutlet weak var reduceQtyButton: UIButton!
20 | @IBOutlet weak var increaseQtyButton: UIButton!
21 | @IBOutlet weak var addToCartButton: UIButton!
22 |
23 | var meal: Meal?
24 | var restaurant: Restaurant?
25 | var qty = 1
26 |
27 | @IBAction func goToCart(_ sender: Any) {
28 | performSegue(withIdentifier: "ViewCartSegue", sender: "ViewCart")
29 | }
30 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
31 | let destination = sender as? String
32 | if(destination == "ViewCart"){
33 | tabBarController?.selectedIndex = 1
34 | }
35 | }
36 |
37 | override func viewDidLoad() {
38 | super.viewDidLoad()
39 |
40 | loadMeal()
41 | // Do any additional setup after loading the view.
42 | configure()
43 | }
44 |
45 |
46 | func loadMeal() {
47 |
48 | if let price = meal?.price {
49 | lbTotal.text = "Total\n$\(price)0"
50 | labelIndividualCost.text = "Each\n$\(price)0"
51 | }
52 |
53 |
54 | mealName.text = meal?.name
55 | mealDescription.text = meal?.short_description
56 | // Helpers.loadImage(imgMeal, "http://cdn.sallysbakingaddiction.com/wp-content/uploads/2020/03/mini-quiches.jpg")
57 |
58 | if let imageUrl = meal?.image {
59 | Helpers.loadImage(mealImage, "\(imageUrl)")
60 | //print(imageUrl)
61 | }
62 | }
63 |
64 | private func configure() {
65 | mealImage.layer.cornerRadius = 32
66 | mealImage.clipsToBounds = true
67 | view.backgroundColor = .systemGray5
68 |
69 |
70 | labelIndividualCost.backgroundColor = .white
71 | lbTotal.backgroundColor = .white
72 |
73 | labelIndividualCost.layer.cornerRadius = 16
74 | labelIndividualCost.clipsToBounds = true
75 | lbTotal.layer.cornerRadius = 16
76 | lbTotal.clipsToBounds = true
77 | lbQty.clipsToBounds = true
78 | lbQty.layer.cornerRadius = 8
79 | reduceQtyButton.layer.cornerRadius = 8
80 | increaseQtyButton.layer.cornerRadius = 8
81 | addToCartButton.layer.cornerRadius = 8
82 |
83 | }
84 |
85 |
86 | //
87 |
88 | @IBAction func removeQty(_ sender: Any) {
89 | if qty >= 2 {
90 | qty -= 1
91 | lbQty.text = String(qty)
92 |
93 | if let price = meal?.price {
94 | lbTotal.text = "Total\n$\(price * Float(qty))0"
95 | }
96 | }
97 | }
98 |
99 | @IBAction func addQty(_ sender: Any) {
100 | if qty < 99 {
101 | qty += 1
102 | lbQty.text = String(qty)
103 |
104 | if let price = meal?.price {
105 | lbTotal.text = "Total\n$\(price * Float(qty))0"
106 | }
107 | }
108 |
109 | }
110 |
111 | @IBAction func addToCart(_ sender: Any) {
112 | print("Add to cart from restaurant: \(restaurant?.id)")
113 | //print("\(Cart.currentCart.restaurant)")
114 |
115 | let image = UIImageView(frame: CGRect(x: 0, y: 0, width: 60, height: 40))
116 | image.image = UIImage(named: "")
117 | image.center = CGPoint(x: self.view.frame.width/2, y: self.view.frame.height-100)
118 | self.view.addSubview(image)
119 |
120 | UIView.animate(withDuration: 1.0,
121 | delay: 0.0,
122 | options: UIView.AnimationOptions.curveEaseOut,
123 | animations: { image.center = CGPoint(x: self.view.frame.width - 40, y: 24) },
124 | completion: { _ in
125 | image.removeFromSuperview()
126 | let cartItem = CartItem(meal: self.meal!, qty: self.qty)
127 | guard let cartRestaurant = Cart.currentCart.restaurant, let currentRestaurant = self.restaurant else {
128 | print(Cart.currentCart.restaurant)
129 | print(self.restaurant)
130 | // If those requirements are not met
131 | Cart.currentCart.restaurant = self.restaurant
132 | Cart.currentCart.items.append(cartItem)
133 | print("Added item(s) to empty cart")
134 | self.dismiss()
135 | return
136 | }
137 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel){_ in
138 | self.dismiss()
139 | }
140 | // If ordering meal from the same restaurant
141 | if cartRestaurant.id == currentRestaurant.id {
142 | let inCart = Cart.currentCart.items.firstIndex(where: { (item) -> Bool in
143 | return item.meal.id! == cartItem.meal.id!
144 | })
145 | if let index = inCart {
146 | let alertView = UIAlertController(
147 | title: "Add more?",
148 | message: "Your Cart already has this.",
149 | preferredStyle: .alert)
150 | let okAction = UIAlertAction(title: "Add more", style: .default, handler: { (action: UIAlertAction!) in
151 | print("Added more of the same item")
152 | Cart.currentCart.items[index].qty += self.qty
153 | self.dismiss()
154 | })
155 | alertView.addAction(okAction)
156 | alertView.addAction(cancelAction)
157 | self.present(alertView, animated: true, completion: nil)
158 | } else {
159 | print("Added something new")
160 | Cart.currentCart.items.append(cartItem)
161 | self.dismiss()
162 | }
163 | }
164 | else {// If ordering meal from the another restaurant
165 | print("Diff Rest")
166 | let alertView = UIAlertController(
167 | title: "Start new Order?",
168 | message: "You're ordering meal from another restaurant. Create New Order?",
169 | preferredStyle: .alert)
170 | let okAction = UIAlertAction(title: "New Order", style: .default, handler: { (action: UIAlertAction!) in
171 | Cart.currentCart.items = []
172 | Cart.currentCart.items.append(cartItem)
173 | Cart.currentCart.restaurant = self.restaurant
174 | print("Item(s) added from new restaurant")
175 | self.dismiss()
176 | })
177 | alertView.addAction(okAction)
178 | alertView.addAction(cancelAction)
179 | self.present(alertView, animated: true, completion: nil)
180 | }
181 | })
182 | }
183 | func dismiss(){
184 | self.navigationController?.popViewController(animated: true)
185 | }
186 |
187 | }
188 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Customer/OrderViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrderViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 | import SwiftyJSON
10 | import MapKit
11 |
12 | class OrderViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, MKMapViewDelegate {
13 |
14 | @IBOutlet weak var tbvOrder: UITableView!
15 |
16 | @IBOutlet var statusLabel: UILabel!
17 |
18 | @IBOutlet weak var map: MKMapView!
19 | @IBOutlet weak var lbStatus: UILabel!
20 |
21 | //
22 | var cart = [JSON]()
23 |
24 | var destination: MKPlacemark?
25 | var source: MKPlacemark?
26 |
27 | var driverPin: MKPointAnnotation!
28 | var selfPin: MKPointAnnotation!
29 | var restaurantPin: MKPointAnnotation!
30 |
31 | var userLocation: CLLocationCoordinate2D!
32 |
33 |
34 | var updateDriverLocationTimer = Timer()
35 | var zoomTimer = Timer()
36 | var refreshTimer = Timer()
37 |
38 |
39 |
40 | override func viewDidLoad() {
41 | super.viewDidLoad()
42 |
43 | tbvOrder.dataSource = self
44 | tbvOrder.delegate = self
45 |
46 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
47 | self.getLatestOrder()
48 | }
49 |
50 | map.layer.cornerRadius = 32
51 |
52 | // Do any additional setup after loading the view.
53 | }
54 |
55 | override func viewWillAppear(_ animated: Bool) {
56 | print("On orderView Controller")
57 | if(!self.refreshTimer.isValid){
58 | setRefreshViewControllerTimer()
59 | }
60 | let seconds = 1.0
61 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
62 | self.autoZoom()
63 | }
64 | }
65 |
66 |
67 | override func viewDidAppear(_ animated: Bool) {
68 | }
69 |
70 | func setRefreshViewControllerTimer(){
71 | print("setRefreshViewControllerTimer START")
72 | refreshTimer = Timer.scheduledTimer(
73 | timeInterval: 5.0,
74 | target: self,
75 | selector: #selector(refreshViewController),
76 | userInfo: nil,
77 | repeats: true)
78 | }
79 | @objc func refreshViewController(){
80 | self.getLatestOrder()
81 | }
82 | func getLatestOrder() {
83 | //print("Get Latest Order from Order View Controller")
84 | APIManager.shared.getLatestOrder { [self] (json) in
85 | let order = json["order"]
86 | //print(json)
87 | //print("order status:\(json["order"]["status"] as? String ?? "no prev orders")")
88 | let orderStatus = json["order"]["status"].string as? String ?? nil
89 | //print("order status:\(orderStatus)")
90 | if orderStatus != nil {
91 | if(orderStatus! == "Delivered"){
92 | self.zoomTimer.invalidate()
93 | self.updateDriverLocationTimer.invalidate()
94 | statusLabel.text = "Previous Order:"
95 | }
96 | if let orderDetails = order["order_details"].array {
97 | self.lbStatus.text = order["status"].string!
98 | self.cart = orderDetails
99 | self.tbvOrder.reloadData()
100 | }
101 | let from = order["restaurant"]["address"].string!
102 | let to = order["address"].string!
103 |
104 | self.getLocation(from, "Restaurant", { (sou) in
105 | self.source = sou
106 | self.getLocation(to, "You", { (des) in
107 | self.destination = des
108 | self.getDirections()
109 | })
110 | })
111 | if orderStatus! == "On the way" {
112 | statusLabel.text = "Current order:"
113 | //getDriverLocation(self)
114 | if(!self.zoomTimer.isValid){
115 | self.setZoomTimer()
116 | }
117 | if(!self.updateDriverLocationTimer.isValid){
118 | self.setUpdateDriverLocationTimer()
119 | }
120 | }
121 | } else {
122 | print("No prev order")
123 | self.lbStatus.text = "No previous orders"
124 | }
125 | }
126 | //self.autoZoom()
127 | }
128 | func setZoomTimer() {
129 | print("SetZoomTimer START")
130 | //getDriverLocation(self)
131 | zoomTimer = Timer.scheduledTimer(
132 | timeInterval: 5.0,
133 | target: self,
134 | selector: #selector(autoZoom),
135 | userInfo: nil,
136 | repeats: true)
137 | }
138 | @objc func autoZoom() {
139 | //print("AutoZoom called")
140 | var zoomRect = MKMapRect.null
141 | for annotation in self.map.annotations {
142 | let annotationPoint = MKMapPoint(annotation.coordinate)
143 | let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.1, height: 0.1)
144 | zoomRect = zoomRect.union(pointRect)
145 | }
146 | let insetWidth = -zoomRect.size.width * 0.2
147 | let insetHeight = -zoomRect.size.height * 0.2
148 | let insetRect = zoomRect.insetBy(dx: insetWidth, dy: insetHeight)
149 |
150 | self.map.setVisibleMapRect(insetRect, animated: true)
151 | }
152 |
153 |
154 | // repeats: to update driver location
155 | func setUpdateDriverLocationTimer() {
156 | print("SetUpdateDriverLocationTimer START")
157 | //getDriverLocation(self)
158 | updateDriverLocationTimer = Timer.scheduledTimer(
159 | timeInterval: 0.1,
160 | target: self,
161 | selector: #selector(getDriverLocation(_:)),
162 | userInfo: nil,
163 | repeats: true)
164 | }
165 | @objc func getDriverLocation(_ sender: AnyObject) {
166 | //print("Get Driver Location from OrderViewController")
167 | APIManager.shared.getDriverLocation { (json) in
168 | if let location = json["location"].string {
169 | print("Printing Driver Location from OrderView Controller")
170 | print(location)
171 | let split = location.components(separatedBy: ",")
172 | let lat = split[0]
173 | let lng = split[1]
174 | let coordinate = CLLocationCoordinate2D(latitude: CLLocationDegrees(lat)!, longitude: CLLocationDegrees(lng)!)
175 | //print(coordinate)
176 | // Create pin annotation for Driver
177 | if self.driverPin != nil {
178 | self.driverPin.coordinate = coordinate
179 | } else {
180 | self.driverPin = MKPointAnnotation()
181 | self.driverPin.coordinate = coordinate
182 | self.driverPin.title = "Driver"
183 | self.map.addAnnotation(self.driverPin)
184 | }
185 | // Reset zoom rect to cover 3 locations
186 | } else {
187 | self.updateDriverLocationTimer.invalidate()
188 | self.zoomTimer.invalidate()
189 | self.map.removeAnnotation(self.driverPin)
190 | self.map.removeAnnotation(self.restaurantPin)
191 | print("Timer END")
192 | }
193 | }
194 | }
195 | //Map Function
196 | // #1
197 | func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
198 | print("mapView Start")
199 |
200 | let renderer = MKPolylineRenderer(overlay: overlay)
201 | renderer.strokeColor = UIColor.blue
202 | renderer.lineWidth = 5.0
203 |
204 | return renderer
205 | }
206 |
207 | // #2
208 | // When you call getLocation for each address, you add an "annotation" to the map
209 | // these are the pins
210 | func getLocation(_ address: String,_ title: String,_ completionHandler: @escaping (MKPlacemark) -> Void) {
211 |
212 | let geocoder = CLGeocoder()
213 | geocoder.geocodeAddressString(address) { (placemarks, error) in
214 | //print("Address: ************")
215 | //print("Address: \(placemarks?.first?.location?.coordinate)")
216 | //print("Address: \(placemarks?.first?.location?.description)")
217 | if (error != nil) {
218 | print("Error in geolocation: \(error!)")
219 | }
220 |
221 | if let placemark = placemarks?.first {
222 |
223 | let coordinates: CLLocationCoordinate2D = placemark.location!.coordinate
224 |
225 | // Create a pin
226 | let dropPin = MKPointAnnotation()
227 | dropPin.coordinate = coordinates
228 | dropPin.title = title
229 | if(title=="Restaurant"){
230 | self.restaurantPin = dropPin
231 | } else if(title == "You"){
232 | self.userLocation = coordinates
233 | }
234 | self.map.addAnnotation(dropPin)
235 | completionHandler(MKPlacemark.init(placemark: placemark))
236 | }
237 | }
238 | }
239 |
240 | // #3
241 | func getDirections() {
242 |
243 | let request = MKDirections.Request()
244 | request.source = MKMapItem.init(placemark: source!)
245 | request.destination = MKMapItem.init(placemark: destination!)
246 | request.requestsAlternateRoutes = false
247 |
248 | let directions = MKDirections(request: request)
249 | directions.calculate { (response, error) in
250 |
251 | if error != nil {
252 | print("Error: ", error)
253 | } else {
254 | // Show route
255 | self.showRoute(response: response!)
256 | }
257 | }
258 |
259 | }
260 |
261 | // #4
262 | func showRoute(response: MKDirections.Response) {
263 |
264 | for route in response.routes {
265 | self.map.addOverlay(route.polyline, level: MKOverlayLevel.aboveRoads)
266 | }
267 |
268 | //
269 | // var zoomRect = MKMapRect.null
270 | // for annotation in self.map.annotations {
271 | // let annotationPoint = MKMapPoint(annotation.coordinate)
272 | // let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.1, height: 0.1)
273 | // zoomRect = zoomRect.union(pointRect)
274 | // }
275 | //
276 | // let insetWidth = -zoomRect.size.width * 0.2
277 | // let insetHeight = -zoomRect.size.height * 0.2
278 | // let insetRect = zoomRect.insetBy(dx: insetWidth, dy: insetHeight)
279 | //
280 | // self.map.setVisibleMapRect(insetRect, animated: true)
281 | //
282 | }
283 |
284 |
285 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
286 |
287 | let annotationIdentifier = "MyPin"
288 |
289 | var annotationView: MKAnnotationView?
290 | if let dequeueAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) {
291 |
292 | annotationView = dequeueAnnotationView
293 | annotationView?.annotation = annotation
294 | } else {
295 |
296 | annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
297 | }
298 |
299 | if let annotationView = annotationView, let name = annotation.title! {
300 | switch name {
301 | case "Driver":
302 | annotationView.canShowCallout = true
303 | annotationView.image = UIImage(named: "pin_car")
304 | case "Restaurant":
305 | annotationView.canShowCallout = true
306 | annotationView.image = UIImage(named: "pin_restaurant")
307 | case "You":
308 | annotationView.canShowCallout = true
309 | annotationView.image = UIImage(named: "pin_customer")
310 | default:
311 | annotationView.canShowCallout = true
312 | annotationView.image = UIImage(named: "pin_car")
313 | }
314 | }
315 |
316 | return annotationView
317 | }
318 |
319 | //Table View Functions
320 |
321 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
322 | return cart.count
323 | }
324 |
325 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
326 | let cell = tableView.dequeueReusableCell(withIdentifier: "OrderCell") as! OrderCell
327 |
328 | let item = cart[indexPath.row]
329 | cell.orderItemQuantityLabel.text = String(item["quantity"].int!)
330 | cell.orderItemNameLabel.text = item["meal"]["name"].string
331 | //cell.orderItemPriceLabel.text = "$\(String(item["sub_total"].float!))"
332 | cell.orderItemPriceLabel.text = (String(format: "$%.2f", item["sub_total"].float!))
333 |
334 | return cell
335 | }
336 |
337 | //End
338 |
339 |
340 | }
341 |
342 | extension UIView {
343 |
344 | @IBInspectable var cornerRadiusV: CGFloat {
345 | get {
346 | return layer.cornerRadius
347 | }
348 | set {
349 | layer.cornerRadius = newValue
350 | layer.masksToBounds = newValue > 0
351 | }
352 | }
353 |
354 | @IBInspectable var borderWidthV: CGFloat {
355 | get {
356 | return layer.borderWidth
357 | }
358 | set {
359 | layer.borderWidth = newValue
360 | }
361 | }
362 |
363 | @IBInspectable var borderColorV: UIColor? {
364 | get {
365 | return UIColor(cgColor: layer.borderColor!)
366 | }
367 | set {
368 | layer.borderColor = newValue?.cgColor
369 | }
370 | }
371 | }
372 |
373 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Customer/PaymentViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PaymentViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 | import Stripe
10 | import SwiftyJSON
11 |
12 | class PaymentViewController: UIViewController {
13 |
14 |
15 | @IBOutlet weak var cardTextField: STPPaymentCardTextField!
16 |
17 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
18 | let destination = sender as? String
19 | if(destination == "CurrentOrder"){
20 | tabBarController?.selectedIndex = 2
21 | }
22 | }
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | }
27 |
28 | override func viewDidAppear(_ animated: Bool) {
29 | print("On Payment View Controller")
30 | }
31 |
32 | @IBAction func placeOrder(_ sender: Any) {
33 | print("_________________________PlaceOrderButton from Payment View Controller_________________________")
34 | //print("_________________________placeOrder -> getLatestOrder_______")
35 | APIManager.shared.getLatestOrder { (json) in
36 |
37 | //print(json)
38 | let orderStatus = json["order"]["status"].string ?? nil
39 |
40 | print("previous order status: \(orderStatus), may be NIL if no prev order")
41 |
42 | // if json["order"]["status"] as? String == nil || json["order"]["status"] as? String == "Delivered" {
43 | if orderStatus == "Delivered" || orderStatus == nil{
44 | // if json["order"]["status"] == "Delivered" || json["order"]["total"] == nil{
45 | // Processing the payment and create an Order
46 | print("________________Order can be placed________________")
47 | //let card = self.cardTextField.cardParams
48 | //let card: STPCardParams = STPCardParams()
49 | let card: STPCardParams = STPCardParams()
50 | card.number = self.cardTextField!.cardNumber
51 | card.expMonth = UInt(self.cardTextField!.expirationMonth)
52 | card.expYear = UInt(self.cardTextField!.expirationYear)
53 | card.cvc = self.cardTextField!.cvc
54 | // card.number = "4242424242424242"
55 | // card.expMonth = 12
56 | // card.expYear = 22
57 | // card.cvc = "123"
58 | STPAPIClient.shared.createToken(withCard: card, completion: { (token, error) in
59 | //print("____________Card Token: \(token!)__________")
60 | if let myError = error {
61 | print("My Error:", myError)
62 | } else if let stripeToken = token {
63 | //print("____________Token Created no errors__________")
64 | //print(token)
65 | APIManager.shared.createOrder(stripeToken: stripeToken.tokenId) { (json) in
66 | //Cart.currentCart.reset()
67 | self.dismissAndGo()
68 | }
69 | print("_________________________Order Successfully Created_______")
70 | }
71 | })
72 | } else {
73 | // Showing an alert message.
74 | print("Place Order Error")
75 | let cancelAction = UIAlertAction(title: "OK", style: .cancel) { _ in
76 | self.navigationController?.popViewController(animated: true)
77 | }
78 | let okAction = UIAlertAction(title: "Go to order", style: .default, handler: { (action) in
79 | self.dismissAndGo()
80 | })
81 | let alertView = UIAlertController(title: "Already Order?", message: "Your current order isn't completed", preferredStyle: .alert)
82 | alertView.addAction(okAction)
83 | alertView.addAction(cancelAction)
84 | self.present(alertView, animated: true, completion: nil)
85 | }
86 | }
87 | }
88 |
89 | func dismissAndGo(){
90 | self.navigationController?.popViewController(animated: true)
91 | self.performSegue(withIdentifier: "CurrentOrderSegue", sender: "CurrentOrder")
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Customer/ProfileViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class ProfileViewController: UIViewController {
11 |
12 |
13 | @IBOutlet weak var imgAvatar: UIImageView!
14 |
15 | @IBOutlet weak var lbFirstName: UILabel!
16 | @IBOutlet weak var lbLastName: UILabel!
17 | @IBOutlet weak var userEmailLabel: UILabel!
18 | @IBOutlet weak var signoutActionButton: UIButton!
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 |
23 |
24 | let fullName = User.currenUser.name!
25 | userEmailLabel.text = User.currenUser.email
26 |
27 | let components = fullName.components(separatedBy: " ")
28 |
29 | lbFirstName.text = components.first
30 | lbLastName.text = components.last
31 |
32 | imgAvatar.image = try! UIImage(data: Data (contentsOf: URL(string: User.currenUser.pictureURL!)!))
33 |
34 | imgAvatar.layer.cornerRadius = imgAvatar.bounds.height/2
35 | signoutActionButton.layer.cornerRadius = 16
36 | // Do any additional setup after loading the view.
37 | }
38 |
39 |
40 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
41 | if segue.identifier == "CustomerLogout" {
42 | // APIManager.shared.logout(completionHandler:{ (error) in
43 | // if error == nil {
44 | FBManager.shared.logOut()
45 | User.currenUser.resetInfo()
46 | // print("logggin out")
47 | // }
48 |
49 | // })
50 | }
51 | }
52 |
53 | @IBAction func signoutAction(_ sender: Any) {
54 | FBManager.shared.logOut()
55 | User.currenUser.resetInfo()
56 | self.dismiss(animated: true, completion: nil)
57 | }
58 |
59 | // if segue.identifier == "CustomerLogout" {
60 | // APIManager.shared.logout(completionHandler: {
61 | // (error) in
62 | // if error == nil {
63 | // FBManager.shared.logOut()
64 | // User.currenUser.resetInfo()
65 | // print("logggin out")
66 | //
67 | // let storyboard = UIStoryboard(name: "Main", bundle: nil)
68 | // let appController = storyboard.instantiateViewController(withIdentifier: "MainController") as! LoginViewController
69 | // let appDelegate = UIApplication.shared.delegate as! AppDelegate
70 | // appDelegate.window?.rootViewController = appController
71 | // }
72 | //
73 | // })
74 | // }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Customer/RestaurantViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RestaurantViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import UIKit
9 | import Stripe
10 |
11 | class RestaurantViewController: UIViewController , UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
12 |
13 | @IBOutlet weak var searchRestaurant: UISearchBar!
14 | @IBOutlet weak var tbvRestaurant: UITableView!
15 |
16 | @IBOutlet weak var userWelcomeLabel: UILabel!
17 |
18 | enum Section { case main }
19 |
20 | //Getting data Dictionaries
21 | var restaurants: [Restaurant] = []
22 | var filterRestaurants = [Restaurant]()
23 |
24 | //var dataSource: UITableViewDiffableDataSource!
25 |
26 | override func viewDidLoad() {
27 | super.viewDidLoad()
28 | tbvRestaurant.dataSource = self
29 | tbvRestaurant.delegate = self
30 | //loadRestaurants()
31 |
32 |
33 | userWelcomeLabel.text = User.currenUser.name
34 |
35 | }
36 |
37 | override func viewWillAppear(_ animated: Bool) {
38 | loadRestaurants()
39 | }
40 |
41 | func loadRestaurants() {
42 | APIManager.shared.getRestaurants(completionHandler: {
43 | (json) in
44 | if json != nil {
45 | self.restaurants = []
46 | if let listRest = json!["restaurants"].array {
47 | for item in listRest {
48 | let restaurant = Restaurant(json: item)
49 | self.restaurants.append(restaurant)
50 | //print(item)
51 | //print("Restaurant \(item["name"])")
52 | //print("Description \(item["description"])")
53 | }
54 | self.tbvRestaurant.reloadData()
55 | }
56 | }
57 | })
58 | //print(restaurants)
59 | }
60 |
61 | func loadImage(imageView: UIImageView, urlString: String) {
62 | let imgUrl:URL = URL(string: urlString)!
63 |
64 | URLSession.shared.dataTask(with: imgUrl) {
65 | (data, response, error) in
66 | guard let data = data, error == nil else {return}
67 |
68 | DispatchQueue.main.async(execute: {
69 | imageView.image = UIImage(data: data)
70 | })
71 | }.resume()
72 | }
73 |
74 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
75 | if segue.identifier == "MealList" {
76 | let controller = segue.destination as! CustomerMenuViewController
77 | controller.restaurant = restaurants[(tbvRestaurant.indexPathForSelectedRow?.row)!]
78 | }
79 | }
80 |
81 | //Searcb Bar
82 | func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
83 | filterRestaurants = self.restaurants.filter({ (res: Restaurant) -> Bool in
84 | return res.name?.lowercased().range(of: searchText.lowercased()) != nil
85 | print(searchText)
86 | })
87 | self.tbvRestaurant.reloadData()
88 | }
89 |
90 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
91 | if searchRestaurant.text != "" {
92 | return self.filterRestaurants.count
93 | }
94 | return restaurants.count
95 | }
96 |
97 | // func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
98 | // return 350
99 | // }
100 |
101 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
102 | //let cell = UITableViewCell()
103 |
104 | let cell = tbvRestaurant.dequeueReusableCell(withIdentifier: "RestaurantCell") as! RestaurantCell
105 |
106 | let restaurant: Restaurant
107 |
108 | if searchRestaurant.text != "" {
109 | restaurant = filterRestaurants[indexPath.row]
110 | } else {
111 | restaurant = restaurants[indexPath.row]
112 | }
113 |
114 | cell.lbRestaurantName.text = restaurant.name
115 | cell.lbRestaurantDesc.text = restaurant.description
116 | cell.lbRestaurantAddress.text = "Address: \(restaurant.address ?? StringConstants.ErrorMessages.NO_ADDRESS)"
117 | cell.lbRestaurantPhone.text = "Phone: \(restaurant.phone ?? StringConstants.ErrorMessages.NO_PHONE)"
118 |
119 |
120 | if let logoName = restaurant.logo {
121 | let url = "\(logoName)"
122 | loadImage(imageView: cell.imgRestaurantLogo, urlString: url)
123 |
124 | }
125 |
126 | return cell
127 | }
128 | }
129 |
130 |
131 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Driver/DeliveryViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeliveryViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 | import MapKit
10 |
11 | class DeliveryViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
12 |
13 | @IBOutlet weak var lbcustomerName: UILabel!
14 | @IBOutlet weak var lbCustomerAddress: UILabel!
15 | @IBOutlet weak var imgCustomerAvatar: UIImageView!
16 | @IBOutlet weak var viewInfo: UIView!
17 | @IBOutlet weak var map: MKMapView!
18 | @IBOutlet weak var bcomplete: UIButton!
19 |
20 | let maxd = 0.0001
21 | let delta = 0.00001
22 | let simulatorFrequency = 0.02
23 |
24 | var orderId: Int?
25 | var driverHasOrder: Bool = false
26 | var customerName: String?
27 | var lbMessage = UILabel(frame: CGRect(x: 0, y: 0, width: 250, height: 200))
28 | //map destination
29 | var destination: MKPlacemark?
30 | var source: MKPlacemark?
31 |
32 | var locationManager: CLLocationManager!
33 |
34 | var driverPin: MKPointAnnotation!
35 | var restaurantPin: MKPointAnnotation!
36 | var customerPin: MKPointAnnotation!
37 |
38 |
39 | var driverLocation: CLLocationCoordinate2D!
40 | var restaurantLocation: CLLocationCoordinate2D!
41 | var customerLocation: CLLocationCoordinate2D!
42 |
43 | //Update location every 3 sec
44 | var refreshTimer = Timer()
45 | var zoomTimer = Timer()
46 | var updateDriverLocationTimer = Timer()
47 | var simulatorToRestaurantTimer = Timer()
48 | var simulatorToCustomerTimer = Timer()
49 | var moving = false
50 |
51 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
52 | let destination = sender as? String
53 | if(destination == "AvailableOrders"){
54 | tabBarController?.selectedIndex = 0
55 | }
56 | }
57 |
58 | /*
59 | view did load
60 | first startRefreshTimer
61 | - check if there's a current order
62 | - reload page
63 |
64 | viewwillappear
65 | if(!notLocation) - set fake location
66 | else remain where you are
67 |
68 | if (order){
69 | startLocationTimer
70 | */
71 |
72 |
73 | override func viewDidLoad() {
74 | super.viewDidLoad()
75 |
76 | // MUST REMOVE PINS FROM CUSTOMER RESTAURANT VIEW CONTROLLER
77 |
78 | // if CLLocationManager.locationServicesEnabled() {
79 | // locationManager = CLLocationManager()
80 | // locationManager.delegate = self
81 | // locationManager.desiredAccuracy = kCLLocationAccuracyBest
82 | // locationManager.requestAlwaysAuthorization()
83 | // locationManager.startUpdatingLocation()
84 | // self.map.showsUserLocation = false
85 | // }
86 |
87 |
88 |
89 | if(self.driverLocation == nil){
90 | print("________Moving in 10 seconds___")
91 | // print(self.driverLocation)
92 | // print(self.driverLocation)
93 | }
94 |
95 | //Update location
96 | // DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
97 | // print("2 ")
98 | // self.updateDriverLocationTimer = Timer.scheduledTimer(
99 | // timeInterval: 0.5,
100 | // target: self,
101 | // selector: #selector(self.sendDriverLocationToServer(_:)),
102 | // userInfo: nil,
103 | // repeats: true)
104 | // }
105 |
106 |
107 | }
108 |
109 | override func viewWillAppear(_ animated: Bool) {
110 | // Show current Driver's location
111 | loadData()
112 | LoadUnloadTimer(timer: &refreshTimer, phase: "setOn", name:"RefreshTimer", interval: 5.0, function: #selector(loadData))
113 | if(orderId != -1){
114 | autoZoom()
115 | LoadUnloadTimer(timer: &zoomTimer, phase: "setOn", name:"ZoomTimer", interval: 3.0, function: #selector(autoZoom))
116 | }
117 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
118 | if(self.driverLocation == nil){
119 | self.setDriverLocationTo()
120 | }
121 | }
122 |
123 |
124 | }
125 |
126 | override func viewWillDisappear(_ animated: Bool) {
127 | LoadUnloadTimer(timer: &refreshTimer, phase: "setOff", name:"RefreshTimer", interval: nil, function: nil)
128 | LoadUnloadTimer(timer: &zoomTimer, phase: "setOff", name:"ZoomTimer", interval: nil, function: nil)
129 | }
130 |
131 | // @objc func setRefreshVCTimer(){
132 | // print("RefreshViewControllerTimer START")
133 | // self.loadData()
134 | // self.refreshTimer = Timer.scheduledTimer(
135 | // timeInterval: 5.0,
136 | // target: self,
137 | // selector: #selector(loadData),
138 | // userInfo: nil,
139 | // repeats: true)
140 | // }
141 |
142 | func LoadUnloadTimer(timer: inout Timer, phase: String, name: String, interval: Double? = nil, function: Selector? = nil){
143 | if(!timer.isValid && phase == "setOn"){
144 | print("Started \(name)")
145 | timer = Timer.scheduledTimer(
146 | timeInterval: interval!,
147 | target: self,
148 | selector: function!,
149 | userInfo: nil,
150 | repeats: true)
151 | } else if(timer.isValid && phase == "setOff"){
152 | timer.invalidate()
153 | print("Stopped \(name)")
154 | }
155 | }
156 |
157 | @objc func sendDriverLocationToServer(_ sender: AnyObject) {
158 | //self.autoZoom()
159 | if(self.driverLocation != nil){
160 | APIManager.shared.updateLocation(location: self.driverLocation) { (json) in
161 | //print(self.driverLocation)
162 | if self.driverPin != nil {
163 | self.driverPin.coordinate = self.driverLocation
164 | } else {
165 | self.driverPin = MKPointAnnotation()
166 | self.driverPin.coordinate = self.driverLocation
167 | self.driverPin.title = "You"
168 | self.map.addAnnotation(self.driverPin)
169 | }
170 | }
171 | }
172 | }
173 |
174 |
175 |
176 | override func viewDidAppear(_ animated: Bool) {
177 | DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {
178 | if(!self.simulatorToRestaurantTimer.isValid && self.restaurantLocation != nil && self.map.isHidden == false){
179 | DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
180 | print("*******************Moving to Restaurant now*******************")
181 | //print("Total distance to travel dx:\(abs(self.restaurantLocation.longitude-self.driverLocation.longitude)) dy:\(abs(self.driverLocation.latitude-self.restaurantLocation.latitude))")
182 | self.simulatorToRestaurantTimer = Timer.scheduledTimer(
183 | timeInterval: self.simulatorFrequency,
184 | target: self,
185 | selector: #selector(self.moveToRestaurant),
186 | userInfo: nil,
187 | repeats: true)
188 | }
189 | }
190 | }
191 |
192 | }
193 |
194 | @objc func loadData() {
195 | APIManager.shared.getCurrentDriverOrder { (json) in
196 | let order = json["order"]
197 | if let id = order["id"].int, order["status"] == "On the way" {
198 | self.lbMessage.text = ""
199 | self.showHideMap(state: "show")
200 | for annotation in self.map.annotations {
201 | //print(annotation)
202 | }
203 | self.orderId = id
204 | //print("Order id retrieved: \(self.orderId)")
205 | //print(order)
206 | let to = order["address"].string!
207 | let from = order["restaurant"]["address"].string!
208 | // let customerName = order["customer"]["name"].string!
209 | self.customerName = order["customer"]["name"].string!
210 | let customerAvatar = order["customer"]["avatar"].string!
211 | self.lbcustomerName.text = self.customerName
212 | self.lbCustomerAddress.text = from
213 |
214 | self.imgCustomerAvatar.image = try! UIImage(data: Data(contentsOf: URL(string: customerAvatar)!))
215 | self.imgCustomerAvatar.layer.cornerRadius = 50/2
216 | self.imgCustomerAvatar.clipsToBounds = true
217 |
218 | self.getLocation(to, "CustomerPin", { (des) in
219 | self.destination = des
220 | if(self.customerPin == nil) {
221 | self.customerPin = MKPointAnnotation()
222 | self.customerPin.coordinate = des.coordinate }
223 | self.customerLocation = des.coordinate
224 | //print("To destination: \(self.destinationLocation) coordinates")
225 |
226 | self.getLocation(from, "RestaurantPin", { (sou) in
227 | self.source = sou
228 | if(self.restaurantPin == nil) {
229 | self.restaurantPin = MKPointAnnotation()
230 | self.restaurantPin.coordinate = sou.coordinate
231 | }
232 | self.restaurantLocation = sou.coordinate
233 | //print("From source: \(self.restaurantLocation) coordinates")
234 | })
235 | })
236 | self.LoadUnloadTimer(timer: &self.updateDriverLocationTimer, phase: "setOn", name: "UpdateLocationTimer", interval: 0.1, function: #selector(self.sendDriverLocationToServer))
237 | //self.LoadUnloadTimer(timer: &self.zoomTimer, phase: "setOn", name: "ZoomTimer", interval: 1.0, function: #selector(self.autoZoom))
238 |
239 | } else {
240 | self.LoadUnloadTimer(timer: &self.updateDriverLocationTimer, phase: "setOff", name: "UpdateLocationTimer", interval: nil, function: nil)
241 | self.LoadUnloadTimer(timer: &self.zoomTimer, phase: "setOff", name: "ZoomTimer", interval: nil, function: nil)
242 | self.showHideMap(state: "hide")
243 | // Showing a message here
244 | self.lbMessage.center = self.view.center
245 | self.lbMessage.textAlignment = NSTextAlignment.center
246 | self.lbMessage.text = "You don't have any orders for delivery."
247 | self.lbMessage.numberOfLines = 0
248 | self.view.addSubview(self.lbMessage)
249 | self.orderId = -1
250 | }
251 | }
252 | }
253 |
254 | @objc func autoZoom() {
255 | //print("AutoZoom called")
256 | var zoomRect = MKMapRect.null
257 | for annotation in self.map.annotations {
258 | let annotationPoint = MKMapPoint(annotation.coordinate)
259 | let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 0.1, height: 0.1)
260 | zoomRect = zoomRect.union(pointRect)
261 | }
262 | let insetWidth = -zoomRect.size.width * 0.2
263 | let insetHeight = -zoomRect.size.height * 0.2
264 | let insetRect = zoomRect.insetBy(dx: insetWidth, dy: insetHeight)
265 | self.map.setVisibleMapRect(insetRect, animated: true)
266 | }
267 |
268 |
269 |
270 | // #1 - Delegate method of MKMapViewDelegate
271 |
272 | func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
273 | let renderer = MKPolylineRenderer(overlay: overlay)
274 | renderer.strokeColor = UIColor.blue
275 | renderer.lineWidth = 5.0
276 | return renderer
277 | }
278 |
279 | // #2 - Convert an address string to a location on the map
280 | func getLocation(_ address: String,_ title: String,_ completionHandler: @escaping (MKPlacemark) -> Void) {
281 | let geocoder = CLGeocoder()
282 | geocoder.geocodeAddressString(address) { (placemarks, error) in
283 | if (error != nil) {
284 | print("Error: ", error as Any)
285 | print("error: \(error as Any)")
286 | }
287 | if let placemark = placemarks?.first {
288 | let coordinates: CLLocationCoordinate2D = placemark.location!.coordinate
289 | // Create a pin
290 | let dropPin = MKPointAnnotation()
291 | dropPin.coordinate = coordinates
292 | dropPin.title = title
293 | self.map.addAnnotation(dropPin)
294 | completionHandler(MKPlacemark.init(placemark: placemark))
295 | }
296 | }
297 | }
298 |
299 | @objc func moveToRestaurant(){
300 | moveTo_(self.restaurantLocation, "Restaurant")
301 | }
302 |
303 | @objc func moveToCustomer(){
304 | moveTo_(self.customerLocation, "Customer")
305 | }
306 |
307 | func moveTo_(_ destination: CLLocationCoordinate2D,_ name: String){
308 | //print("****MOVING******")
309 | self.moving = true
310 | var dx = abs(self.driverLocation.longitude-destination.longitude)
311 | var dy = abs(self.driverLocation.latitude-destination.latitude)
312 | if(dx > maxd && dy > maxd){
313 | self.moveX(destination)
314 | self.moveY(destination)
315 | dx = abs(self.driverLocation.longitude-destination.longitude)
316 | dy = abs(self.driverLocation.latitude-destination.latitude)
317 | //print("New Location: \(self.driverLocation)")
318 | } else if(dx > maxd){
319 | self.moveX(destination)
320 | dx = abs(self.driverLocation.longitude-destination.longitude)
321 | } else if(dy > maxd){
322 | self.moveY(destination)
323 | dy = abs(self.driverLocation.latitude-destination.latitude)
324 | } else{
325 | print("___________Driver arrived to \(name)___________")
326 | if(name == "Restaurant"){
327 | self.simulatorToRestaurantTimer.invalidate()
328 | self.moving = false
329 | DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
330 | let degreeKMToMiles = 111.111 * 0.621371
331 | print("Moving to Customer now")
332 | // let distanceX = abs(self.customerLocation.longitude-self.driverLocation.longitude) * degreeKMToMiles
333 | // let distanceY = abs(self.driverLocation.latitude-self.customerLocation.latitude) * degreeKMToMiles
334 | //print(String(format:"Total distance to travel dx: %.2f dy: %.2f",distanceX, distanceY))
335 | self.simulatorToCustomerTimer = Timer.scheduledTimer(
336 | timeInterval: self.simulatorFrequency,
337 | target: self,
338 | selector: #selector(self.moveToCustomer),
339 | userInfo: nil,
340 | repeats: true)
341 | }
342 | } else if (name == "Customer"){
343 | self.simulatorToCustomerTimer.invalidate()
344 | self.moving = false
345 | }
346 | }
347 | }
348 |
349 | func moveX(_ destination: CLLocationCoordinate2D){
350 | let dx = abs(self.driverLocation.longitude-destination.longitude)
351 | let rx = Double(arc4random_uniform(10)+1)
352 | if(dx > maxd){
353 | if(self.driverLocation.longitude > destination.longitude){
354 | self.driverLocation.longitude -= delta * rx
355 | } else { self.driverLocation.longitude += delta * rx }
356 | }
357 | }
358 | func moveY(_ destination: CLLocationCoordinate2D){
359 | let dy = abs(self.driverLocation.latitude-destination.latitude)
360 | let ry = Double(arc4random_uniform(10)+1)
361 | if(dy > maxd){
362 | if(self.driverLocation.latitude > destination.latitude){
363 | self.driverLocation.latitude -= delta * ry
364 | } else { self.driverLocation.latitude += delta * ry }
365 | }
366 | }
367 | //Location
368 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
369 | print(locations)
370 | let location = locations.last! as CLLocation
371 | self.driverLocation = location.coordinate
372 | print("Location Manager Called, current location: 4\(location.coordinate)")
373 | // print("location")
374 | // print(location.coordinate)
375 | // Create pin annotation for Driver
376 | if driverPin != nil {
377 | driverPin.coordinate = self.driverLocation
378 | } else {
379 | driverPin = MKPointAnnotation()
380 | driverPin.coordinate = self.driverLocation
381 | self.map.addAnnotation(driverPin)
382 | }
383 | driverPin.title = "You"
384 | //All 3 locations
385 | var zoomRect = MKMapRect.null
386 | for annotation in self.map.annotations {
387 | let annotationPoint = MKMapPoint(annotation.coordinate)
388 | let pointRect = MKMapRect(x: annotationPoint.x, y: annotationPoint.y, width: 10, height: 10)
389 | zoomRect = zoomRect.union(pointRect)
390 | }
391 |
392 | let insetWidth = -zoomRect.size.width //* 0.2
393 | let insetHeight = -zoomRect.size.height //* 0.2
394 | let insetRect = zoomRect.insetBy(dx: insetWidth, dy: insetHeight)
395 |
396 | self.map.setVisibleMapRect(insetRect, animated: true)
397 | }
398 |
399 | //Complete Order Button Action
400 | @IBAction func completeOrder(_ sender: Any) {
401 |
402 | if(canProceedWithOrder("CompleteOrder")){
403 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
404 | let okAction = UIAlertAction(title: "OK", style: .default) { (action) in
405 | APIManager.shared.compeleteOrder(orderId: self.orderId!, completionHandler: { (json) in
406 | print("__________OrderCOMplete Fuction________")
407 | print(json)
408 | if json != nil {
409 | // Stop updating driver location
410 | // self.updateDriverLocationTimer.invalidate()
411 | // self.locationManager.stopUpdatingLocation()
412 | // Redirect driver to the Ready Orders View
413 | self.map.removeAnnotation(self.restaurantPin)
414 | self.map.removeAnnotation(self.customerPin)
415 | self.performSegue(withIdentifier: "ViewOrdersSegue", sender: "AvailableOrders")
416 | }
417 | })
418 | }
419 | let alertView = UIAlertController(title: "Complete Order", message: "Please hand the order to \(self.customerName) before completing", preferredStyle: .alert)
420 | alertView.addAction(cancelAction)
421 | alertView.addAction(okAction)
422 |
423 | self.present(alertView, animated: true, completion: nil)
424 | } else {
425 | print("Cannot complete order")
426 | let cancelAction = UIAlertAction(title: "Okay", style: .cancel)
427 | // let okAction = UIAlertAction(title: "Go to order", style: .default, handler: { (action) in
428 | // self.performSegue(withIdentifier: "ViewOrder", sender: self)
429 | // })
430 | let alertView = UIAlertController(title: "You're not there yet", message: "Your location tells us you're not close enough to the customer's address", preferredStyle: .alert)
431 | //alertView.addAction(okAction)
432 | alertView.addAction(cancelAction)
433 | self.present(alertView, animated: true, completion: nil)
434 | }
435 | }
436 |
437 | func canProceedWithOrder(_ phase: String)->Bool{
438 | if(self.customerLocation == nil || self.restaurantLocation == nil || self.driverLocation == nil ){ return false }
439 | let location : CLLocationCoordinate2D
440 | if(phase == "CompleteOrder"){
441 | location = self.customerLocation
442 | } else if(phase == "Pickup") {
443 | location = self.restaurantLocation
444 | } else {
445 | return false;
446 | }
447 | let dx = abs(self.driverLocation.longitude-location.longitude)
448 | let dy = abs(self.driverLocation.latitude-location.latitude)
449 | if(dx < maxd && dy < maxd){
450 | return true
451 | }
452 | return false
453 | }
454 | func showHideMap(state: String){
455 | if(state == "show"){
456 | self.map.isHidden = false
457 | self.viewInfo.isHidden = false
458 | self.bcomplete.isHidden = false
459 | } else if(state == "hide"){
460 | self.map.isHidden = true
461 | self.viewInfo.isHidden = true
462 | self.bcomplete.isHidden = true
463 | }
464 | }
465 | func setDriverLocationTo(){
466 | var newLocation = CLLocationCoordinate2D.init()
467 | newLocation.latitude = 40.6882
468 | newLocation.longitude = -73.9642
469 | print("New Current Location set to: \(newLocation)")
470 | self.driverLocation = newLocation
471 | // let x = customerLocation.longitude
472 | // let y = customerLocation.latitude
473 | //print("Destination x=\(x) y=\(y)")
474 | }
475 | }
476 |
477 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Driver/DriversOrderViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DriversOrderViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class DriversOrderViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
11 |
12 |
13 | @IBOutlet weak var userWelcomeLabel: UILabel!
14 | @IBOutlet weak var tbvDriverOrder: UITableView!
15 | //variables
16 | var orders = [DriverOrder]()
17 | var loadOrdersTimer = Timer()
18 |
19 |
20 |
21 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
22 | let destination = sender as? String
23 | if(destination == "CurrentDelivery"){
24 | tabBarController?.selectedIndex = 1
25 | }
26 | }
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 | tbvDriverOrder.dataSource = self
30 | tbvDriverOrder.delegate = self
31 | userWelcomeLabel.text = User.currenUser.name
32 |
33 | }
34 | override func viewWillAppear(_ animated: Bool) {
35 | LoadUnloadTimer(state: "setOn")
36 |
37 | }
38 | override func viewWillDisappear(_ animated: Bool) {
39 | LoadUnloadTimer(state: "setOff")
40 |
41 | }
42 |
43 |
44 | func LoadUnloadTimer(state: String){
45 | if(self.loadOrdersTimer.isValid && state == "setOff"){
46 | loadOrdersTimer.invalidate()
47 |
48 | } else if(!self.loadOrdersTimer.isValid && state == "setOn"){
49 | setLoadOrdersTimer()
50 | }
51 | }
52 | func setLoadOrdersTimer(){
53 | loadReadyOrders()
54 | if (!loadOrdersTimer.isValid){
55 | loadOrdersTimer = Timer.scheduledTimer(
56 | timeInterval: 5.0,
57 | target: self,
58 | selector: #selector(loadReadyOrders),
59 | userInfo: nil,
60 | repeats: true)
61 | }
62 | }
63 | @objc func loadReadyOrders() {
64 | print("Orders Loaded")
65 | APIManager.shared.getDriverOrders{(json) in
66 | if json != nil {
67 | self.orders = []
68 | if let readyOrders = json["orders"].array {
69 | for item in readyOrders {
70 | //print(item)
71 | let order = DriverOrder(json: item)
72 | self.orders.append(order)
73 | }
74 | }
75 | self.tbvDriverOrder.reloadData()
76 | }
77 | }
78 | }
79 | //Picking Order: Will changen to Pick uo only if close enough
80 | //Also allow to pick up from far away, but can only deliver to customer once order has been "Picked Up"
81 | private func pickOrder(orderId: Int) {
82 | print("____________pickOrderFunction Pressed")
83 | APIManager.shared.pickOrder(orderId: orderId) { (json) in
84 | if let status = json["status"].string {
85 | switch status {
86 | case "failed":
87 | let alertView = UIAlertController(title: "Error", message: json["error"].string!, preferredStyle: .alert)
88 | let cancelAction = UIAlertAction(title: "OK", style: .cancel)
89 | alertView.addAction(cancelAction)
90 | self.present(alertView, animated: true, completion: nil)
91 | default:
92 | // Showing an alert saying Success
93 | let alertView = UIAlertController(title: nil, message: "Success!", preferredStyle: .alert)
94 | let okAction = UIAlertAction(title: "Show my map", style: .default, handler: { (action) in
95 | self.performSegue(withIdentifier: "CurrentDeliverySegue", sender: "CurrentDelivery")
96 | })
97 | alertView.addAction(okAction)
98 | self.present(alertView, animated: true, completion: nil)
99 | }
100 | }
101 | }
102 | }
103 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
104 | return orders.count
105 | }
106 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
107 | let cell = tableView.dequeueReusableCell(withIdentifier: "DriversOrderCell") as! DriversOrderCell
108 | let order = orders[indexPath.row]
109 | cell.lbRestaurantName.text = order.restaurantName
110 | cell.lbCustomerName.text = order.customerName
111 | cell.lbCustomerAddress.text = order.customerAddress
112 | cell.imgCustomerAvatar.image = try! UIImage(data: Data(contentsOf: URL(string: order.customerAvatar!)!))
113 | cell.imgCustomerAvatar.layer.cornerRadius = 50/2
114 | return cell
115 | }
116 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
117 | return 125
118 | }
119 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
120 | let order = orders[indexPath.row]
121 | self.pickOrder(orderId: order.id!)
122 | }
123 |
124 |
125 |
126 |
127 | }
128 |
129 |
130 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Driver/DriversProfileViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DriversProfileViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/7/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class DriversProfileViewController: UIViewController {
11 |
12 |
13 | @IBOutlet weak var driversAvatar: UIImageView!
14 | @IBOutlet weak var lbLastName: UILabel!
15 | @IBOutlet weak var lbFirstName: UILabel!
16 | @IBOutlet weak var lbEmail: UILabel!
17 |
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 |
22 | configureUserProfile()
23 | configure()
24 | }
25 |
26 | func configure() {
27 | driversAvatar.clipsToBounds = true
28 | driversAvatar.layer.cornerRadius = driversAvatar.bounds.height/2
29 | }
30 |
31 | func configureUserProfile() {
32 | let fullName = User.currenUser.name!
33 | let components = fullName.components(separatedBy: " ")
34 |
35 | lbFirstName.text = components.first
36 | lbLastName.text = components.last
37 |
38 | lbEmail.text = User.currenUser.email
39 |
40 | //TODO: Split the name
41 |
42 | driversAvatar.image = try! UIImage(data: Data (contentsOf: URL(string: User.currenUser.pictureURL!)!))
43 | }
44 |
45 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
46 | if segue.identifier == "DriverLogout" {
47 | // APIManager.shared.logout(completionHandler:{ (error) in
48 | // if error == nil {
49 | FBManager.shared.logOut()
50 | User.currenUser.resetInfo()
51 | print("logggin out")
52 | // }
53 |
54 | // })
55 |
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Controllers/Driver/RevenueViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RevenueViewController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 | import Charts
10 |
11 | class RevenueViewController: UIViewController {
12 |
13 | @IBOutlet weak var viewChart: BarChartView!
14 |
15 | // var chart: BarChartView!
16 |
17 | var weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
18 |
19 |
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | //
23 |
24 | // #1 Initialize chart
25 | self.initializeChart()
26 |
27 | // #2 Load data to chart
28 | self.loadDataToChart()
29 |
30 | // Do any additional setup after loading the view.
31 | }
32 |
33 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
34 | let tabBarController = segue.destination as! UITabBarController
35 | let destination = sender as? String
36 | if(destination == "toProfile"){
37 | tabBarController.selectedIndex = 3
38 | }
39 | }
40 |
41 |
42 |
43 | func initializeChart() {
44 |
45 | viewChart.noDataText = "No Data"
46 | viewChart.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInCubic)
47 | viewChart.xAxis.labelPosition = .bottom
48 | viewChart.chartDescription?.text = ""
49 | // viewChart.descriptionText = ""
50 | // viewChart.xAxis.setLabelsToSkip(0)
51 |
52 | viewChart.legend.enabled = false
53 | viewChart.scaleYEnabled = false
54 | viewChart.scaleXEnabled = false
55 | viewChart.pinchZoomEnabled = false
56 | viewChart.doubleTapToZoomEnabled = false
57 |
58 | viewChart.leftAxis.axisMinimum = 0.0
59 | // viewChart.leftAxis.axisMaximum = 100.00
60 | viewChart.highlighter = nil
61 | viewChart.rightAxis.enabled = false
62 | viewChart.xAxis.drawGridLinesEnabled = false
63 |
64 | }
65 |
66 | func loadDataToChart() {
67 |
68 | APIManager.shared.getDriverRevenue { (json) in
69 |
70 | if json != nil {
71 | print(json)
72 | let revenue = json["revenue"]
73 |
74 | var dataEntries: [BarChartDataEntry] = []
75 | print("___DATA ENTRY_________")
76 | print(dataEntries)
77 |
78 | for i in 0..>>>>>> main
46 | viewChart.legend.enabled = false
47 | viewChart.scaleYEnabled = false
48 | viewChart.scaleXEnabled = false
49 | viewChart.pinchZoomEnabled = false
50 | viewChart.doubleTapToZoomEnabled = false
51 |
52 | viewChart.leftAxis.axisMinimum = 0.0
53 | viewChart.leftAxis.axisMaximum = 100.00
54 | viewChart.highlighter = nil
55 | viewChart.rightAxis.enabled = false
56 | viewChart.xAxis.drawGridLinesEnabled = false
57 |
58 | }
59 |
60 |
61 |
62 | func loadDataToChart() {
63 |
64 | APIManager.shared.getDriverRevenue { (json) in
65 |
66 | if json != nil {
67 |
68 | print(json)
69 |
70 | let revenue = json["revenue"]
71 |
72 | var dataEntries: [BarChartDataEntry] = []
73 |
74 | for i in 0..
2 |
3 |
4 |
5 | FacebookAdvertiserIDCollectionEnabled
6 |
7 | FacebookAutoLogAppEventsEnabled
8 |
9 | CFBundleURLTypes
10 |
11 |
12 | CFBundleURLSchemes
13 |
14 | fb4544345565654675
15 |
16 |
17 |
18 | FacebookAppID
19 | 4544345565654675
20 | FacebookClientToken
21 | CLIENT-TOKEN
22 | FoodDelivery
23 | APP-NAME
24 | UIApplicationSceneManifest
25 |
26 | UIApplicationSupportsMultipleScenes
27 |
28 | UISceneConfigurations
29 |
30 | UIWindowSceneSessionRoleApplication
31 |
32 |
33 | UISceneConfigurationName
34 | Default Configuration
35 | UISceneDelegateClassName
36 | $(PRODUCT_MODULE_NAME).SceneDelegate
37 | UISceneStoryboardFile
38 | Main
39 |
40 |
41 |
42 |
43 | NSAppTransportSecurity
44 |
45 | NSAllowsArbitraryLoads
46 |
47 |
48 | NSLocationAlwaysUsageDescription
49 | This application requires location services to work
50 | NSLocationAlwaysAndWhenInUseUsageDescription
51 | This application requires location services to work
52 | NSLocationWhenInUseUsageDescription
53 | This application requires location services to work
54 |
55 |
56 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Manager/APIConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIConstants.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Orlando Vargas on 11/12/21.
6 | //
7 |
8 | import Foundation
9 |
10 | struct APIConstants {
11 | struct URL {
12 | static let BASE_URL: String = "https://fooddeliverynowapp.herokuapp.com/"
13 | }
14 |
15 | struct Client {
16 | static let ID = "Ij7nN3zWD1VEAtD3Zpv0dyIFzuF4eesM6xHzRHMK"
17 | static let SKEY = "M6FL8MLOMwcNrHnRA1iE0zGeA0lj2tc0xEtw7p5xzmjvbyY3pY2hrDAsBuXHi7LG5fxCRRXr8pVjxpfeiQhJYgLKY8HAZfIwADKD7uvqGq7QnEvKlik9iuWcGKhqZ7zF"
18 | }
19 |
20 | struct Stripe {
21 | static let PKEY = "pk_test_51HrVG3I0pSVz7qhzEY2QqtUIEExOLgxPUNg6DCif6ioIXwD5bkzGazpkgCr8vxf2CR3ALwgsUCDzArymDdUIZ00E00H73KAHLA"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Manager/APIManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIManager.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import Foundation
9 | import Alamofire
10 | import SwiftyJSON
11 | import FBSDKLoginKit
12 | import MapKit
13 |
14 | class APIManager {
15 |
16 | static let shared = APIManager()
17 |
18 | let baseURL = NSURL(string: BASE_URL)
19 | let defaults = UserDefaults.standard
20 |
21 | var accessToken = ""
22 | var refreshToken = ""
23 | var timeLeft = 0
24 | var expirationDate = Date()
25 | var tokenAvailable = false
26 | var userType = ""
27 |
28 | func resetUserDefaults(){
29 | // for (key, value) in UserDefaults.standard.dictionaryRepresentation() {
30 | // print("\(key) = \(value) \n")
31 | // }
32 | print("REMOVED IMPORTANT KEYS")
33 | defaults.removeObject(forKey: "access_token")
34 | defaults.removeObject(forKey: "refresh_token")
35 | defaults.removeObject(forKey: "time_left")
36 | defaults.removeObject(forKey: "expiration_date")
37 | defaults.removeObject(forKey: "user_type")
38 | print(defaults.value(forKey: "access_token"))
39 | print(defaults.value(forKey: "accessToken"))
40 | print(defaults.value(forKey: "refresh_token"))
41 | print(defaults.value(forKey: "time_left"))
42 | print(defaults.value(forKey: "expiration_date"))
43 | print(defaults.value(forKey: "user_type"))
44 | }
45 |
46 | func populateFromDefaults(){
47 | accessToken = defaults.value(forKey: "access_token") as! String
48 | refreshToken = defaults.string(forKey: "refresh_token") as! String
49 | timeLeft = defaults.value(forKey: "time_left") as! Int
50 | expirationDate = defaults.value(forKey: "expiration_date") as! Date
51 | userType = defaults.value(forKey: "user_type") as! String
52 | }
53 |
54 | func checkTokens() -> Bool {
55 |
56 | // print("checking TokenStatus")
57 | // print("API Global variables accessToken: \(accessToken) -- refreshToken: \(refreshToken)")
58 | // print("UserDfault variables accessToken: \(defaults.value(forKey: "access_token")) -- refreshToken: \(defaults.value(forKey: "refresh_token"))")
59 |
60 | //print("Facebook Auth token: \((AccessToken.current?.tokenString)!)\nExpires in: \((AccessToken.current?.expirationDate)!)")
61 |
62 | let fbAuthTk = (AccessToken.current?.tokenString)!
63 | let fbAuthTkTime = Int((AccessToken.current?.expirationDate as! Date).timeIntervalSinceNow) / (3600 * 24)
64 | //print("Facebook Auth token: \(fbAuthTk)\nExpires in: \(fbAuthTkTime) days")
65 |
66 | // let access_token = defaults.value(forKey: "access_token")!
67 | // print("access_token: \(access_token)")
68 | // let refresh_token = defaults.value(forKey: "refreshToken")!
69 | // print("rewfresh-token: \(refresh_token)")
70 | // let time_left = defaults.value(forKey: "timeLeft")!
71 | // print("Tokens time left in minutes: \(time_left)")
72 | // let expiration_date = defaults.value(forKey: "expirationDate")!
73 | // print("Tokens expiration date \(expiration_date)")
74 |
75 | if(AccessToken.isCurrentAccessTokenActive){
76 |
77 | self.timeLeft = getTokenTimeLeft()
78 |
79 | if(self.timeLeft > 59){ // Tokens have an hour left of life
80 | //print("Token life greater than 59 min")
81 | return true
82 | } else{
83 | //print("Token life less than 59 min")
84 |
85 | // getNewToken(user_type: userType, completionHandler : {
86 | // (error) in
87 | // if error == nil {
88 | // }
89 | // })
90 | return false
91 | }
92 | // // another way of displaying time Data of Facebook Access Token, unnecessary but helpful to visualize
93 | // let expDate = AccessToken.current?.expirationDate
94 | // let formatter = DateComponentsFormatter()
95 | // formatter.allowedUnits = [.day, .hour, .minute]
96 | // let timeInMinHour = formatter.string(from: Date.now, to: expDate!)
97 | // print("time in days hr:min = \(timeInMinHour!)")
98 |
99 | // let timeLeftHours = Int(expDate!.timeIntervalSinceNow - Date.now.timeIntervalSinceNow) / (60 * 60)
100 | // print("Facebook access token expires in \(timeLeftInteger) hours")
101 |
102 | }
103 | print("should never print, fb token inactive")
104 | return false
105 |
106 | }
107 |
108 | func getTokenTimeLeft()->Int{
109 | // interval is given in seconds, we want to look at the minutes left
110 | //return Int(self.expirationDate.timeIntervalSinceNow) / 60
111 | //let time = defaults.value(forKey: "time_left") as? Int ?? 0
112 | let date = defaults.value(forKey: "expiration_date") as? Date ?? Date.now
113 | let time = Int((date as! Date).timeIntervalSinceNow) / (60)
114 | print("Get Token Time Left: \(time) minutes")
115 | return time
116 | }
117 |
118 | func populateUserDefaults(){
119 | self.defaults.set(self.timeLeft, forKey: "time_left")
120 | self.defaults.set(self.accessToken, forKey: "access_token")
121 | self.defaults.set(self.refreshToken, forKey: "refresh_token")
122 | self.defaults.set(self.expirationDate, forKey: "expiration_date")
123 | self.defaults.set(self.userType, forKey: "user_type")
124 | }
125 |
126 | func getNewToken(){
127 | print("getting new token")
128 | let path = "api/social/convert-token/"
129 | let url = baseURL!.appendingPathComponent(path)
130 | let params: [String: Any] = [
131 | "grant_type": "convert_token",
132 | "client_id" : APIConstants.Client.ID,
133 | "client_secret" : APIConstants.Client.SKEY,
134 | "backend" : "facebook",
135 | "token" : AccessToken.current!.tokenString,
136 | "user_type" : userType,
137 | ]
138 | //print("________________User Type: \(params["user_type"]!)__________________________")
139 |
140 | AF.request(url!, method: .post, parameters: params, encoding: JSONEncoding.default).responseJSON { [self]
141 | (response) in
142 | switch response.result {
143 | case .success(let value):
144 | let jsonData = JSON(value)
145 | self.accessToken = jsonData["access_token"].string!
146 | self.refreshToken = jsonData["refresh_token"].string!
147 | self.expirationDate = Date().addingTimeInterval(TimeInterval(jsonData["expires_in"].int!))
148 | self.timeLeft = getTokenTimeLeft()
149 | self.userType = params["user_type"] as! String
150 | // print("_____________________________________________________________")
151 | // print("Token jsonData: \(jsonData)")
152 | // print("Access and Refresh Tokens expire in \(jsonData["expires_in"].int! / 60) minutes")
153 | // print("Self.expired = \(self.expirationDate)")
154 | // print("Date Now = \(Date.now)")
155 | // print("json expires in = \(jsonData["expires_in"])")
156 | // print("_____________________________________________________________")
157 |
158 | populateUserDefaults()
159 |
160 | print("_______________Success___________________")
161 | break
162 |
163 | case .failure(let error):
164 | print("________EROR______")
165 | break
166 | }
167 | }
168 | }
169 |
170 | func getToken(user_type: String, completitionHandler: @escaping (NSError?) -> Void){
171 | let path = "api/social/convert-token/"
172 | let url = baseURL!.appendingPathComponent(path)
173 | let params: [String: Any] = [
174 | "grant_type": "convert_token",
175 | "client_id" : APIConstants.Client.ID,
176 | "client_secret" : APIConstants.Client.SKEY,
177 | "backend" : "facebook",
178 | "token" : AccessToken.current!.tokenString,
179 | "user_type" : user_type,
180 | ]
181 | //print("________________User Type: \(params["user_type"]!)__________________________")
182 | //Using alamofire for the request
183 | AF.request(url!, method: .post, parameters: params, encoding: JSONEncoding.default).responseJSON { [self]
184 | (response) in
185 | switch response.result {
186 | case .success(let value):
187 | let jsonData = JSON(value)
188 | self.accessToken = jsonData["access_token"].string!
189 | self.refreshToken = jsonData["refresh_token"].string!
190 | self.expirationDate = Date().addingTimeInterval(TimeInterval(jsonData["expires_in"].int!))
191 | self.timeLeft = getTokenTimeLeft()
192 | self.userType = user_type
193 | // print("_____________________________________________________________")
194 | // print("Token jsonData: \(jsonData)")
195 | // print("Access and Refresh Tokens expire in \(jsonData["expires_in"].int! / 60) minutes")
196 | // print("Self.expired = \(self.expirationDate)")
197 | // print("Date Now = \(Date.now)")
198 | // print("json expires in = \(jsonData["expires_in"])")
199 | // print("_____________________________________________________________")
200 |
201 | populateUserDefaults()
202 |
203 | completitionHandler(nil)
204 | print("_______________Success___________________")
205 | break
206 |
207 |
208 | case .failure(let error):
209 | completitionHandler(error as NSError)
210 | print("________EROR______")
211 | break
212 | }
213 | }
214 | }
215 |
216 | //APi to login the user
217 | func login(user_type: String, completitionHandler: @escaping (NSError?) -> Void) {
218 |
219 | print("Loging User: API Manager")
220 |
221 | tokenAvailable = checkTokens()
222 |
223 | if(!tokenAvailable){
224 | print("token not available for reuse")
225 | getToken(user_type: user_type, completitionHandler: completitionHandler)
226 | } else{
227 | populateFromDefaults()
228 | completitionHandler(nil)
229 | }
230 | }
231 |
232 | //Aoi to logout the user
233 | func logout(completionHandler: @escaping (NSError?) -> Void) {
234 | resetUserDefaults()
235 | let path = "api/social/revoke-token/"
236 | let url = baseURL!.appendingPathComponent(path)
237 | print(url)
238 | // let headers : HTTPHeaders = [
239 | // "Content-Type" : "application/x-www-form-urlencoded"
240 | // ]
241 | let params: [String: Any] = [
242 | "client_id" : APIConstants.Client.ID,
243 | "client_secret" : APIConstants.Client.SKEY,
244 | "token" : self.accessToken,
245 | ]
246 | //print(self.accessToken)
247 | // Alamofire for the requests
248 | AF.request(url!, method: .post, parameters: params, encoding: URLEncoding(), headers: nil).responseJSON{(response) in
249 |
250 | // switch response.result {
251 | //
252 | // case .success:
253 | // //print("__________success...__________")
254 | // completionHandler(nil)
255 | // print("__________LOGOUT SUCCESSFUL__________")
256 | // break
257 | //
258 | // case .failure(let error):
259 | // completionHandler(error as NSError?)
260 | // //print("__________LOGOUT EROR__________")
261 | // }
262 | switch response.result {
263 | case .success(let value):
264 | let jsonData = JSON(value)
265 | completionHandler(nil)
266 | print("__________________________________________")
267 | print(jsonData)
268 | //
269 |
270 | case .failure(let error):
271 | completionHandler(error as NSError?)
272 | print("Failed")
273 | }
274 | }
275 | }
276 |
277 |
278 |
279 |
280 | // shold update user defualts too when refreshing token
281 |
282 | // API to refresh the token when it's expired
283 | func refreshTokenIfNeed(completionHandler: @escaping () -> Void) {
284 |
285 | let path = "api/social/refresh-token/"
286 | let url = baseURL?.appendingPathComponent(path)
287 | let params: [String: Any] = [
288 | "access_token": self.accessToken,
289 | "refresh_token": self.refreshToken
290 | ]
291 |
292 | if (Date() > self.expirationDate) {
293 |
294 | AF.request(url!, method: .post, parameters: params, encoding: JSONEncoding.default).responseJSON(completionHandler: { (response) in
295 |
296 | switch response.result {
297 | case .success(let value):
298 | let jsonData = JSON(value)
299 | self.accessToken = jsonData["access_token"].string!
300 | self.expirationDate = Date().addingTimeInterval(TimeInterval(jsonData["expires_in"].int!))
301 | completionHandler()
302 | break
303 |
304 | case .failure:
305 | break
306 | }
307 | })
308 | } else {
309 | completionHandler()
310 | }
311 | }
312 |
313 |
314 | // Get restaurants List
315 |
316 | func getRestaurants(completionHandler: @escaping (JSON?) -> Void){
317 | let path = "api/customer/restaurants/"
318 | let url = baseURL?.appendingPathComponent(path)
319 |
320 | AF.request(url!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON{ response in
321 |
322 | switch response.result {
323 | case .success(let value):
324 | let jsonData = JSON(value)
325 | completionHandler(jsonData)
326 | break
327 |
328 | case .failure:
329 | completionHandler(nil)
330 | break
331 | }
332 | }
333 | print("___________________RESTAURANTS_______________________")
334 | }
335 |
336 | // Get restaurants List
337 |
338 | func getMeals(resturantId: Int, completionHandler: @escaping (JSON?) -> Void){
339 | let path = "api/customer/meals/\(resturantId)"
340 | let url = baseURL?.appendingPathComponent(path)
341 |
342 | AF.request(url!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON{ response in
343 |
344 | switch response.result {
345 | case .success(let value):
346 | let jsonData = JSON(value)
347 | completionHandler(jsonData)
348 | break
349 |
350 | case .failure:
351 | completionHandler(nil)
352 | break
353 | }
354 | }
355 | // print("___________________MEALS______________________")
356 | // print("_______________________________________________________")
357 | }
358 |
359 |
360 |
361 |
362 |
363 | //
364 | // Request Server function
365 | func requestServer(_ method: Alamofire.HTTPMethod,_ path: String,_ params: [String: Any]?,_ encoding: ParameterEncoding,_ completionHandler: @escaping (JSON) -> Void ) {
366 | //print("Request Server from API Manager")
367 | let url = baseURL?.appendingPathComponent(path)
368 | //print("In req server")
369 | AF.request(url!, method: method, parameters: params, encoding: JSONEncoding.default, headers: nil).responseJSON{ response in
370 | switch response.result {
371 | case .success(let value):
372 | let jsonData = JSON(value)
373 | //print(jsonData)
374 | //print("____requestServer success____")
375 | completionHandler(jsonData)
376 | break
377 | case .failure:
378 | print("reqServer Failed")
379 | break
380 | }
381 | }
382 |
383 | }
384 |
385 |
386 | // API - Creating new order
387 | // func createOrder(stripeToken: String, completionHandler: @escaping (JSON) -> Void) {
388 | //
389 | // let path = "api/customer/order/add/"
390 | // let simpleArray = Cart.currentCart.items
391 | // let jsonArray = simpleArray.map { item in
392 | // return [
393 | // "meal_id": item.meal.id!,
394 | // "quantity": item.qty
395 | // ]
396 | // }
397 | //
398 | // if JSONSerialization.isValidJSONObject(jsonArray) {
399 | //
400 | // do {
401 | //
402 | // let data = try JSONSerialization.data(withJSONObject: jsonArray, options: [])
403 | // let dataString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)!
404 | //
405 | // let params: [String: Any] = [
406 | // "access_token": self.accessToken,
407 | // "stripe_token": stripeToken,
408 | // "restaurant_id": "\(Cart.currentCart.restaurant!.id!)",
409 | // "order_details": dataString,
410 | // "address": Cart.currentCart.address!
411 | // ]
412 | // print("___________________URL_______________________")
413 | // print("_______________________________________________________")
414 | // print(data)
415 | //
416 | // requestServer(.post, path, params, URLEncoding(), completionHandler)
417 | //
418 | // }
419 | // catch {
420 | // print("JSON serialization failed: \(error)")
421 | // }
422 | // }
423 | // }
424 |
425 | // API - Getting the latest order (Customer)
426 | // func getLatestOrder(completionHandler: @escaping (JSON) -> Void) {
427 | //
428 | // let path = "api/customer/order/latest/"
429 | // let params: [String: Any] = [
430 | // "access_token": self.accessToken
431 | // ]
432 | //
433 | // print("___________________URL_______________________")
434 | // print("_______________________________________________________")
435 | // print(accessToken)
436 | //
437 | // requestServer(.get, path, params, URLEncoding(), completionHandler)
438 | //
439 | // print(path)
440 | // }
441 |
442 | //Creating New Order Payment
443 | func createOrder(stripeToken: String, completionHandler: @escaping (JSON) -> Void){
444 | print("Create Order from API Manager")
445 | let path = "api/customer/order/add/"
446 | let url = baseURL?.appendingPathComponent(path)
447 | //print("___________________Create Order URL_______________________")
448 | print(url!)
449 | let simpleArray = Cart.currentCart.items
450 | let jsonArray = simpleArray.map { item in
451 | return [
452 | "meal_id": item.meal.id!,
453 | "quantity": item.qty
454 | ]
455 | }
456 | if JSONSerialization.isValidJSONObject(jsonArray) {
457 | do {
458 | let data = try JSONSerialization.data(withJSONObject: jsonArray, options: [])
459 | let dataString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)!
460 | let params: [String: Any] = [
461 | //"access_token": self.accessToken,
462 | "access_token" : accessToken,
463 | "stripe_token": stripeToken,
464 | "restaurant_id": "\(Cart.currentCart.restaurant!.id!)",
465 | "order_details": dataString,
466 | "address": Cart.currentCart.address!
467 | ]
468 | // print(accessToken)
469 | // print(stripeToken)
470 | // print("\(Cart.currentCart.restaurant!.id!)")
471 | // print(dataString)
472 | // print(Cart.currentCart.address!)
473 | //requestServer(.post, path, params, URLEncoding(), completionHandler)
474 | //testing request
475 | AF.request(url!, method: .post, parameters: params, encoding: URLEncoding.default).responseJSON(completionHandler: { (response) in
476 | switch response.result {
477 | case .success(let value):
478 | let jsonData = JSON(value)
479 | print(value)
480 | completionHandler(jsonData)
481 | Cart.currentCart.reset()
482 | break
483 | case .failure:
484 | print("failure to create order")
485 | break
486 | }
487 | })
488 | //print("___________________CREATE ORDER_______________________")
489 | }
490 | catch {
491 | print("JSON serialization failed: \(error)")
492 | }
493 | }
494 |
495 | }
496 |
497 | //Getting latest Orders from Customer
498 | func getLatestOrder(completionHandler: @escaping (JSON) -> Void) {
499 |
500 | let path = "api/customer/order/latest/"
501 | let url = baseURL?.appendingPathComponent(path)
502 | //print("__________________getLatestOrderStartAPIManager_____________")
503 | //print("________________________URL_______________________")
504 | //print("__________________________________________________")
505 |
506 | //print(url!)
507 | //print(String(describing: "\(url!)"))
508 |
509 | var params: [String: Any] = [
510 | "access_token": self.accessToken
511 | ]
512 | //print(params)
513 |
514 | //requestServer(.get, path, params, URLEncoding(), completionHandler)
515 | //testing request
516 | AF.request(url!, method: .get, parameters: params, encoding: URLEncoding.default).responseJSON(completionHandler: { (response) in
517 |
518 | switch response.result {
519 | case .success(let value):
520 | let jsonData = JSON(value)
521 | //print("____________getLatestOrderSUCCESS_____________")
522 | completionHandler(jsonData)
523 | break
524 |
525 | case .failure:
526 | print("*********getLatestOrderFAILED***********")
527 | print(response)
528 | break
529 | }
530 | })
531 | //print("__________________getLatestOrderEnd_______________")
532 | }
533 |
534 |
535 |
536 |
537 |
538 | //DRIVER FUNCTIONS
539 |
540 | func getDriverOrders(completionHandler: @escaping (JSON) -> Void) {
541 | let path = "api/driver/orders/ready/"
542 | requestServer(.get, path, nil, URLEncoding(), completionHandler)
543 | }
544 |
545 |
546 | func pickOrder(orderId: Int, completionHandler: @escaping (JSON) -> Void) {
547 | let path = "api/driver/order/pick/"
548 | let url = baseURL?.appendingPathComponent(path)
549 | let params: [String: Any] = [
550 | "order_id": "\(orderId)",
551 | "access_token": self.accessToken
552 | ]
553 | print("__________PARAMS_______")
554 | print(accessToken)
555 |
556 | //testing request
557 | AF.request(url!, method: .post, parameters: params, encoding: URLEncoding.default).responseJSON(completionHandler: { (response) in
558 |
559 | switch response.result {
560 | case .success(let value):
561 | let jsonData = JSON(value)
562 | completionHandler(jsonData)
563 | break
564 |
565 | case .failure:
566 | break
567 | }
568 | })
569 |
570 |
571 |
572 | //requestServer(.post, path, params, URLEncoding(), completionHandler)
573 | print("______________________order poick up Success______")
574 | }
575 |
576 |
577 | // Driver Order
578 | func getCurrentDriverOrder(completionHandler: @escaping (JSON) -> Void) {
579 | let path = "api/driver/order/latest/"
580 | let url = baseURL?.appendingPathComponent(path)
581 | let params: [String: Any] = [
582 | "access_token": self.accessToken
583 | ]
584 | AF.request(url!, method: .get, parameters: params, encoding: URLEncoding.default).responseJSON(completionHandler: { (response) in
585 |
586 | switch response.result {
587 | case .success(let value):
588 | let jsonData = JSON(value)
589 | completionHandler(jsonData)
590 | break
591 | case .failure:
592 | print("Driver Current Order FAILURE")
593 | break
594 | }
595 | })
596 | }
597 |
598 |
599 |
600 | func updateLocation(location: CLLocationCoordinate2D, completionHandler: @escaping (JSON) -> Void) {
601 | let path = "api/driver/location/update/"
602 | let url = baseURL?.appendingPathComponent(path)
603 | let params: [String: Any] = [
604 | "access_token": self.accessToken,
605 | "location": "\(location.latitude),\(location.longitude)"
606 | ]
607 | //requestServer(.post, path, params, URLEncoding(), completionHandler)
608 | //testing request
609 | AF.request(url!, method: .post, parameters: params, encoding: URLEncoding.default).responseJSON(completionHandler: { (response) in
610 |
611 | switch response.result {
612 | case .success(let value):
613 | let jsonData = JSON(value)
614 | completionHandler(jsonData)
615 | //print("_____Updating Drivers Location Success_____")
616 | break
617 |
618 | case .failure:
619 | print("Updating Drivers Location FAILURE")
620 | break
621 | }
622 | })
623 |
624 | }
625 |
626 |
627 | func getDriverLocation(completionHandler: @escaping (JSON) -> Void) {
628 | //print("______________________API Manager getDriverLocation start______________")
629 | let path = "api/customer/driver/location/"
630 | let url = baseURL?.appendingPathComponent(path)
631 | let params: [String: Any] = [
632 | "access_token": self.accessToken
633 | ]
634 | AF.request(url!, method: .get, parameters: params, encoding: URLEncoding.default).responseJSON(completionHandler: { (response) in
635 | switch response.result {
636 | case .success(let value):
637 | let jsonData = JSON(value)
638 | completionHandler(jsonData)
639 | break
640 | case .failure:
641 | print("failure")
642 | break
643 | }
644 | })
645 | }
646 | func compeleteOrder(orderId: Int, completionHandler: @escaping (JSON) -> Void) {
647 | let path = "api/driver/order/complete/"
648 | let url = baseURL?.appendingPathComponent(path)
649 | let params: [String: Any] = [
650 | "order_id": "\(orderId)",
651 | "access_token": self.accessToken
652 | ]
653 | //requestServer(.post, path, params, URLEncoding(), completionHandler)
654 |
655 | //testing request
656 | AF.request(url!, method: .post, parameters: params, encoding: URLEncoding.default).responseJSON(completionHandler: { (response) in
657 |
658 | switch response.result {
659 | case .success(let value):
660 | let jsonData = JSON(value)
661 | completionHandler(jsonData)
662 | break
663 |
664 | case .failure:
665 | break
666 | }
667 | })
668 | print("______________________Complete Order Success______")
669 | }
670 |
671 |
672 | func getDriverRevenue(completionHandler: @escaping (JSON) -> Void) {
673 | let path = "api/driver/revenue/"
674 | let url = baseURL?.appendingPathComponent(path)
675 | let params: [String: Any] = [
676 | "access_token": self.accessToken
677 | ]
678 | //requestServer(.get, path, params, URLEncoding(), completionHandler)
679 | //testing request
680 | AF.request(url!, method: .get, parameters: params, encoding: URLEncoding.default).responseJSON(completionHandler: { (response) in
681 |
682 | switch response.result {
683 | case .success(let value):
684 | let jsonData = JSON(value)
685 | completionHandler(jsonData)
686 | break
687 |
688 | case .failure:
689 | break
690 | }
691 | })
692 | print("______________________Driver Revenue______")
693 | }
694 |
695 |
696 | //End Class APIMAnager
697 | }
698 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Manager/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import Foundation
9 |
10 |
11 | let BASE_URL: String = "https://fooddeliverynowapp.herokuapp.com/"
12 | // this code comes from the admin website
13 | //let CLIENT_ID = "Ij7nN3zWD1VEAtD3Zpv0dyIFzuF4eesM6xHzRHMK"
14 | //let CLIENT_SECRET = "M6FL8MLOMwcNrHnRA1iE0zGeA0lj2tc0xEtw7p5xzmjvbyY3pY2hrDAsBuXHi7LG5fxCRRXr8pVjxpfeiQhJYgLKY8HAZfIwADKD7uvqGq7QnEvKlik9iuWcGKhqZ7zF"
15 |
16 | let USER_TYPE_CUSTOMER = "customer"
17 | let USER_TYPE_DRIVER = "driver"
18 |
19 | //let STRIPE_PUBLIC_KEY = "pk_test_51HrVG3I0pSVz7qhzEY2QqtUIEExOLgxPUNg6DCif6ioIXwD5bkzGazpkgCr8vxf2CR3ALwgsUCDzArymDdUIZ00E00H73KAHLA"
20 |
21 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Manager/FBManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FBManager.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import Foundation
9 | import FBSDKLoginKit
10 | import SwiftyJSON
11 |
12 | class FBManager {
13 |
14 | static let shared = LoginManager()
15 |
16 | public class func getFBUserData(compleationHandler: @escaping () -> Void) {
17 | if (AccessToken.current != nil ) {
18 | GraphRequest(graphPath: "me", parameters: ["fields" : "name, email, picture.type(normal)"]).start { (connection, result, error) in
19 | //check if theres any errors
20 | if (error == nil) {
21 | let json = JSON(result!)
22 | //print(json)
23 |
24 | //User data info
25 | User.currenUser.setInfo(json: json)
26 | compleationHandler()
27 | }
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Manager/Helper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Helper.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import Foundation
9 | import FBSDKLoginKit
10 | import SwiftyJSON
11 |
12 |
13 | // Helper method to load image asynchronously
14 |
15 | class Helpers {
16 |
17 | static func loadImage(_ imageView: UIImageView,_ urlString: String) {
18 | let imgURL: URL = URL(string: urlString)!
19 |
20 | URLSession.shared.dataTask(with: imgURL) { (data, response, error) in
21 |
22 | guard let data = data, error == nil else { return}
23 |
24 | DispatchQueue.main.async(execute: {
25 | imageView.image = UIImage(data: data)
26 | })
27 | }.resume()
28 |
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Manager/StringConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringConstants.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Orlando Vargas on 11/12/21.
6 | //
7 |
8 | import Foundation
9 |
10 | struct StringConstants {
11 | struct Title {
12 | static let APP_TITLE = "Delivery App"
13 | }
14 |
15 | struct Headers {
16 |
17 | }
18 |
19 | struct UserType {
20 | static let CUSTOMER = "customer"
21 | static let DRIVER = "driver"
22 | }
23 |
24 | struct ErrorMessages {
25 | static let NO_ADDRESS = "Not listed"
26 | static let NO_PHONE = "Not listed"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Model/Customer/Cart.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Cart.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import Foundation
9 |
10 | class CartItem {
11 |
12 | var meal: Meal
13 | var qty: Int
14 |
15 | init(meal: Meal, qty: Int) {
16 | self.meal = meal
17 | self.qty = qty
18 | }
19 | }
20 |
21 | class Cart {
22 |
23 | static let currentCart = Cart()
24 |
25 | var restaurant: Restaurant?
26 | var items = [CartItem]()
27 | var address: String?
28 |
29 | func getTotal() -> Float {
30 | var total: Float = 0
31 |
32 | for item in self.items {
33 | total = total + Float(item.qty) * item.meal.price!
34 | }
35 |
36 | return total
37 | }
38 |
39 | func reset() {
40 | self.restaurant = nil
41 | self.items = []
42 | self.address = nil
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Model/Customer/Meal.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Meal.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import Foundation
9 | import SwiftyJSON
10 |
11 | class Meal {
12 |
13 | var id: Int?
14 | var name: String?
15 | var short_description: String?
16 | var image: String?
17 | var price: Float?
18 |
19 | init(json: JSON) {
20 |
21 | self.id = json["id"].int
22 | self.name = json["name"].string
23 | self.short_description = json["short_description"].string
24 | self.image = json["image"].string
25 | self.price = json["price"].float
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Model/Customer/Restaurant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Restaurant.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import Foundation
9 | import SwiftyJSON
10 |
11 | class Restaurant {
12 |
13 | var id: Int?
14 | var name: String?
15 | var description: String?
16 | var address: String?
17 | var phone: String?
18 | var logo: String?
19 |
20 | init(json: JSON) {
21 | self.id = json["id"].int
22 | self.name = json["name"].string
23 | self.description = json["description"].string
24 | self.address = json["address"].string
25 | self.phone = json["phone"].string
26 | self.logo = json["logo"].string
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Model/Customer/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import Foundation
9 | import SwiftyJSON
10 |
11 | class User {
12 | var name: String?
13 | var email: String?
14 | var pictureURL: String?
15 |
16 | static let currenUser = User()
17 |
18 | func setInfo(json: JSON) {
19 | self.name = json["name"].string
20 | self.email = json["email"].string
21 |
22 | let image = json["picture"].dictionary
23 | let imageData = image?["data"]?.dictionary
24 | self.pictureURL = imageData?["url"]?.string
25 |
26 | }
27 |
28 | func resetInfo() {
29 | self.name = nil
30 | self.email = nil
31 | self.pictureURL = nil
32 | }
33 |
34 |
35 |
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Model/Driver/DriverOrder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DriverOrder.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import Foundation
9 | import SwiftyJSON
10 |
11 | class DriverOrder {
12 |
13 | var id: Int?
14 | var customerName: String?
15 | var customerAddress: String?
16 | var customerAvatar: String?
17 | var restaurantName: String?
18 |
19 | init(json: JSON) {
20 |
21 | self.id = json["id"].int
22 | self.customerName = json["customer"]["name"].string
23 | self.customerAddress = json["address"].string
24 | self.customerAvatar = json["customer"]["avatar"].string
25 | self.restaurantName = json["restaurant"]["name"].string
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Model/Driver/Revenue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Revenue.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 12/7/21.
6 | //
7 |
8 | import Foundation
9 | import SwiftyJSON
10 |
11 | class DriverRevenue {
12 |
13 | var tuesday: Int?
14 | var sunday: Int?
15 | var friday: Int?
16 | var saturday: Int?
17 | var wednesday: Int?
18 | var thursday: Int?
19 | var monday: Int?
20 |
21 | init(json: JSON) {
22 |
23 | self.tuesday = json["Tue"].int
24 | self.sunday = json["Sun"].int
25 | self.friday = json["Fri"].int
26 | self.saturday = json["Sat"].int
27 | self.wednesday = json["Wed"].int
28 | self.thursday = json["Thu"].int
29 | self.monday = json["Mon"].int
30 |
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Utilities/FloatingTabBarController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FloatingTabBarController.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Orlando Vargas on 11/21/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class FloatingTabBarController: UITabBarController {
11 | override func viewDidLoad() {
12 | super.viewDidLoad()
13 |
14 | let layer = CAShapeLayer()
15 | layer.path = UIBezierPath(roundedRect: CGRect(x: 30, y: tabBar.bounds.minY + 5, width: tabBar.bounds.width - 64, height: tabBar.bounds.height + 10), cornerRadius: 16).cgPath
16 | layer.shadowColor = UIColor.lightGray.cgColor
17 | layer.shadowOffset = CGSize(width: 5.0, height: 5.0)
18 | layer.shadowRadius = 25.0
19 | layer.shadowOpacity = 0.3
20 | layer.borderWidth = 1.0
21 | layer.opacity = 1.0
22 | layer.isHidden = false
23 | layer.masksToBounds = false
24 | layer.fillColor = UIColor.black.cgColor
25 |
26 | tabBar.layer.insertSublayer(layer, at: 0)
27 |
28 | if let items = self.tabBar.items {
29 | items.forEach { item in
30 | item.imageInsets = UIEdgeInsets(top: 20, left: 0, bottom: -20, right: 0)
31 | }
32 | }
33 | tabBar.unselectedItemTintColor = .systemOrange
34 | tabBar.unselectedItemTintColor = .white
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/Utilities/UIHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIHelper.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Orlando Vargas on 12/4/21.
6 | //
7 |
8 | import UIKit
9 |
10 | struct UIHelper {
11 |
12 | static func createOneColumnFlowLayout(in view: UIView) -> UICollectionViewFlowLayout {
13 | let width = view.bounds.width
14 | let padding: CGFloat = 12
15 | let availableWidth = width - (padding*2)
16 |
17 | let flowLayout = UICollectionViewFlowLayout()
18 | flowLayout.sectionInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
19 | flowLayout.itemSize = CGSize(width: availableWidth, height: width/3)
20 |
21 | return flowLayout
22 | }
23 |
24 | static func createThreeColumnFlowLayout(in view: UIView) -> UICollectionViewFlowLayout {
25 | let width = view.bounds.width
26 | let padding: CGFloat = 12
27 | let minimumItemSpacing: CGFloat = 10
28 | let availableWidth = width - (padding*2) - (minimumItemSpacing*2)
29 | let itemWidth = availableWidth / 3
30 |
31 | let flowLayout = UICollectionViewFlowLayout()
32 | flowLayout.sectionInset = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
33 | flowLayout.itemSize = CGSize(width: itemWidth, height: itemWidth + 40)
34 |
35 | return flowLayout
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/View/Customer/CartCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CartCell.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 | import MapKit
10 |
11 | class CartCell: UITableViewCell {
12 |
13 | @IBOutlet weak var qtyItemLabel: UILabel!
14 | @IBOutlet weak var mealNameLabel: UILabel!
15 | @IBOutlet weak var priceItemLabel: UILabel!
16 | @IBOutlet weak var mealImage: UIImageView!
17 |
18 |
19 | override func awakeFromNib() {
20 | super.awakeFromNib()
21 | mealImage.contentMode = .scaleAspectFill
22 | mealImage.layer.cornerRadius = 8
23 | // Initialization code
24 | }
25 |
26 | override func setSelected(_ selected: Bool, animated: Bool) {
27 | super.setSelected(selected, animated: animated)
28 |
29 | // Configure the view for the selected state
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/View/Customer/MenuCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuCell.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class MenuCell: UITableViewCell {
11 |
12 |
13 | @IBOutlet weak var mealImg: UIImageView!
14 | @IBOutlet weak var mealName: UILabel!
15 | @IBOutlet weak var mealDescription: UILabel!
16 | @IBOutlet weak var mealPrice: UILabel!
17 |
18 | override func awakeFromNib() {
19 | super.awakeFromNib()
20 | // Initialization code
21 | }
22 |
23 | override func setSelected(_ selected: Bool, animated: Bool) {
24 | super.setSelected(selected, animated: animated)
25 |
26 | // Configure the view for the selected state
27 | configure()
28 | }
29 |
30 | override func layoutSubviews() {
31 | super.layoutSubviews()
32 | contentView.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
33 | }
34 |
35 | func configure() {
36 | mealPrice.layer.cornerRadius = mealPrice.bounds.height/2
37 | mealImg.layer.cornerRadius = 36
38 |
39 | self.backgroundColor = .systemGray5
40 | self.layer.cornerRadius = 36
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/View/Customer/OrderCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OrderCell.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class OrderCell: UITableViewCell {
11 |
12 | @IBOutlet weak var orderItemQuantityLabel: UILabel!
13 | @IBOutlet weak var orderItemNameLabel: UILabel!
14 | @IBOutlet weak var orderItemPriceLabel: UILabel!
15 |
16 |
17 | override func awakeFromNib() {
18 | super.awakeFromNib()
19 | // Initialization code
20 | }
21 |
22 | override func setSelected(_ selected: Bool, animated: Bool) {
23 | super.setSelected(selected, animated: animated)
24 |
25 | // Configure the view for the selected state
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/View/Customer/RestaurantCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RestaurantCell.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/4/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class RestaurantCell: UITableViewCell {
11 |
12 |
13 | @IBOutlet weak var imgRestaurantLogo: UIImageView!
14 | @IBOutlet weak var lbRestaurantName: UILabel!
15 | @IBOutlet weak var lbRestaurantAddress: UILabel!
16 | @IBOutlet weak var lbRestaurantPhone: UILabel!
17 | @IBOutlet weak var lbRestaurantDesc: UILabel!
18 |
19 | override func awakeFromNib() {
20 | super.awakeFromNib()
21 | // Initialization code
22 | imgRestaurantLogo.clipsToBounds = true
23 | imgRestaurantLogo.layer.cornerRadius = 36
24 | }
25 |
26 | override func setSelected(_ selected: Bool, animated: Bool) {
27 | super.setSelected(selected, animated: animated)
28 |
29 | // Configure the view for the selected state
30 | }
31 |
32 |
33 |
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/View/Driver/DriverRevenueCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DriverRevenueCell.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 12/7/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class DriverRevenueCell: UITableViewCell {
11 |
12 |
13 | @IBOutlet weak var revenueLabel: UILabel!
14 |
15 | override func awakeFromNib() {
16 | super.awakeFromNib()
17 | // Initialization code
18 | }
19 |
20 | override func setSelected(_ selected: Bool, animated: Bool) {
21 | super.setSelected(selected, animated: animated)
22 |
23 | // Configure the view for the selected state
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/FoodDeliveryApp/View/Driver/DriversOrderCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DriversOrderCell.swift
3 | // FoodDeliveryApp
4 | //
5 | // Created by Alvaro Gonzalez on 11/5/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class DriversOrderCell: UITableViewCell {
11 |
12 | @IBOutlet weak var lbRestaurantName: UILabel!
13 | @IBOutlet weak var lbCustomerName: UILabel!
14 | @IBOutlet weak var lbCustomerAddress: UILabel!
15 | @IBOutlet weak var imgCustomerAvatar: UIImageView!
16 |
17 | override func awakeFromNib() {
18 | super.awakeFromNib()
19 | // Initialization code
20 | configure()
21 | }
22 |
23 | override func setSelected(_ selected: Bool, animated: Bool) {
24 | super.setSelected(selected, animated: animated)
25 |
26 | // Configure the view for the selected state
27 | }
28 |
29 | func configure() {
30 | imgCustomerAvatar.clipsToBounds = true
31 | imgCustomerAvatar.layer.cornerRadius = imgCustomerAvatar.bounds.height/2
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'FoodDeliveryApp' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for FoodDeliveryApp
9 | pod 'Alamofire'
10 | pod 'AlamofireImage'
11 | pod 'SwiftKeychainWrapper'
12 | pod 'Stripe'
13 | pod 'FBSDKCoreKit'
14 | pod 'FBSDKLoginKit'
15 | pod 'FBSDKShareKit'
16 | pod 'SwiftyJSON'
17 | pod 'Charts'
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Alamofire (5.4.4)
3 | - AlamofireImage (4.2.0):
4 | - Alamofire (~> 5.4)
5 | - Charts (3.6.0):
6 | - Charts/Core (= 3.6.0)
7 | - Charts/Core (3.6.0)
8 | - FBAEMKit (12.1.0):
9 | - FBSDKCoreKit_Basics (= 12.1.0)
10 | - FBSDKCoreKit (12.1.0):
11 | - FBAEMKit (= 12.1.0)
12 | - FBSDKCoreKit_Basics (= 12.1.0)
13 | - FBSDKCoreKit_Basics (12.1.0)
14 | - FBSDKLoginKit (12.1.0):
15 | - FBSDKCoreKit (= 12.1.0)
16 | - FBSDKShareKit (12.1.0):
17 | - FBSDKCoreKit (= 12.1.0)
18 | - Stripe (21.9.0):
19 | - Stripe/Stripe3DS2 (= 21.9.0)
20 | - StripeCore (= 21.9.0)
21 | - StripeUICore (= 21.9.0)
22 | - Stripe/Stripe3DS2 (21.9.0):
23 | - StripeCore (= 21.9.0)
24 | - StripeUICore (= 21.9.0)
25 | - StripeCore (21.9.0)
26 | - StripeUICore (21.9.0):
27 | - StripeCore (= 21.9.0)
28 | - SwiftKeychainWrapper (4.0.1)
29 | - SwiftyJSON (5.0.1)
30 |
31 | DEPENDENCIES:
32 | - Alamofire
33 | - AlamofireImage
34 | - Charts
35 | - FBSDKCoreKit
36 | - FBSDKLoginKit
37 | - FBSDKShareKit
38 | - Stripe
39 | - SwiftKeychainWrapper
40 | - SwiftyJSON
41 |
42 | SPEC REPOS:
43 | trunk:
44 | - Alamofire
45 | - AlamofireImage
46 | - Charts
47 | - FBAEMKit
48 | - FBSDKCoreKit
49 | - FBSDKCoreKit_Basics
50 | - FBSDKLoginKit
51 | - FBSDKShareKit
52 | - Stripe
53 | - StripeCore
54 | - StripeUICore
55 | - SwiftKeychainWrapper
56 | - SwiftyJSON
57 |
58 | SPEC CHECKSUMS:
59 | Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9
60 | AlamofireImage: 34a2d90b0e5fe6a5605f85ae4b7b01e784c60192
61 | Charts: b1e3a1f5a1c9ba5394438ca3b91bd8c9076310af
62 | FBAEMKit: 56c0bb9b42e3747cd82b67934f0c2b19325382ea
63 | FBSDKCoreKit: 75368765d9c2303073145a7925dfaa9d60bcd77b
64 | FBSDKCoreKit_Basics: 39865aff97e5f6951a78fb3e89dc4460e35e1895
65 | FBSDKLoginKit: e993f97c7cc794c5da4056d8aec3c3d66033a727
66 | FBSDKShareKit: 24943f539dbe382a6fad04952ea1aee29b100c1f
67 | Stripe: 41c3d261501e1dc84755b1bdabdaae50ebf92b53
68 | StripeCore: 2ea9531e863ef20f191a0d61f00dedb7f061baef
69 | StripeUICore: a0f9e40520823d34c63baec0782fc4a0c7fb7484
70 | SwiftKeychainWrapper: 807ba1d63c33a7d0613288512399cd1eda1e470c
71 | SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
72 |
73 | PODFILE CHECKSUM: a61ffb833dd1c103ff8c0cf15983d4d4ca7eb005
74 |
75 | COCOAPODS: 1.11.2
76 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # FoodDeliveryApp
2 |
3 | ## Table of Contents
4 | 1. [Overview](#Overview)
5 | 1. [Product Spec](#Product-Spec)
6 | 1. [Wireframes](#Wireframes)
7 | 2. [Schema](#Schema)
8 |
9 |
10 | ## User Stories
11 |
12 | The following functionality is completed:
13 |
14 |
15 | ### Customer : App
16 | #### Customer : App Week 11
17 | - [x] Customer sees app icon in home screen and styled launch screen.
18 | - [x] Customer can sign up to create a new account.
19 | - [x] Customer can log in.
20 | - [x] Customer can logout.
21 | - [x] Customer can see profile information
22 | - [x] Customer can view restaurants
23 | - [x] Customer can view specific restaurant menu
24 | - [x] Customer can add meal to its cart
25 | - [x] Customer can start a new order with other restaurant
26 | - [x] Customer can pay for its order
27 | - [x] Customer can view live map current order
28 |
29 | #### Customer : App Week 12
30 | - [X] Customer App Redesign v2 Started
31 |
32 |
33 | ### Driver : App
34 | #### Driver : App Week 11
35 | - [x] Driver sees see all orders
36 | - [x] Driver can pick an order
37 | - [x] Driver can see live map of customer address
38 | - [x] Driver can complete an order
39 | - [x] Driver can see profile details
40 |
41 | #### Driver : App Week 12
42 | - [X] Driver App Redesign v2 Started
43 |
44 |
45 | ### Restaurant Owner : Website/API
46 | #### Customer : App Week 11
47 | - [x] Restauranteurs can sign in / logout
48 | - [x] Restauranteurs can register there business
49 | - [x] Restauranteurs can see Orders Report
50 | - [x] Restauranteurs get live notifications of new orders
51 | - [x] Restauranteurs can view Account Summary
52 | #### Customer : App Week 12
53 | - [x] Restauranteurs can view and add dishes
54 | - [x] Restauranteurs can update dishes
55 | - [x] Restauranteurs can view and update profile
56 |
57 |
58 | ## Overview
59 | ### Description
60 | Delivery App's purpose is to allow customers to order food from any restaurant, providing them with the option to pick-up or get their order delivered.
61 |
62 |
63 |
64 | ### App Evaluation
65 |
66 | - **Category:** Food Delivery Application
67 | - **Mobile:** Mobile is essential for customers to log in and track their current/past orders. Drivers must use the mobile app as well to select from list of deliveries available. Restaurant owners will use the Project website.
68 | - **Story:** We want to facilitate local restaurants sharing their cuisine with future customers, creating jobs and growth of their communities by emphasizing the connection between restaurant owners, their customers, and delivery drivers.
69 | - **Market:** Restaurant owners looking to expand their customer base. Customers looking to order a food delivery without the steep fees and drivers looking to make some extra cash
70 | - **Habit:** The usage of this app would be daily, peak hours during lunch and dinner time for both drivers and customers
71 | - **Scope:** Small to medium sized restaurants in the metropolitan area that would like to expand their customer base, outdoorsy teenagers and adults alike that like to do paid customer service, and hungry customers.
72 |
73 | ## Product Spec
74 |
75 | ### 1. User Stories (Required and Optional)
76 |
77 | **Required Must-have Stories**
78 |
79 | * Restaurant owners will create their account using the Project website. The website will serve as the front end aspect of the restaurant owner user interface, as well as the backend for the iOS API requests.
80 | * All interactions made in the app will be done using Project API requests.
81 | * Customer and Driver must create an account and authenticate using their personal Facebook/Twitter account. This will add an implicit layer of security.
82 | * Customers can select a restaurant to order from, browse their selection and make orders on demand. Pick-up is optional, Delivery is the default
83 | * Customer will be allowed to see details of their order prior to checkout, during and upon order completion
84 | * Drivers can select orders to deliver from their dashboard, each available delivery order includes distance information and possible earnings
85 | * Drivers can see their net earnings data with easy to understand graphs
86 |
87 | **Optional Nice-to-have Stories**
88 |
89 | * User accounts can serve as both Customer and Driver
90 | * Tip Jar for recurring drivers
91 | * Informal chat box between customer, driver and restaurant owner
92 |
93 | ### 2. Screen Archetypes
94 |
95 | * Welcome Page
96 | * User can select between customer and driver, login accordingly or create a new account
97 | * Dashboard
98 | * Customer and driver dashboards are different, but they share some commonalities such as the user profile.
99 | * At the bottom of the screen, there is a navigation bar that allows for easy navigation between the screens.
100 | * Driver dashboard navigation pane includes a page to view revenue from previous delivered orders.
101 |
102 | ### 3. Navigation
103 |
104 | **Tab Navigation** (Tab to Screen)
105 |
106 | * Customer
107 | * [Customer Dashboard]
108 | * [Profile]
109 | * [Current Order Status: On the way]
110 | * [Cart]
111 | * [Part Orders]
112 |
113 |
114 | **iOS App Flow Navigation** (Screen to Screen)
115 |
116 | * Welcome Page
117 | * Customer Login -> Create Account -> Customer Dashboard
118 | * Driver Login -> Create Account -> Customer Dashboard
119 | * Customer Dashboard ->
120 | * Restaurant List -> Meals Available -> Cart -> Checkout
121 | * List of meals available by selected restaurant -> Details Page
122 | * From Navigation Bar
123 | * Order Status
124 | * Cart
125 | * Customer Profile
126 | * Part orders
127 | * Driver Dashboard -> Deliveries Available -> Current Order
128 | * Driver Profile
129 | * Deliveries available
130 | * Previous orders
131 | * Revenue stats
132 |
133 | ## Wireframes
134 | [Add picture of your hand sketched wireframes in this section]
135 |
136 |
137 | ### [BONUS] Digital Wireframes & Mockups
138 |
139 |
140 |
141 |
157 |
158 |
159 |
160 | ## **Schema**
161 | ### **Models**
162 |
163 | #### **User: Customer**
164 |
165 | | Property | Type | Description |
166 | | ------------- | -------- | ------------|
167 | | customerId | Token| The token generated for the user's current session |
168 | | profileImage | List | URL containing user profile image |
169 | | firstName | String | customerId property |
170 | | lastName | String | customerId property |
171 | | mobileNumber | Number | customerId property |
172 | | email | String | customerId property |
173 | | savedItems | List | List containing all user saved meal or restaurant ids |
174 | | orderHistory | List | List containing the current user's past orders|
175 | | paymentStatus | Boolean | Flag to check if new payment must be requested during checkout |
176 | | deliverTo | String | contains customer address |
177 |
178 | #### **Order**
179 |
180 | | Property | Type | Description |
181 | | ------------- | -------- | ------------|
182 | | orderId | Number | unique order identifier
183 | | customerId | Token| The token generated for the user's current session |
184 | | cartItems | List | List of key/value pairs, key for the mealId and value for the quantity |
185 | | deliveryFee | Number | Some flat or proportional rate |
186 | | orderTotal | Number | Decimal value of order subtotal, taxes and fees |
187 | | driverId | Number | Identifer of driver delivering order |
188 | | orderMetadata | List | contains timestamps, payment information and other order details |
189 |
190 | ### **Networking**
191 | #### List of network requests by screen
192 | - Login Screen
193 | - (POST) Authentication Token
194 | ```swift
195 | let path = "api/social/convert-token/"
196 | let url = baseURL!.appendingPathComponent(path)
197 | let params: [String: ANY] = [
198 | "grant-type": "convert-token",
199 | "client_id": CLIENT_ID,
200 | "client_secret": CLIENT_SECRET,
201 | "backend": "facebook",
202 | "token": AccessToken.current!.tokenString,
203 | "user_type": userType
204 | ]
205 |
206 | AF.request(url!, method: .post, parameters: params, encoding: JSONEnconding.default).responseJSON{
207 | ....
208 | }
209 |
210 | ```
211 | - Customer Dashboard
212 | - (GET) Retrieve List of available restaurants
213 | ```swift
214 |
215 | let path = "api/customer/restaurants/"
216 | let url = baseURL?.appendingPathComponenet(path)
217 |
218 | AF.request(url!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON{
219 | ...
220 | })
221 |
222 | ```
223 |
224 | - Customer Dashboard -> Select Restaurant
225 | - (GET) Retrieve Meals available from selected restaurant
226 | ```swift
227 |
228 | let path = "api/customer/meals/\(restaurantId)"
229 | let url = baseURL?.appendingPathComponent(path)
230 |
231 | AF.request(url!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON{
232 | ...
233 | })
234 |
235 | ```
236 | - Checkout -> Submit Order
237 | - (POST) Collect information locally and create a new order
238 | ```swift
239 |
240 | let path = "api/customer/order/add/"
241 | let url - baseURL?.appendingPathComponenet(path)
242 |
243 | let simpleArray = Cart.currentCart.items
244 | let jsonArray = simpleArray.map { items in
245 | return [
246 | "meal_id": item.meal.id!,
247 | "quantity": item.qty
248 | ]
249 | }
250 |
251 | if JSONSerialization.isValidJSONObject(jsonArray){
252 | do {
253 | ...
254 | ...
255 | let params: [String: Any] = [
256 | "access_token":...
257 | "stripe_token":....
258 | "restaurant_id":....
259 | "order_details":...
260 | "address":...
261 | ]
262 | }
263 | }
264 |
265 | ```
266 |
267 | ### **Existing API Endpoints**
268 | ##### API hosted locally, not yet deployed
269 | - Base URL - `ALLOWED_HOSTS = ['https://fooddeliverynowapp.herokuapp.com/', 'http://127.0.0.1:8000']`
270 |
271 | HTTP Verb | Endpoint | Description
272 | ----------|----------|------------
273 | `POST` | `api/social/convert-token` | authenticate user logged in with facebook
274 | `POST` | `api/social/revoke-token` | logout user from facebook
275 | `POST` | `api/social/refresh-token` | generates new token
276 | `GET` | `api/customer/restaurants` | get all restaurants available to customer
277 | `GET` | `api/customer/meals/` | get all the meals available from some restaurant
278 | `POST` | `api/customer/order/create` | create new order
279 | `POST` | `api/customer/order//add` | add to order
280 | `POST` | `api/customer/order//seal` | finalize order, submit
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 | ### **Unit 11 - Build Sprint 2**
289 | ##### Example of how the app works Driver/Customer/Owner
290 |
291 |
292 |
293 | ### **Unit 12 - Build Sprint 3**
294 | ##### Example of the design v2
295 |
296 |
297 |
298 |
299 | ### **Unit 13 - Final Build Sprint**
300 | ##### Example of the design website dashaboard, customer app, driver app v2
301 |
302 |
303 |
304 |
305 |
--------------------------------------------------------------------------------
/Submission/Unit 12 - Build Sprint 3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/Submission/Unit 12 - Build Sprint 3.gif
--------------------------------------------------------------------------------
/Submission/Unit 13 - Final Build Sprint.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/Submission/Unit 13 - Final Build Sprint.gif
--------------------------------------------------------------------------------
/Submission/break.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/Submission/break.png
--------------------------------------------------------------------------------
/Submission/flowchart.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/Submission/flowchart.PNG
--------------------------------------------------------------------------------
/Submission/gif1-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/Submission/gif1-1.gif
--------------------------------------------------------------------------------
/Submission/gif1-2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/Submission/gif1-2.gif
--------------------------------------------------------------------------------
/Submission/gif2-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SiliconValley4/DeliveryApp/89979e35a7b3a66bb31c722158e60f29fbba2471/Submission/gif2-1.gif
--------------------------------------------------------------------------------