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