├── MyTravelHelper ├── Assets.xcassets │ ├── Contents.json │ ├── train.imageset │ │ ├── train@1X.png │ │ ├── train@2X.png │ │ ├── train@3X.png │ │ └── Contents.json │ ├── transitArrow.imageset │ │ ├── arrow@2x.jpg │ │ ├── arrow@3x.jpg │ │ ├── arrow@2x-1.jpg │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── App-utils │ ├── AppConstants.swift │ ├── AppUtils.swift │ ├── ProgressIndicator.swift │ ├── Extension.swift │ └── Reachability.swift ├── AppDelegate.swift ├── Modules │ └── SearchTrains │ │ ├── Router │ │ └── SearchTrainRouter.swift │ │ ├── View │ │ ├── TrainInfoCell.swift │ │ └── SearchTrainViewController.swift │ │ ├── Protocols │ │ └── SearchTrainProtocols.swift │ │ ├── Presenter │ │ └── SearchTrainPresenter.swift │ │ ├── Entity │ │ ├── Stations.swift │ │ ├── TrainMovements.swift │ │ └── StationInfo.swift │ │ ├── Repository │ │ └── Client.swift │ │ └── Interactor │ │ └── SearchTrainInteractor.swift ├── Info.plist └── App-Ui │ └── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── MyTravelHelper.xcworkspace ├── xcuserdata │ ├── satish.xcuserdatad │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── UserInterfaceState.xcuserstate │ ├── navroz.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ ├── jmfi0001.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ ├── navrozsingh.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ └── vijay.ghooli.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── MyTravelHelper.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── satish.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ ├── jmfi0001.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── navroz.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── satish.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── navrozsingh.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ └── vijay.ghooli.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── Podfile ├── MyTravelHelperTests ├── Info.plist ├── SearchTrainInteractorsTest.swift ├── SearchTrainPresenterTests.swift └── MockResponses.swift ├── Podfile.lock └── readme.MD /MyTravelHelper/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MyTravelHelper/Assets.xcassets/train.imageset/train@1X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper/Assets.xcassets/train.imageset/train@1X.png -------------------------------------------------------------------------------- /MyTravelHelper/Assets.xcassets/train.imageset/train@2X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper/Assets.xcassets/train.imageset/train@2X.png -------------------------------------------------------------------------------- /MyTravelHelper/Assets.xcassets/train.imageset/train@3X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper/Assets.xcassets/train.imageset/train@3X.png -------------------------------------------------------------------------------- /MyTravelHelper/Assets.xcassets/transitArrow.imageset/arrow@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper/Assets.xcassets/transitArrow.imageset/arrow@2x.jpg -------------------------------------------------------------------------------- /MyTravelHelper/Assets.xcassets/transitArrow.imageset/arrow@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper/Assets.xcassets/transitArrow.imageset/arrow@3x.jpg -------------------------------------------------------------------------------- /MyTravelHelper/Assets.xcassets/transitArrow.imageset/arrow@2x-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper/Assets.xcassets/transitArrow.imageset/arrow@2x-1.jpg -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/xcuserdata/satish.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/xcuserdata/navroz.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper.xcworkspace/xcuserdata/navroz.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/xcuserdata/satish.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper.xcworkspace/xcuserdata/satish.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/xcuserdata/jmfi0001.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper.xcworkspace/xcuserdata/jmfi0001.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/xcuserdata/navrozsingh.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper.xcworkspace/xcuserdata/navrozsingh.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/xcuserdata/vijay.ghooli.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper.xcworkspace/xcuserdata/vijay.ghooli.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MyTravelHelper.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MyTravelHelper.xcodeproj/project.xcworkspace/xcuserdata/satish.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/navrozSingh/TechTest/HEAD/MyTravelHelper.xcodeproj/project.xcworkspace/xcuserdata/satish.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MyTravelHelper/App-utils/AppConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppConstants.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let PROGRESS_INDICATOR_VIEW_TAG: Int = 10 12 | -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MyTravelHelper.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MyTravelHelper.xcodeproj/xcuserdata/jmfi0001.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MyTravelHelper.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 6 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MyTravelHelper.xcodeproj/xcuserdata/navroz.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MyTravelHelper.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 5 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MyTravelHelper.xcodeproj/xcuserdata/satish.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MyTravelHelper.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 6 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MyTravelHelper.xcodeproj/xcuserdata/navrozsingh.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MyTravelHelper.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MyTravelHelper.xcodeproj/xcuserdata/vijay.ghooli.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MyTravelHelper.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MyTravelHelper/Assets.xcassets/train.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "train@1X.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "train@2X.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "train@3X.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /MyTravelHelper/Assets.xcassets/transitArrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow@2x-1.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "arrow@2x.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "arrow@3x.jpg", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'MyTravelHelper' do 5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for MyTravelHelper 9 | pod 'XMLParsing', :git => 'https://github.com/ShawnMoore/XMLParsing.git' 10 | #pod 'Alamofire', '~> 4.3' 11 | pod 'DropDown' 12 | pod 'SwiftSpinner' 13 | 14 | target 'MyTravelHelperTests' do 15 | inherit! :search_paths 16 | # Pods for testing 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /MyTravelHelper/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | let notice = SearchTrainRouter.createModule() 17 | window = UIWindow(frame: UIScreen.main.bounds) 18 | window?.rootViewController = UINavigationController(rootViewController: notice) 19 | window?.makeKeyAndVisible() 20 | return false 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /MyTravelHelperTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MyTravelHelper/App-utils/AppUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppUtils.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | /* Show Progress Indicator */ 13 | func showProgressIndicator(view:UIView){ 14 | view.isUserInteractionEnabled = false 15 | let progressIndicator = ProgressIndicator(text: "Please wait..") 16 | progressIndicator.tag = PROGRESS_INDICATOR_VIEW_TAG 17 | view.addSubview(progressIndicator) 18 | } 19 | 20 | /* Hide progress Indicator */ 21 | func hideProgressIndicator(view:UIView){ 22 | view.isUserInteractionEnabled = true 23 | 24 | if let viewWithTag = view.viewWithTag(PROGRESS_INDICATOR_VIEW_TAG) { 25 | viewWithTag.removeFromSuperview() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DropDown (2.3.12) 3 | - SwiftSpinner (1.6.2) 4 | - XMLParsing (0.0.3) 5 | 6 | DEPENDENCIES: 7 | - DropDown 8 | - SwiftSpinner 9 | - XMLParsing (from `https://github.com/ShawnMoore/XMLParsing.git`) 10 | 11 | SPEC REPOS: 12 | https://github.com/CocoaPods/Specs.git: 13 | - DropDown 14 | - SwiftSpinner 15 | 16 | EXTERNAL SOURCES: 17 | XMLParsing: 18 | :git: https://github.com/ShawnMoore/XMLParsing.git 19 | 20 | CHECKOUT OPTIONS: 21 | XMLParsing: 22 | :commit: 594272e4df40798c53ae1735ee65f3f387291670 23 | :git: https://github.com/ShawnMoore/XMLParsing.git 24 | 25 | SPEC CHECKSUMS: 26 | DropDown: 0bcc278799ec11a3d34ce1aac9367fbbaf5423d9 27 | SwiftSpinner: 3a4c6dea425e5a31b908a8b1dd5dcd1ad4d96340 28 | XMLParsing: 085edb6fa22c71d6b53a79299f990a91e7414d19 29 | 30 | PODFILE CHECKSUM: 42811fdc070b391d438395b04a81798f692ba318 31 | 32 | COCOAPODS: 1.9.1 33 | -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/Router/SearchTrainRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchTrainRouter.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | class SearchTrainRouter: PresenterToRouterProtocol { 11 | static func createModule() -> SearchTrainViewController { 12 | let view = UIStoryboard.mainstoryboard.instantiateViewController(withIdentifier: "searchTrain") as! SearchTrainViewController 13 | let presenter: ViewToPresenterProtocol & InteractorToPresenterProtocol = SearchTrainPresenter() 14 | let interactor: PresenterToInteractorProtocol = SearchTrainInteractor() 15 | let router:PresenterToRouterProtocol = SearchTrainRouter() 16 | let networkClient: Client = TrainClient() 17 | 18 | view.presenter = presenter 19 | presenter.view = view 20 | presenter.router = router 21 | presenter.interactor = interactor 22 | interactor.presenter = presenter 23 | interactor.networkClient = networkClient 24 | 25 | return view 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /readme.MD: -------------------------------------------------------------------------------- 1 | ## Below are the changes I have made to project in order to achieve exercise GOALS 2 | 3 | ## In Appdelegate I have following changes 4 | - [x] added Navigationbar 5 | - [x] Setup ‘window’ properly 6 | 7 | ## Then I have made few changes Entities. 8 | - [x] Added a static method to get URLREQUEST 9 | - [x] function to save ‘StationTrain’ to defaults 10 | 11 | ## Then I moved to ‘SearchTrainInteractor’ and made the following changes 12 | - [x] removed amlofire. 13 | - [x] Implemented ‘URLSession’. 14 | - [x] Managed multiple requests in ‘proceesTrainListforDestinationCheck’. 15 | - [x] Segregated code 16 | - [x] Added favourite functionality. 17 | 18 | ## In Presenter, Protocols, Router . 19 | - [x] only removed boilerplate code to with generic functions. 20 | 21 | ## After that I code the view part in ‘SearchTrainViewController.swift’ 22 | 23 | - [x] Changed to cell handling. 24 | - [x] handled Favourite functionality via closure 25 | - [x] Changed view component to run on main thread. 26 | 27 | ## Lastly in ‘SearchTrainPresenterTests’ added few test 28 | 29 | ###### Newly implemented tasks 30 | - [x] Implemented the network adapter layer. 31 | - [x] Wrote test cases for interactor 32 | -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/View/TrainInfoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrainInfoswift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TrainInfoCell: UITableViewCell { 12 | @IBOutlet weak var destinationTimeLabel: UILabel! 13 | @IBOutlet weak var sourceTimeLabel: UILabel! 14 | @IBOutlet weak var destinationInfoLabel: UILabel! 15 | @IBOutlet weak var souceInfoLabel: UILabel! 16 | @IBOutlet weak var trainCode: UILabel! 17 | var favStationTrain: ((StationTrain)->())? 18 | var train: StationTrain? { 19 | didSet { 20 | trainCode.text = train?.trainCode 21 | souceInfoLabel.text = train?.stationFullName 22 | sourceTimeLabel.text = train?.expDeparture 23 | if let _destinationDetails = train?.destinationDetails { 24 | destinationInfoLabel.text = _destinationDetails.locationFullName 25 | destinationTimeLabel.text = _destinationDetails.expDeparture 26 | } 27 | } 28 | } 29 | @IBAction func saveStationTrain() { 30 | guard let favStationTrain = favStationTrain, 31 | let train = train 32 | else { 33 | return 34 | } 35 | favStationTrain(train) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /MyTravelHelper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | LSRequiresIPhoneOS 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIRequiredDeviceCapabilities 31 | 32 | armv7 33 | 34 | UISupportedInterfaceOrientations 35 | 36 | UIInterfaceOrientationPortrait 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /MyTravelHelper/App-Ui/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 | -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/Protocols/SearchTrainProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchTrainProtocols.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum ErrorMessages: Error { 12 | case noInternet 13 | case NoTrainsFound 14 | case NoTrainAvailbilityFromSource 15 | case InvalidSourceAndDestination 16 | } 17 | 18 | protocol ViewToPresenterProtocol: class{ 19 | var view: PresenterToViewProtocol? {get set} 20 | var interactor: PresenterToInteractorProtocol? {get set} 21 | var router: PresenterToRouterProtocol? {get set} 22 | func fetchallStations() 23 | func searchTapped(source:String,destination:String) 24 | func saveStationToFav(_ station: StationTrain) 25 | func getFavouriteStation()->StationTrain? 26 | } 27 | 28 | protocol PresenterToViewProtocol: class{ 29 | func saveFetchedStations(stations:[Station]?) 30 | func updateLatestTrainList(trainsList: [StationTrain]) 31 | func showErrorMessage(for Error: ErrorMessages) 32 | } 33 | 34 | protocol PresenterToRouterProtocol: class { 35 | static func createModule()-> SearchTrainViewController 36 | } 37 | 38 | protocol PresenterToInteractorProtocol: class { 39 | var presenter:InteractorToPresenterProtocol? {get set} 40 | var networkClient: Client?{get set} 41 | func fetchallStations() 42 | func fetchTrainsFromSource(sourceCode:String,destinationCode:String) 43 | func saveStationToFav(_ station: StationTrain) 44 | func getFavouriteStation()->StationTrain? 45 | } 46 | 47 | protocol InteractorToPresenterProtocol: class { 48 | func stationListFetched(list:[Station]) 49 | func fetchedTrainsList(trainsList:[StationTrain]?) 50 | func showErrorMessage(for Error: ErrorMessages) 51 | func getFavouriteStation()->StationTrain? 52 | } 53 | -------------------------------------------------------------------------------- /MyTravelHelper/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/Presenter/SearchTrainPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchTrainPresenter.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SearchTrainPresenter:ViewToPresenterProtocol { 12 | private var stationsList:[Station] = [Station]() 13 | var interactor: PresenterToInteractorProtocol? 14 | var router: PresenterToRouterProtocol? 15 | var view:PresenterToViewProtocol? 16 | 17 | func searchTapped(source: String, destination: String) { 18 | let sourceStationCode = getStationCode(stationName: source) 19 | let destinationStationCode = getStationCode(stationName: destination) 20 | interactor?.fetchTrainsFromSource(sourceCode: sourceStationCode, 21 | destinationCode: destinationStationCode) 22 | } 23 | func fetchallStations() { 24 | interactor?.fetchallStations() 25 | } 26 | func getFavouriteStation() -> StationTrain? { 27 | interactor?.getFavouriteStation() 28 | } 29 | func saveStationToFav(_ station: StationTrain) { 30 | interactor?.saveStationToFav(station) 31 | } 32 | private func getStationCode(stationName:String)->String { 33 | let stationCode = stationsList.filter{$0.stationDesc == stationName}.first 34 | return stationCode?.stationCode?.lowercased() ?? "" 35 | } 36 | } 37 | 38 | extension SearchTrainPresenter: InteractorToPresenterProtocol { 39 | func showErrorMessage(for Error: ErrorMessages) { 40 | self.view?.showErrorMessage(for: Error) 41 | } 42 | func fetchedTrainsList(trainsList: [StationTrain]?) { 43 | if let _trainsList = trainsList { 44 | self.view?.updateLatestTrainList(trainsList: _trainsList) 45 | } 46 | //TODO: Check if this required 47 | /* 48 | else { 49 | self.view?.showNoTrainsFoundAlert() 50 | }*/ 51 | } 52 | func stationListFetched(list: [Station]) { 53 | self.stationsList = list 54 | self.view?.saveFetchedStations(stations: list) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/Entity/Stations.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Stations.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Stations: Codable { 12 | var stationsList: [Station] 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case stationsList = "objStation" 16 | } 17 | } 18 | 19 | struct Station: Codable { 20 | let stationDesc: String? 21 | let stationLatitude: Double? 22 | let stationLongitude: Double? 23 | let stationCode: String? 24 | let stationId: Int? 25 | 26 | enum CodingKeys: String, CodingKey { 27 | case stationDesc = "StationDesc" 28 | case stationLatitude = "StationLatitude" 29 | case stationLongitude = "StationLongitude" 30 | case stationCode = "StationCode" 31 | case stationId = "StationId" 32 | } 33 | init(desc: String, latitude: Double, longitude: Double, code: String, stationId: Int) { 34 | self.stationDesc = desc 35 | self.stationLatitude = latitude 36 | self.stationLongitude = longitude 37 | self.stationCode = code 38 | self.stationId = stationId 39 | } 40 | 41 | init(from decoder: Decoder) throws { 42 | let values = try decoder.container(keyedBy: CodingKeys.self) 43 | let stationDesc = try values.decode(String.self, forKey: .stationDesc) 44 | let stationLatitude = try values.decode(Double.self, forKey: .stationLatitude) 45 | let stationLongitude = try values.decode(Double.self, forKey: .stationLongitude) 46 | let stationCode = try values.decode(String.self, forKey: .stationCode) 47 | let stationId = try values.decode(Int.self, forKey: .stationId) 48 | self.init(desc: stationDesc, latitude: stationLatitude, longitude: stationLongitude, code: stationCode, stationId: stationId) 49 | 50 | } 51 | } 52 | extension Station { 53 | static func request() -> URLRequest { 54 | var allStation = URL.baseUrl 55 | allStation.addPath("/realtime/realtime.asmx/getAllStationsXML") 56 | return URLRequest(url: allStation) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/Entity/TrainMovements.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrainMovements.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct TrainMovementsData: Codable { 12 | var trainMovements: [TrainMovement] 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case trainMovements = "objTrainMovements" 16 | } 17 | } 18 | 19 | struct TrainMovement: Codable { 20 | let trainCode: String? 21 | let locationCode: String? 22 | let locationFullName: String? 23 | let expDeparture:String? 24 | 25 | enum CodingKeys: String, CodingKey { 26 | case trainCode = "TrainCode" 27 | case locationCode = "LocationCode" 28 | case locationFullName = "LocationFullName" 29 | case expDeparture = "ExpectedDeparture" 30 | } 31 | init(trainCode: String, locationCode: String, locationFullName: String,expDeparture:String) { 32 | self.trainCode = trainCode 33 | self.locationCode = locationCode 34 | self.locationFullName = locationFullName 35 | self.expDeparture = expDeparture 36 | } 37 | init(from decoder: Decoder) throws { 38 | let values = try decoder.container(keyedBy: CodingKeys.self) 39 | let trainCode = try values.decode(String.self, forKey: .trainCode) 40 | let locationCode = try values.decode(String.self, forKey: .locationCode) 41 | let locationFullName = try values.decode(String.self, forKey: .locationFullName) 42 | let expDeparture = try values.decode(String.self, forKey: .expDeparture) 43 | self.init(trainCode: trainCode, 44 | locationCode: locationCode, 45 | locationFullName: locationFullName, 46 | expDeparture: expDeparture) 47 | } 48 | } 49 | extension TrainMovementsData { 50 | static func request(for code: String, date trainDate: String) -> URLRequest { 51 | var getTrainMovementsXML = URL.baseUrl 52 | getTrainMovementsXML.addPath("/realtime/realtime.asmx/getTrainMovementsXML") 53 | getTrainMovementsXML.appendQueryItem("TrainId", value: code) 54 | getTrainMovementsXML.appendQueryItem("TrainDate", value: trainDate) 55 | return URLRequest(url: getTrainMovementsXML) 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/Repository/Client.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Client.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Navroz Singh on 08/01/21. 6 | // Copyright © 2021 Sample. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | protocol Client { 11 | func fetchallStations(resultHandler: @escaping (Result) -> Void) 12 | func fetchTrainsFromSource(_ sourceRequest: URLRequest, 13 | resultHandler: @escaping (Result<[StationTrain], ErrorMessages>) -> Void) 14 | func proceesTrainListforDestinationCheck(_ sourceRequest: URLRequest, 15 | resultHandler: @escaping (Result) -> Void) 16 | } 17 | class TrainClient: Client { 18 | func fetchallStations(resultHandler: @escaping (Result) -> Void) { 19 | 20 | URLSession.shared.perform(Station.request(), 21 | decode: Stations.self) { (result) in 22 | switch result { 23 | case .success(let stations): 24 | resultHandler(.success(stations)) 25 | case .failure(_): 26 | //MARK: Fix me Cover all Cases 27 | resultHandler(.failure(.noInternet)) 28 | } 29 | } 30 | } 31 | func fetchTrainsFromSource(_ sourceRequest: URLRequest, 32 | resultHandler: @escaping (Result<[StationTrain], ErrorMessages>) -> Void) { 33 | URLSession.shared.perform(sourceRequest, decode: StationData.self) { (result) in 34 | switch result { 35 | case .failure(let error): 36 | let error = error as NSError 37 | switch error.code { 38 | case -1009: 39 | resultHandler(.failure(.noInternet)) 40 | break 41 | case 400...404: 42 | resultHandler(.failure(.NoTrainsFound)) 43 | break 44 | default: break 45 | } 46 | case .success(let stationData): 47 | if stationData.trainsList.count > 0 { 48 | resultHandler(.success(stationData.trainsList)) 49 | } else { 50 | resultHandler(.failure(.NoTrainsFound)) 51 | } 52 | } 53 | } 54 | } 55 | func proceesTrainListforDestinationCheck(_ sourceRequest: URLRequest, 56 | resultHandler: @escaping (Result) -> Void) { 57 | URLSession.shared.perform(sourceRequest, 58 | decode: TrainMovementsData.self) { (result) in 59 | switch result { 60 | //MARK: Fix me Cover all Cases 61 | case .failure(_): 62 | resultHandler(.failure(.noInternet)) 63 | break 64 | case .success(let trainMovements): 65 | resultHandler(.success(trainMovements)) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /MyTravelHelper/App-utils/ProgressIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressIndicator.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import Foundation 12 | import UIKit 13 | 14 | class ProgressIndicator: UIVisualEffectView { 15 | var text: String? { 16 | didSet { 17 | label.text = text 18 | label.textColor = UIColor.gray 19 | } 20 | } 21 | 22 | let activityIndictor: UIActivityIndicatorView = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.whiteLarge) 23 | let label: UILabel = UILabel() 24 | let blurEffect = UIBlurEffect(style: .dark) 25 | let vibrancyView: UIVisualEffectView 26 | 27 | init(text: String) { 28 | self.text = text 29 | self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect)) 30 | super.init(effect: blurEffect) 31 | self.setup() 32 | } 33 | 34 | required init?(coder aDecoder: NSCoder) { 35 | self.text = "" 36 | self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect)) 37 | super.init(coder: aDecoder) 38 | self.setup() 39 | } 40 | 41 | func setup() { 42 | contentView.addSubview(vibrancyView) 43 | contentView.addSubview(activityIndictor) 44 | contentView.addSubview(label) 45 | activityIndictor.startAnimating() 46 | } 47 | 48 | override func didMoveToSuperview() { 49 | super.didMoveToSuperview() 50 | 51 | if let superview = self.superview { 52 | let width = superview.frame.size.width / 1.8 53 | let height: CGFloat = 70.0 54 | self.frame = CGRect(x: superview.frame.size.width / 2 - width / 2, 55 | y: superview.frame.height / 2 - height / 2, 56 | width: width, 57 | height: height) 58 | vibrancyView.frame = self.bounds 59 | 60 | let activityIndicatorSize: CGFloat = 40 61 | activityIndictor.frame = CGRect(x: 15, 62 | y: height / 2 - activityIndicatorSize / 2, 63 | width: activityIndicatorSize, 64 | height: activityIndicatorSize) 65 | 66 | layer.cornerRadius = 8.0 67 | layer.masksToBounds = true 68 | label.text = text 69 | label.textAlignment = NSTextAlignment.center 70 | label.frame = CGRect(x: activityIndicatorSize + 5, 71 | y: 0, 72 | width: width - activityIndicatorSize - 15, 73 | height: height) 74 | label.textColor = UIColor.white 75 | label.font = UIFont.boldSystemFont(ofSize: 17) 76 | } 77 | } 78 | 79 | func show() { 80 | self.isHidden = false 81 | } 82 | 83 | func hide() { 84 | self.isHidden = true 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /MyTravelHelper/App-utils/Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Extension.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Navroz on 04/01/21. 6 | // Copyright © 2021 Sample. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XMLParsing 11 | 12 | extension URL { 13 | static var baseUrl: URL { 14 | guard let url = URL(string: "http://api.irishrail.ie/") else { 15 | fatalError("Invalid base URL") 16 | } 17 | return url 18 | } 19 | mutating func addPath(_ path: String) { 20 | guard var urlComponents = URLComponents(string: absoluteString) else { return } 21 | urlComponents.path = path 22 | self = urlComponents.url ?? self 23 | } 24 | mutating func appendQueryItem(_ name: String, value: String?) { 25 | guard var urlComponents = URLComponents(string: absoluteString) else { return } 26 | var queryItems: [URLQueryItem] = urlComponents.queryItems ?? [] 27 | let queryItem = URLQueryItem(name: name, value: value) 28 | queryItems.append(queryItem) 29 | urlComponents.queryItems = queryItems 30 | self = urlComponents.url ?? self 31 | } 32 | } 33 | extension UIStoryboard { 34 | static var mainstoryboard: UIStoryboard { 35 | return UIStoryboard(name:"Main",bundle: Bundle.main) 36 | } 37 | } 38 | extension Date { 39 | static var TrainDate: String { 40 | let today = Date() 41 | let formatter = DateFormatter() 42 | formatter.dateFormat = "dd/MM/yyyy" 43 | let dateString = formatter.string(from: today) 44 | return dateString 45 | } 46 | } 47 | extension URLSession { 48 | func perform(_ request: URLRequest, 49 | decode decodable: T.Type, 50 | result: @escaping (Result) -> Void) { 51 | 52 | URLSession.shared.dataTask(with: request) { (data, response, error) in 53 | guard let data = data 54 | else { 55 | result(.failure(NSError.nilData)) 56 | return 57 | } 58 | guard let object = try? XMLDecoder().decode(decodable.self, from: data) 59 | else { 60 | debugPrint(String(decoding: data, as: UTF8.self)) 61 | result(.failure(NSError.badResponse)) 62 | return 63 | } 64 | debugPrint(String(decoding: data, as: UTF8.self)) 65 | result(.success(object)) 66 | }.resume() 67 | } 68 | } 69 | 70 | extension NSError { 71 | static let nilData = NSError(domain: "com.example.com.MyTravelHelper", 72 | code: 404, 73 | userInfo: [NSLocalizedDescriptionKey : "Data is nil"]) 74 | static let badResponse = NSError(domain: "com.example.com.MyTravelHelper", 75 | code: 400, 76 | userInfo: [NSLocalizedDescriptionKey : "Unable to decode response"]) 77 | static let noNetwork = NSError(domain: "com.example.com.MyTravelHelper", 78 | code: -1009, 79 | userInfo: [NSLocalizedDescriptionKey : "The Internet connection appears to be offline."]) 80 | } 81 | -------------------------------------------------------------------------------- /MyTravelHelperTests/SearchTrainInteractorsTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchTrainInteractorsTest.swift 3 | // MyTravelHelperTests 4 | // 5 | // Created by Navroz on 10/01/21. 6 | // Copyright © 2021 Sample. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MyTravelHelper 11 | class InteractorTest: XCTestCase { 12 | var view: ViewToPresenterProtocol? 13 | var Interactor: PresenterToInteractorProtocol? 14 | var presenter = MockPresenter() 15 | var network = MockNetwork() 16 | override func setUp() { 17 | Interactor = SearchTrainInteractor() 18 | Interactor?.networkClient = network 19 | Interactor?.presenter = presenter 20 | } 21 | func testFetchallStationsFailure() { 22 | presenter.currentError = .NoTrainsFound 23 | network.currentError = .NoTrainsFound 24 | Interactor?.fetchallStations() 25 | } 26 | func testFetchallStationsSuccess() { 27 | presenter.currentError = nil 28 | network.currentError = nil 29 | Interactor?.fetchallStations() 30 | } 31 | func testEmptyFetchTrainList() { 32 | presenter.currentError = .InvalidSourceAndDestination 33 | Interactor?.fetchTrainsFromSource(sourceCode: "", destinationCode: "") 34 | } 35 | func testFetchTrainList() { 36 | let unlockExpectation = expectation(description: "delegate is called twice") 37 | presenter.expectation = unlockExpectation 38 | Interactor?.fetchTrainsFromSource(sourceCode: "Dublin Belfast", destinationCode: "DUNMR") 39 | wait(for: [unlockExpectation], timeout: 60) 40 | } 41 | 42 | func testFavourite() { 43 | let station = Responses.TrainList()[0] 44 | Interactor?.saveStationToFav(station) 45 | let favStation = presenter.getFavouriteStation() 46 | XCTAssertEqual(station.trainCode, favStation?.trainCode) 47 | } 48 | 49 | 50 | } 51 | class MockPresenter: InteractorToPresenterProtocol { 52 | var expectation: XCTestExpectation? 53 | 54 | var currentError: ErrorMessages? 55 | func stationListFetched(list: [Station]) { 56 | let staticList = Responses.AllStation().stationsList 57 | XCTAssertEqual(staticList.count, list.count) 58 | } 59 | 60 | func fetchedTrainsList(trainsList: [StationTrain]?) { 61 | XCTAssertEqual(trainsList!.count, 1) 62 | expectation?.fulfill() 63 | } 64 | 65 | func showErrorMessage(for Error: ErrorMessages) { 66 | XCTAssertEqual(currentError, Error) 67 | } 68 | 69 | func getFavouriteStation() -> StationTrain? { 70 | StationTrain.getFavouriteStation() 71 | } 72 | } 73 | class MockNetwork: Client { 74 | var currentError: ErrorMessages? 75 | func fetchallStations(resultHandler: @escaping (Result) -> Void) { 76 | if let currentError = currentError { 77 | resultHandler(.failure(currentError)) 78 | } else { 79 | resultHandler(.success(Responses.AllStation())) 80 | } 81 | } 82 | 83 | func fetchTrainsFromSource(_ sourceRequest: URLRequest, resultHandler: @escaping (Result<[StationTrain], ErrorMessages>) -> Void) { 84 | if let currentError = currentError { 85 | resultHandler(.failure(currentError)) 86 | } else { 87 | resultHandler(.success(Responses.TrainList())) 88 | } 89 | } 90 | func proceesTrainListforDestinationCheck(_ sourceRequest: URLRequest, 91 | resultHandler: @escaping (Result) -> Void) { 92 | if let currentError = currentError { 93 | resultHandler(.failure(currentError)) 94 | } else { 95 | resultHandler(.success(Responses.TrainMovements())) 96 | } 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/Entity/StationInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StationInfo.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct StationData: Codable { 12 | var trainsList: [StationTrain] 13 | 14 | enum CodingKeys: String, CodingKey { 15 | case trainsList = "objStationData" 16 | } 17 | } 18 | 19 | struct StationTrain: Codable { 20 | let trainCode: String? 21 | let stationFullName: String? 22 | let stationCode: String? 23 | let trainDate: String? 24 | let dueIn: Int? 25 | let lateBy:Int? 26 | let expArrival:String? 27 | let expDeparture:String? 28 | var destinationDetails:TrainMovement? 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case trainCode = "Traincode" 32 | case stationFullName = "Stationfullname" 33 | case stationCode = "Stationcode" 34 | case trainDate = "Traindate" 35 | case dueIn = "Duein" 36 | case lateBy = "Late" 37 | case expArrival = "Exparrival" 38 | case expDeparture = "Expdepart" 39 | case destinationDetails = "destinationDetails" 40 | } 41 | init(trainCode: String, 42 | fullName: String, 43 | stationCode: String, 44 | trainDate: String, 45 | dueIn: Int, 46 | lateBy:Int, 47 | expArrival:String, 48 | expDeparture:String, 49 | destinationDetails: TrainMovement?) { 50 | self.trainCode = trainCode 51 | self.stationFullName = fullName 52 | self.stationCode = stationCode 53 | self.trainDate = trainDate 54 | self.dueIn = dueIn 55 | self.lateBy = lateBy 56 | self.expArrival = expArrival 57 | self.expDeparture = expDeparture 58 | self.destinationDetails = destinationDetails 59 | } 60 | init(from decoder: Decoder) throws { 61 | let values = try decoder.container(keyedBy: CodingKeys.self) 62 | let trainCode = try values.decode(String.self, forKey: .trainCode) 63 | let stationFullName = try values.decode(String.self, forKey: .stationFullName) 64 | let stationCode = try values.decode(String.self, forKey: .stationCode) 65 | let trainDate = try values.decode(String.self, forKey: .trainDate) 66 | let dueIn = try values.decode(Int.self, forKey: .dueIn) 67 | let lateBy = try values.decode(Int.self, forKey: .lateBy) 68 | let expArrival = try values.decode(String.self, forKey: .expArrival) 69 | let expDeparture = try values.decode(String.self, forKey: .expDeparture) 70 | let destinationDetails = try? values.decode(TrainMovement.self, forKey: .destinationDetails) 71 | 72 | self.init(trainCode: trainCode, 73 | fullName: stationFullName, 74 | stationCode: stationCode, 75 | trainDate: trainDate, 76 | dueIn: dueIn, 77 | lateBy: lateBy, 78 | expArrival: expArrival, 79 | expDeparture: expDeparture, destinationDetails: destinationDetails) 80 | } 81 | } 82 | extension StationData { 83 | static func request(for sourceCode: String) -> URLRequest { 84 | var getStationDataByCodeXML = URL.baseUrl 85 | getStationDataByCodeXML.addPath("/realtime/realtime.asmx/getStationDataByCodeXML") 86 | getStationDataByCodeXML.appendQueryItem("StationCode", value: sourceCode) 87 | return URLRequest(url: getStationDataByCodeXML) 88 | } 89 | } 90 | //MARK: Saving logic 91 | extension StationTrain { 92 | static func saveStationAsFavourite(_ station: StationTrain) { 93 | UserDefaults.standard.set(try? PropertyListEncoder().encode(station), forKey:"FavStationTrain") 94 | } 95 | static func getFavouriteStation() -> StationTrain? { 96 | if let data = UserDefaults.standard.value(forKey:"FavStationTrain") as? Data { 97 | let station = try? PropertyListDecoder().decode(StationTrain.self, from: data) 98 | return station 99 | } 100 | return nil 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /MyTravelHelperTests/SearchTrainPresenterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchTrainPresenterTests.swift 3 | // MyTravelHelperTests 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MyTravelHelper 11 | 12 | class SearchTrainPresenterTests: XCTestCase { 13 | var presenter: SearchTrainPresenter! 14 | var view = SearchTrainMockView() 15 | var interactor = SearchTrainInteractorMock() 16 | 17 | override func setUp() { 18 | presenter = SearchTrainPresenter() 19 | presenter.view = view 20 | presenter.interactor = interactor 21 | interactor.presenter = presenter 22 | } 23 | 24 | func testfetchallStations() { 25 | presenter.fetchallStations() 26 | if !view.isSaveFetchedStatinsCalled { 27 | XCTFail("saveFetchedStations func not called") 28 | } 29 | } 30 | func testErrorMessages() { 31 | let error: ErrorMessages = .noInternet 32 | presenter.showErrorMessage(for: error) 33 | XCTAssertEqual(view.error, error) 34 | } 35 | func testTrainList() { 36 | let sourceName = "dubin" 37 | let destinationName = "foundhound" 38 | 39 | interactor.fetchTrainsFromSource(sourceCode: sourceName, destinationCode: destinationName) 40 | guard view.trainsList?.count == 1, 41 | let stationFullName = view.trainsList?[0].stationFullName , 42 | let locationFullName = view.trainsList?[0].destinationDetails?.locationFullName else { 43 | XCTFail("invalid station") 44 | return 45 | } 46 | XCTAssertEqual(stationFullName, sourceName) 47 | XCTAssertEqual(locationFullName, destinationName) 48 | } 49 | 50 | func testFav() { 51 | let stationTrain: StationTrain = StationTrain.init(trainCode: "A123", fullName: "sourceStation", stationCode: "456", trainDate: "5/01/2021", dueIn: 2, lateBy: 3, expArrival: "16:00", expDeparture: "18:00", destinationDetails: TrainMovement.init(trainCode: "A345", locationCode: "798", locationFullName: "destinationStation", expDeparture: "20:00")) 52 | presenter.saveStationToFav(stationTrain) 53 | let favStation = presenter.getFavouriteStation() 54 | XCTAssertEqual(stationTrain.stationFullName, favStation?.stationFullName) 55 | XCTAssertEqual(stationTrain.stationCode, favStation?.stationCode) 56 | XCTAssertEqual(stationTrain.expArrival, favStation?.expArrival) 57 | XCTAssertEqual(stationTrain.destinationDetails?.locationFullName, favStation?.destinationDetails?.locationFullName) 58 | } 59 | 60 | override func tearDown() { 61 | presenter = nil 62 | } 63 | } 64 | 65 | 66 | class SearchTrainMockView:PresenterToViewProtocol { 67 | var error: ErrorMessages? 68 | func showErrorMessage(for Error: ErrorMessages) { 69 | error = Error 70 | } 71 | var isSaveFetchedStatinsCalled = false 72 | func saveFetchedStations(stations: [Station]?) { 73 | isSaveFetchedStatinsCalled = true 74 | } 75 | var trainsList:[StationTrain]? 76 | func updateLatestTrainList(trainsList: [StationTrain]) { 77 | self.trainsList = trainsList 78 | } 79 | } 80 | 81 | class SearchTrainInteractorMock: PresenterToInteractorProtocol { 82 | var networkClient: Client? 83 | var presenter: InteractorToPresenterProtocol? 84 | 85 | func fetchallStations() { 86 | let station = Station(desc: "Belfast Central", 87 | latitude: 54.6123, 88 | longitude: -5.91744, 89 | code: "BFSTC", 90 | stationId: 228) 91 | presenter?.stationListFetched(list: [station]) 92 | } 93 | 94 | func fetchTrainsFromSource(sourceCode: String, destinationCode: String) { 95 | let stationTrain: StationTrain = StationTrain.init(trainCode: "A123", fullName: sourceCode, stationCode: "456", trainDate: "5/01/2021", dueIn: 2, lateBy: 3, expArrival: "16:00", expDeparture: "18:00", destinationDetails: TrainMovement.init(trainCode: "A345", locationCode: "798", locationFullName: destinationCode, expDeparture: "20:00")) 96 | 97 | presenter?.fetchedTrainsList(trainsList: [stationTrain]) 98 | } 99 | var favStation: StationTrain? 100 | func saveStationToFav(_ station: StationTrain) { 101 | self.favStation = station 102 | } 103 | func getFavouriteStation() -> StationTrain? { 104 | return favStation 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /MyTravelHelper/App-utils/Reachability.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2015 Isuru Nanayakkara 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | import Foundation 24 | import SystemConfiguration 25 | 26 | let reachabilityStatusChangedNotification = "reachabilityStatusChangedNotification" 27 | 28 | enum ReachabilityType: CustomStringConvertible { 29 | case wwan 30 | case wiFi 31 | 32 | var description: String { 33 | switch self { 34 | case .wwan: return "WWAN" 35 | case .wiFi: return "WiFi" 36 | } 37 | } 38 | } 39 | 40 | enum ReachabilityStatus: CustomStringConvertible { 41 | case offline 42 | case online(ReachabilityType) 43 | case unknown 44 | 45 | var description: String { 46 | switch self { 47 | case .offline: return "Offline" 48 | case .online(let type): return "Online (\(type))" 49 | case .unknown: return "Unknown" 50 | } 51 | } 52 | } 53 | 54 | open class Reach { 55 | func connectionStatus() -> ReachabilityStatus { 56 | var zeroAddress = sockaddr_in() 57 | zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) 58 | zeroAddress.sin_family = sa_family_t(AF_INET) 59 | 60 | guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, { 61 | $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { 62 | SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) 63 | } 64 | }) else { 65 | return .unknown 66 | } 67 | 68 | var flags: SCNetworkReachabilityFlags = [] 69 | if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) { 70 | return .unknown 71 | } 72 | 73 | return ReachabilityStatus(reachabilityFlags: flags) 74 | } 75 | 76 | func monitorReachabilityChanges() { 77 | let host = "google.com" 78 | var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) 79 | let reachability = SCNetworkReachabilityCreateWithName(nil, host)! 80 | 81 | SCNetworkReachabilitySetCallback(reachability, { (_, flags, _) in 82 | let status = ReachabilityStatus(reachabilityFlags: flags) 83 | 84 | NotificationCenter.default.post(name: Notification.Name(rawValue: reachabilityStatusChangedNotification), 85 | object: nil, 86 | userInfo: ["Status": status.description]) 87 | 88 | }, &context) 89 | 90 | SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue) 91 | } 92 | 93 | func isNetworkReachable() -> Bool { 94 | 95 | let status = connectionStatus() 96 | 97 | switch status { 98 | case .unknown, .offline: 99 | return false 100 | case .online(.wwan): 101 | return true 102 | case .online(.wiFi): 103 | return true 104 | } 105 | } 106 | } 107 | 108 | extension ReachabilityStatus { 109 | fileprivate init(reachabilityFlags flags: SCNetworkReachabilityFlags) { 110 | let connectionRequired = flags.contains(.connectionRequired) 111 | let isReachable = flags.contains(.reachable) 112 | let isWWAN = flags.contains(.isWWAN) 113 | 114 | if !connectionRequired && isReachable { 115 | if isWWAN { 116 | self = .online(.wwan) 117 | } else { 118 | self = .online(.wiFi) 119 | } 120 | } else { 121 | self = .offline 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/Interactor/SearchTrainInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchTrainInteractor.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XMLParsing 11 | //import Alamofire 12 | 13 | class SearchTrainInteractor: PresenterToInteractorProtocol { 14 | var networkClient: Client? 15 | var _sourceStationCode = String() 16 | var _destinationStationCode = String() 17 | var presenter: InteractorToPresenterProtocol? 18 | func fetchallStations() { 19 | networkClient?.fetchallStations(resultHandler: { (result) in 20 | switch result { 21 | case .success(let station): 22 | self.presenter?.stationListFetched(list: station.stationsList) 23 | break 24 | case .failure(let error): 25 | self.presenter?.showErrorMessage(for: error) 26 | break 27 | } 28 | }) 29 | } 30 | func fetchTrainsFromSource(sourceCode: String, destinationCode: String) { 31 | guard validSourceAndDestination(sourceCode,destinationCode) 32 | else { 33 | self.presenter?.showErrorMessage(for: .InvalidSourceAndDestination) 34 | return 35 | } 36 | _sourceStationCode = sourceCode 37 | _destinationStationCode = destinationCode 38 | let request = StationData.request(for: sourceCode) 39 | networkClient?.fetchTrainsFromSource(request, resultHandler: { (result) in 40 | switch result { 41 | case .success(let trainsList): 42 | self.proceesTrainListforDestinationCheck(trainsList: trainsList) 43 | break 44 | case .failure(let error): 45 | self.presenter?.showErrorMessage(for: error) 46 | break 47 | } 48 | }) 49 | } 50 | private func proceesTrainListforDestinationCheck(trainsList: [StationTrain]) { 51 | var _trainsList = trainsList 52 | DispatchQueue.global(qos: .background).async { 53 | let group = DispatchGroup() 54 | for index in 0...trainsList.count-1 { 55 | guard let code = trainsList[index].trainCode else { 56 | continue 57 | } 58 | group.enter() 59 | let request = TrainMovementsData.request(for: code, date: Date.TrainDate) 60 | self.networkClient?.proceesTrainListforDestinationCheck(request, resultHandler: { (result) in 61 | group.leave() 62 | switch result { 63 | case .success(let trainMovements): 64 | if let firstStationMoment = self.destinationTrains(for: trainMovements) { 65 | _trainsList[index].destinationDetails = firstStationMoment 66 | } 67 | break 68 | case .failure(let error): 69 | self.presenter?.showErrorMessage(for: error) 70 | break 71 | } 72 | }) 73 | group.wait() 74 | } 75 | group.notify(queue: DispatchQueue.main) { 76 | let sourceToDestinationTrains = _trainsList.filter{$0.destinationDetails != nil} 77 | self.presenter?.fetchedTrainsList(trainsList: sourceToDestinationTrains) 78 | if sourceToDestinationTrains.count == 0 { 79 | self.presenter?.showErrorMessage(for: .NoTrainAvailbilityFromSource) 80 | } 81 | } 82 | } 83 | } 84 | //MARK: Favourite Logic 85 | func saveStationToFav(_ station: StationTrain) { 86 | StationTrain.saveStationAsFavourite(station) 87 | } 88 | func getFavouriteStation() -> StationTrain? { 89 | return StationTrain.getFavouriteStation() 90 | } 91 | } 92 | //MARK: Validation & Destination Logic 93 | extension SearchTrainInteractor { 94 | private func destinationTrains(for trainMovements: TrainMovementsData) -> TrainMovement? { 95 | let _movements = trainMovements.trainMovements 96 | let sourceIndex = _movements.firstIndex(where: {$0.locationCode?.caseInsensitiveCompare(self._sourceStationCode) == .orderedSame}) 97 | let destinationIndex = _movements.firstIndex(where: {$0.locationCode?.caseInsensitiveCompare(self._destinationStationCode) == .orderedSame}) 98 | let desiredStationMoment = _movements.filter{$0.locationCode?.caseInsensitiveCompare(self._destinationStationCode) == .orderedSame} 99 | let isDestinationAvailable = desiredStationMoment.count == 1 100 | 101 | if isDestinationAvailable && sourceIndex! < destinationIndex! { 102 | return desiredStationMoment.first 103 | } 104 | return nil 105 | } 106 | 107 | private func validSourceAndDestination(_ sourceCode: String, _ destinationCode: String) -> Bool { 108 | guard sourceCode != destinationCode, 109 | !sourceCode.isEmpty, 110 | !destinationCode.isEmpty else { 111 | return false 112 | } 113 | return true 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/xcuserdata/navroz.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 39 | 41 | 53 | 54 | 55 | 57 | 69 | 70 | 71 | 73 | 85 | 86 | 87 | 89 | 101 | 102 | 103 | 105 | 115 | 116 | 117 | 119 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /MyTravelHelper/Modules/SearchTrains/View/SearchTrainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchTrainViewController.swift 3 | // MyTravelHelper 4 | // 5 | // Created by Satish on 11/03/19. 6 | // Copyright © 2019 Sample. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftSpinner 11 | import DropDown 12 | 13 | class SearchTrainViewController: UIViewController { 14 | @IBOutlet weak var destinationTextField: UITextField! 15 | @IBOutlet weak var sourceTxtField: UITextField! 16 | @IBOutlet weak var trainsListTable: UITableView! 17 | 18 | var stationsList:[Station] = [Station]() 19 | var trains:[StationTrain] = [StationTrain]() 20 | var presenter:ViewToPresenterProtocol? 21 | var dropDown = DropDown() 22 | var transitPoints:(source:String,destination:String) = ("","") 23 | var favStations: StationTrain? 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | trainsListTable.isHidden = true 28 | setupFavButton() 29 | } 30 | 31 | override func viewWillAppear(_ animated: Bool) { 32 | if stationsList.count == 0 { 33 | SwiftSpinner.useContainerView(view) 34 | SwiftSpinner.show("Please wait loading station list ....") 35 | presenter?.fetchallStations() 36 | } 37 | } 38 | @IBAction func searchTrainsTapped(_ sender: Any) { 39 | view.endEditing(true) 40 | showProgressIndicator(view: self.view) 41 | presenter?.searchTapped(source: transitPoints.source, 42 | destination: transitPoints.destination) 43 | } 44 | } 45 | extension SearchTrainViewController { 46 | private func setupFavButton() { 47 | navigationItem.rightBarButtonItem = nil 48 | guard let favStations = presenter?.getFavouriteStation() else { 49 | return 50 | } 51 | self.favStations = favStations 52 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Favourite Stations", 53 | style: .plain, 54 | target: self, 55 | action: #selector(setFavStations)) 56 | } 57 | @objc func setFavStations() { 58 | transitPoints.source = favStations?.stationFullName ?? "" 59 | transitPoints.destination = favStations?.destinationDetails?.locationFullName ?? "" 60 | self.sourceTxtField.text = transitPoints.source 61 | self.destinationTextField.text = transitPoints.destination 62 | } 63 | } 64 | 65 | extension SearchTrainViewController:PresenterToViewProtocol { 66 | func showErrorMessage(for Error: ErrorMessages) { 67 | DispatchQueue.main.async { 68 | switch Error { 69 | case .noInternet: 70 | self.showNoInterNetAvailabilityMessage() 71 | break 72 | case .NoTrainsFound: 73 | self.showNoTrainsFoundAlert() 74 | break 75 | case .NoTrainAvailbilityFromSource: 76 | self.showNoTrainAvailbilityFromSource() 77 | break 78 | case .InvalidSourceAndDestination: 79 | self.showInvalidSourceOrDestinationAlert() 80 | break 81 | } 82 | } 83 | } 84 | 85 | func showNoInterNetAvailabilityMessage() { 86 | trainsListTable.isHidden = true 87 | hideProgressIndicator(view: self.view) 88 | showAlert(title: "No Internet", message: "Please Check you internet connection and try again", actionTitle: "Okay") 89 | } 90 | func showInvalidSourceAndDestination() { 91 | trainsListTable.isHidden = true 92 | hideProgressIndicator(view: self.view) 93 | showAlert(title: "Invalid Source/destination", message: "Please select valid Source and destination", actionTitle: "Okay") 94 | } 95 | func showNoTrainAvailbilityFromSource() { 96 | trainsListTable.isHidden = true 97 | hideProgressIndicator(view: self.view) 98 | showAlert(title: "No Trains", message: "Sorry No trains arriving source station in another 90 mins", actionTitle: "Okay") 99 | } 100 | 101 | func updateLatestTrainList(trainsList: [StationTrain]) { 102 | DispatchQueue.main.async { 103 | hideProgressIndicator(view: self.view) 104 | self.trains = trainsList 105 | self.trainsListTable.isHidden = false 106 | self.trainsListTable.reloadData() 107 | } 108 | } 109 | 110 | func showNoTrainsFoundAlert() { 111 | trainsListTable.isHidden = true 112 | hideProgressIndicator(view: self.view) 113 | trainsListTable.isHidden = true 114 | showAlert(title: "No Trains", message: "Sorry No trains Found from source to destination in another 90 mins", actionTitle: "Okay") 115 | } 116 | 117 | func showAlert(title:String,message:String,actionTitle:String) { 118 | let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert) 119 | alert.addAction(UIAlertAction(title: actionTitle, style: UIAlertAction.Style.default, handler: nil)) 120 | self.present(alert, animated: true, completion: nil) 121 | } 122 | 123 | func showInvalidSourceOrDestinationAlert() { 124 | trainsListTable.isHidden = true 125 | hideProgressIndicator(view: self.view) 126 | showAlert(title: "Invalid Source/Destination", message: "Invalid Source or Destination Station names Please Check", actionTitle: "Okay") 127 | } 128 | 129 | func saveFetchedStations(stations: [Station]?) { 130 | DispatchQueue.main.async { 131 | if let _stations = stations { 132 | self.stationsList = _stations 133 | } 134 | SwiftSpinner.hide() 135 | } 136 | } 137 | } 138 | 139 | extension SearchTrainViewController:UITextFieldDelegate { 140 | func textFieldDidBeginEditing(_ textField: UITextField) { 141 | dropDown = DropDown() 142 | dropDown.anchorView = textField 143 | dropDown.direction = .bottom 144 | dropDown.bottomOffset = CGPoint(x: 0, y:(dropDown.anchorView?.plainView.bounds.height)!) 145 | dropDown.dataSource = stationsList.compactMap{$0.stationDesc} 146 | dropDown.selectionAction = { (index: Int, item: String) in 147 | if textField == self.sourceTxtField { 148 | self.transitPoints.source = item 149 | }else { 150 | self.transitPoints.destination = item 151 | } 152 | textField.text = item 153 | } 154 | dropDown.show() 155 | } 156 | 157 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 158 | dropDown.hide() 159 | return textField.resignFirstResponder() 160 | } 161 | 162 | func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { 163 | if let inputedText = textField.text { 164 | var desiredSearchText = inputedText 165 | if string != "\n" && !string.isEmpty{ 166 | desiredSearchText = desiredSearchText + string 167 | }else { 168 | desiredSearchText = String(desiredSearchText.dropLast()) 169 | } 170 | 171 | //dropDown.dataSource = stationsList 172 | dropDown.show() 173 | dropDown.reloadAllComponents() 174 | } 175 | return true 176 | } 177 | } 178 | 179 | extension SearchTrainViewController:UITableViewDataSource { 180 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 181 | return trains.count 182 | } 183 | 184 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 185 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "train", 186 | for: indexPath) as? TrainInfoCell 187 | else { 188 | fatalError("cell misconfiguration") 189 | } 190 | cell.train = trains[indexPath.row] 191 | cell.favStationTrain = { station in 192 | self.presenter?.saveStationToFav(station) 193 | self.setupFavButton() 194 | } 195 | return cell 196 | } 197 | 198 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 199 | return 140 200 | } 201 | } 202 | 203 | 204 | -------------------------------------------------------------------------------- /MyTravelHelper.xcworkspace/xcuserdata/navrozsingh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 52 | 53 | 67 | 68 | 69 | 70 | 71 | 73 | 85 | 86 | 87 | 89 | 101 | 102 | 103 | 105 | 117 | 118 | 132 | 133 | 147 | 148 | 149 | 150 | 151 | 153 | 165 | 166 | 167 | 169 | 181 | 182 | 183 | 185 | 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /MyTravelHelper/App-Ui/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 103 | 109 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 129 | 135 | 141 | 142 | 143 | 144 | 145 | 146 | 156 | 157 | 158 | 159 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /MyTravelHelper.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0301C30125A3875100A5E630 /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0301C30025A3875100A5E630 /* Extension.swift */; }; 11 | 03399B7C25A4E6D000A8A343 /* readme.MD in Resources */ = {isa = PBXBuildFile; fileRef = 03399B7B25A4E6D000A8A343 /* readme.MD */; }; 12 | 1654AF9B5118F79C1ACB2509 /* Pods_MyTravelHelperTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD4A9E0F8C97F8AA92AAA053 /* Pods_MyTravelHelperTests.framework */; }; 13 | 753C8FA422361EC9005FF13B /* TrainMovements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FA122361EC9005FF13B /* TrainMovements.swift */; }; 14 | 753C8FA522361EC9005FF13B /* StationInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FA222361EC9005FF13B /* StationInfo.swift */; }; 15 | 753C8FA622361EC9005FF13B /* Stations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FA322361EC9005FF13B /* Stations.swift */; }; 16 | 753C8FA822361FC0005FF13B /* SearchTrainProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FA722361FC0005FF13B /* SearchTrainProtocols.swift */; }; 17 | 753C8FAA2236228C005FF13B /* SearchTrainPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FA92236228C005FF13B /* SearchTrainPresenter.swift */; }; 18 | 753C8FAC223623C2005FF13B /* SearchTrainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FAB223623C2005FF13B /* SearchTrainInteractor.swift */; }; 19 | 753C8FAE22362463005FF13B /* SearchTrainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FAD22362463005FF13B /* SearchTrainViewController.swift */; }; 20 | 753C8FB022365E9A005FF13B /* SearchTrainRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FAF22365E9A005FF13B /* SearchTrainRouter.swift */; }; 21 | 753C8FB522367D1C005FF13B /* AppUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FB422367D1C005FF13B /* AppUtils.swift */; }; 22 | 753C8FB722367DF4005FF13B /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FB622367DF4005FF13B /* ProgressIndicator.swift */; }; 23 | 753C8FBA2236BD80005FF13B /* TrainInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FB92236BD80005FF13B /* TrainInfoCell.swift */; }; 24 | 753C8FBC223731E3005FF13B /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FBB223731E3005FF13B /* Reachability.swift */; }; 25 | 753C8FBE2237367E005FF13B /* SearchTrainPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753C8FBD2237367E005FF13B /* SearchTrainPresenterTests.swift */; }; 26 | 753F0847223618AB00175107 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753F0846223618AB00175107 /* AppDelegate.swift */; }; 27 | 753F084C223618AB00175107 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 753F084A223618AB00175107 /* Main.storyboard */; }; 28 | 753F084E223618AD00175107 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 753F084D223618AD00175107 /* Assets.xcassets */; }; 29 | 753F0851223618AD00175107 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 753F084F223618AD00175107 /* LaunchScreen.storyboard */; }; 30 | 753F087122361A6500175107 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753F087022361A6500175107 /* AppConstants.swift */; }; 31 | 8CBBCE0D40B8FE266A1617DE /* Pods_MyTravelHelper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF635B96516F13D67ADE01AF /* Pods_MyTravelHelper.framework */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | 753F0858223618AE00175107 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 753F083B223618AB00175107 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 753F0842223618AB00175107; 40 | remoteInfo = MyTravelHelper; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 0301C30025A3875100A5E630 /* Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; 46 | 03399B7B25A4E6D000A8A343 /* readme.MD */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = readme.MD; sourceTree = ""; }; 47 | 11F481535B44216B54872EF2 /* Pods-MyTravelHelperTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyTravelHelperTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MyTravelHelperTests/Pods-MyTravelHelperTests.debug.xcconfig"; sourceTree = ""; }; 48 | 2F515F7A370B032163D445BC /* Pods-MyTravelHelper.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyTravelHelper.release.xcconfig"; path = "Pods/Target Support Files/Pods-MyTravelHelper/Pods-MyTravelHelper.release.xcconfig"; sourceTree = ""; }; 49 | 4A3D05BF450D92647BC863F2 /* Pods-MyTravelHelper.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyTravelHelper.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MyTravelHelper/Pods-MyTravelHelper.debug.xcconfig"; sourceTree = ""; }; 50 | 753C8FA122361EC9005FF13B /* TrainMovements.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrainMovements.swift; sourceTree = ""; }; 51 | 753C8FA222361EC9005FF13B /* StationInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StationInfo.swift; sourceTree = ""; }; 52 | 753C8FA322361EC9005FF13B /* Stations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stations.swift; sourceTree = ""; }; 53 | 753C8FA722361FC0005FF13B /* SearchTrainProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTrainProtocols.swift; sourceTree = ""; }; 54 | 753C8FA92236228C005FF13B /* SearchTrainPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTrainPresenter.swift; sourceTree = ""; }; 55 | 753C8FAB223623C2005FF13B /* SearchTrainInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTrainInteractor.swift; sourceTree = ""; }; 56 | 753C8FAD22362463005FF13B /* SearchTrainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTrainViewController.swift; sourceTree = ""; }; 57 | 753C8FAF22365E9A005FF13B /* SearchTrainRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTrainRouter.swift; sourceTree = ""; }; 58 | 753C8FB422367D1C005FF13B /* AppUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUtils.swift; sourceTree = ""; }; 59 | 753C8FB622367DF4005FF13B /* ProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = ""; }; 60 | 753C8FB92236BD80005FF13B /* TrainInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrainInfoCell.swift; sourceTree = ""; }; 61 | 753C8FBB223731E3005FF13B /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; 62 | 753C8FBD2237367E005FF13B /* SearchTrainPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTrainPresenterTests.swift; sourceTree = ""; }; 63 | 753F0843223618AB00175107 /* MyTravelHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyTravelHelper.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 753F0846223618AB00175107 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 65 | 753F084B223618AB00175107 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 66 | 753F084D223618AD00175107 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 67 | 753F0850223618AD00175107 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 68 | 753F0852223618AD00175107 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 69 | 753F0857223618AE00175107 /* MyTravelHelperTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyTravelHelperTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | 753F085D223618AE00175107 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | 753F087022361A6500175107 /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 72 | 849A0A29A57A433EA5AB15FB /* Pods-MyTravelHelperTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MyTravelHelperTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MyTravelHelperTests/Pods-MyTravelHelperTests.release.xcconfig"; sourceTree = ""; }; 73 | AF635B96516F13D67ADE01AF /* Pods_MyTravelHelper.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyTravelHelper.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | BD4A9E0F8C97F8AA92AAA053 /* Pods_MyTravelHelperTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MyTravelHelperTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 753F0840223618AB00175107 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 8CBBCE0D40B8FE266A1617DE /* Pods_MyTravelHelper.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | 753F0854223618AE00175107 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | 1654AF9B5118F79C1ACB2509 /* Pods_MyTravelHelperTests.framework in Frameworks */, 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | /* End PBXFrameworksBuildPhase section */ 95 | 96 | /* Begin PBXGroup section */ 97 | 2BABC0CEC890350419CA1D40 /* Pods */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 4A3D05BF450D92647BC863F2 /* Pods-MyTravelHelper.debug.xcconfig */, 101 | 2F515F7A370B032163D445BC /* Pods-MyTravelHelper.release.xcconfig */, 102 | 11F481535B44216B54872EF2 /* Pods-MyTravelHelperTests.debug.xcconfig */, 103 | 849A0A29A57A433EA5AB15FB /* Pods-MyTravelHelperTests.release.xcconfig */, 104 | ); 105 | name = Pods; 106 | sourceTree = ""; 107 | }; 108 | 5DCA91A6E1C4EF2A4B4FE637 /* Frameworks */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | AF635B96516F13D67ADE01AF /* Pods_MyTravelHelper.framework */, 112 | BD4A9E0F8C97F8AA92AAA053 /* Pods_MyTravelHelperTests.framework */, 113 | ); 114 | name = Frameworks; 115 | sourceTree = ""; 116 | }; 117 | 753C8FB822367E64005FF13B /* App-Ui */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 753F084F223618AD00175107 /* LaunchScreen.storyboard */, 121 | 753F084A223618AB00175107 /* Main.storyboard */, 122 | ); 123 | path = "App-Ui"; 124 | sourceTree = ""; 125 | }; 126 | 753F083A223618AB00175107 = { 127 | isa = PBXGroup; 128 | children = ( 129 | 03399B7B25A4E6D000A8A343 /* readme.MD */, 130 | 753F0845223618AB00175107 /* MyTravelHelper */, 131 | 753F085A223618AE00175107 /* MyTravelHelperTests */, 132 | 753F0844223618AB00175107 /* Products */, 133 | 2BABC0CEC890350419CA1D40 /* Pods */, 134 | 5DCA91A6E1C4EF2A4B4FE637 /* Frameworks */, 135 | ); 136 | sourceTree = ""; 137 | }; 138 | 753F0844223618AB00175107 /* Products */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 753F0843223618AB00175107 /* MyTravelHelper.app */, 142 | 753F0857223618AE00175107 /* MyTravelHelperTests.xctest */, 143 | ); 144 | name = Products; 145 | sourceTree = ""; 146 | }; 147 | 753F0845223618AB00175107 /* MyTravelHelper */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 753C8FB822367E64005FF13B /* App-Ui */, 151 | 753F086F22361A5100175107 /* App-utils */, 152 | 753F08672236190700175107 /* Modules */, 153 | 753F0846223618AB00175107 /* AppDelegate.swift */, 154 | 753F084D223618AD00175107 /* Assets.xcassets */, 155 | 753F0852223618AD00175107 /* Info.plist */, 156 | ); 157 | path = MyTravelHelper; 158 | sourceTree = ""; 159 | }; 160 | 753F085A223618AE00175107 /* MyTravelHelperTests */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 753C8FBD2237367E005FF13B /* SearchTrainPresenterTests.swift */, 164 | 753F085D223618AE00175107 /* Info.plist */, 165 | ); 166 | path = MyTravelHelperTests; 167 | sourceTree = ""; 168 | }; 169 | 753F08672236190700175107 /* Modules */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 753F08682236191900175107 /* SearchTrains */, 173 | ); 174 | path = Modules; 175 | sourceTree = ""; 176 | }; 177 | 753F08682236191900175107 /* SearchTrains */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 753F086A2236196400175107 /* Entity */, 181 | 753F08692236196400175107 /* Interactor */, 182 | 753F086D2236196500175107 /* Presenter */, 183 | 753F086B2236196500175107 /* Protocols */, 184 | 753F086C2236196500175107 /* Router */, 185 | 753F086E2236196500175107 /* View */, 186 | ); 187 | path = SearchTrains; 188 | sourceTree = ""; 189 | }; 190 | 753F08692236196400175107 /* Interactor */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | 753C8FAB223623C2005FF13B /* SearchTrainInteractor.swift */, 194 | ); 195 | path = Interactor; 196 | sourceTree = ""; 197 | }; 198 | 753F086A2236196400175107 /* Entity */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 753C8FA222361EC9005FF13B /* StationInfo.swift */, 202 | 753C8FA322361EC9005FF13B /* Stations.swift */, 203 | 753C8FA122361EC9005FF13B /* TrainMovements.swift */, 204 | ); 205 | path = Entity; 206 | sourceTree = ""; 207 | }; 208 | 753F086B2236196500175107 /* Protocols */ = { 209 | isa = PBXGroup; 210 | children = ( 211 | 753C8FA722361FC0005FF13B /* SearchTrainProtocols.swift */, 212 | ); 213 | path = Protocols; 214 | sourceTree = ""; 215 | }; 216 | 753F086C2236196500175107 /* Router */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | 753C8FAF22365E9A005FF13B /* SearchTrainRouter.swift */, 220 | ); 221 | path = Router; 222 | sourceTree = ""; 223 | }; 224 | 753F086D2236196500175107 /* Presenter */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | 753C8FA92236228C005FF13B /* SearchTrainPresenter.swift */, 228 | ); 229 | path = Presenter; 230 | sourceTree = ""; 231 | }; 232 | 753F086E2236196500175107 /* View */ = { 233 | isa = PBXGroup; 234 | children = ( 235 | 753C8FAD22362463005FF13B /* SearchTrainViewController.swift */, 236 | 753C8FB92236BD80005FF13B /* TrainInfoCell.swift */, 237 | ); 238 | path = View; 239 | sourceTree = ""; 240 | }; 241 | 753F086F22361A5100175107 /* App-utils */ = { 242 | isa = PBXGroup; 243 | children = ( 244 | 753C8FBB223731E3005FF13B /* Reachability.swift */, 245 | 753F087022361A6500175107 /* AppConstants.swift */, 246 | 753C8FB422367D1C005FF13B /* AppUtils.swift */, 247 | 753C8FB622367DF4005FF13B /* ProgressIndicator.swift */, 248 | 0301C30025A3875100A5E630 /* Extension.swift */, 249 | ); 250 | path = "App-utils"; 251 | sourceTree = ""; 252 | }; 253 | /* End PBXGroup section */ 254 | 255 | /* Begin PBXNativeTarget section */ 256 | 753F0842223618AB00175107 /* MyTravelHelper */ = { 257 | isa = PBXNativeTarget; 258 | buildConfigurationList = 753F0860223618AE00175107 /* Build configuration list for PBXNativeTarget "MyTravelHelper" */; 259 | buildPhases = ( 260 | B959427FDE4AD37906C260C9 /* [CP] Check Pods Manifest.lock */, 261 | 753F083F223618AB00175107 /* Sources */, 262 | 753F0840223618AB00175107 /* Frameworks */, 263 | 753F0841223618AB00175107 /* Resources */, 264 | CEB0D538B5BF80D48BFD0091 /* [CP] Embed Pods Frameworks */, 265 | ); 266 | buildRules = ( 267 | ); 268 | dependencies = ( 269 | ); 270 | name = MyTravelHelper; 271 | productName = MyTravelHelper; 272 | productReference = 753F0843223618AB00175107 /* MyTravelHelper.app */; 273 | productType = "com.apple.product-type.application"; 274 | }; 275 | 753F0856223618AE00175107 /* MyTravelHelperTests */ = { 276 | isa = PBXNativeTarget; 277 | buildConfigurationList = 753F0863223618AE00175107 /* Build configuration list for PBXNativeTarget "MyTravelHelperTests" */; 278 | buildPhases = ( 279 | 4F7E6086A5A283A9BEEF924E /* [CP] Check Pods Manifest.lock */, 280 | 753F0853223618AE00175107 /* Sources */, 281 | 753F0854223618AE00175107 /* Frameworks */, 282 | 753F0855223618AE00175107 /* Resources */, 283 | ); 284 | buildRules = ( 285 | ); 286 | dependencies = ( 287 | 753F0859223618AE00175107 /* PBXTargetDependency */, 288 | ); 289 | name = MyTravelHelperTests; 290 | productName = MyTravelHelperTests; 291 | productReference = 753F0857223618AE00175107 /* MyTravelHelperTests.xctest */; 292 | productType = "com.apple.product-type.bundle.unit-test"; 293 | }; 294 | /* End PBXNativeTarget section */ 295 | 296 | /* Begin PBXProject section */ 297 | 753F083B223618AB00175107 /* Project object */ = { 298 | isa = PBXProject; 299 | attributes = { 300 | LastSwiftUpdateCheck = 1010; 301 | LastUpgradeCheck = 1010; 302 | ORGANIZATIONNAME = Sample; 303 | TargetAttributes = { 304 | 753F0842223618AB00175107 = { 305 | CreatedOnToolsVersion = 10.1; 306 | LastSwiftMigration = 1020; 307 | }; 308 | 753F0856223618AE00175107 = { 309 | CreatedOnToolsVersion = 10.1; 310 | LastSwiftMigration = 1020; 311 | TestTargetID = 753F0842223618AB00175107; 312 | }; 313 | }; 314 | }; 315 | buildConfigurationList = 753F083E223618AB00175107 /* Build configuration list for PBXProject "MyTravelHelper" */; 316 | compatibilityVersion = "Xcode 9.3"; 317 | developmentRegion = en; 318 | hasScannedForEncodings = 0; 319 | knownRegions = ( 320 | en, 321 | Base, 322 | ); 323 | mainGroup = 753F083A223618AB00175107; 324 | productRefGroup = 753F0844223618AB00175107 /* Products */; 325 | projectDirPath = ""; 326 | projectRoot = ""; 327 | targets = ( 328 | 753F0842223618AB00175107 /* MyTravelHelper */, 329 | 753F0856223618AE00175107 /* MyTravelHelperTests */, 330 | ); 331 | }; 332 | /* End PBXProject section */ 333 | 334 | /* Begin PBXResourcesBuildPhase section */ 335 | 753F0841223618AB00175107 /* Resources */ = { 336 | isa = PBXResourcesBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | 753F0851223618AD00175107 /* LaunchScreen.storyboard in Resources */, 340 | 753F084E223618AD00175107 /* Assets.xcassets in Resources */, 341 | 03399B7C25A4E6D000A8A343 /* readme.MD in Resources */, 342 | 753F084C223618AB00175107 /* Main.storyboard in Resources */, 343 | ); 344 | runOnlyForDeploymentPostprocessing = 0; 345 | }; 346 | 753F0855223618AE00175107 /* Resources */ = { 347 | isa = PBXResourcesBuildPhase; 348 | buildActionMask = 2147483647; 349 | files = ( 350 | ); 351 | runOnlyForDeploymentPostprocessing = 0; 352 | }; 353 | /* End PBXResourcesBuildPhase section */ 354 | 355 | /* Begin PBXShellScriptBuildPhase section */ 356 | 4F7E6086A5A283A9BEEF924E /* [CP] Check Pods Manifest.lock */ = { 357 | isa = PBXShellScriptBuildPhase; 358 | buildActionMask = 2147483647; 359 | files = ( 360 | ); 361 | inputPaths = ( 362 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 363 | "${PODS_ROOT}/Manifest.lock", 364 | ); 365 | name = "[CP] Check Pods Manifest.lock"; 366 | outputPaths = ( 367 | "$(DERIVED_FILE_DIR)/Pods-MyTravelHelperTests-checkManifestLockResult.txt", 368 | ); 369 | runOnlyForDeploymentPostprocessing = 0; 370 | shellPath = /bin/sh; 371 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 372 | showEnvVarsInLog = 0; 373 | }; 374 | B959427FDE4AD37906C260C9 /* [CP] Check Pods Manifest.lock */ = { 375 | isa = PBXShellScriptBuildPhase; 376 | buildActionMask = 2147483647; 377 | files = ( 378 | ); 379 | inputPaths = ( 380 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 381 | "${PODS_ROOT}/Manifest.lock", 382 | ); 383 | name = "[CP] Check Pods Manifest.lock"; 384 | outputPaths = ( 385 | "$(DERIVED_FILE_DIR)/Pods-MyTravelHelper-checkManifestLockResult.txt", 386 | ); 387 | runOnlyForDeploymentPostprocessing = 0; 388 | shellPath = /bin/sh; 389 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 390 | showEnvVarsInLog = 0; 391 | }; 392 | CEB0D538B5BF80D48BFD0091 /* [CP] Embed Pods Frameworks */ = { 393 | isa = PBXShellScriptBuildPhase; 394 | buildActionMask = 2147483647; 395 | files = ( 396 | ); 397 | inputFileListPaths = ( 398 | "${PODS_ROOT}/Target Support Files/Pods-MyTravelHelper/Pods-MyTravelHelper-frameworks-${CONFIGURATION}-input-files.xcfilelist", 399 | ); 400 | name = "[CP] Embed Pods Frameworks"; 401 | outputFileListPaths = ( 402 | "${PODS_ROOT}/Target Support Files/Pods-MyTravelHelper/Pods-MyTravelHelper-frameworks-${CONFIGURATION}-output-files.xcfilelist", 403 | ); 404 | runOnlyForDeploymentPostprocessing = 0; 405 | shellPath = /bin/sh; 406 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MyTravelHelper/Pods-MyTravelHelper-frameworks.sh\"\n"; 407 | showEnvVarsInLog = 0; 408 | }; 409 | /* End PBXShellScriptBuildPhase section */ 410 | 411 | /* Begin PBXSourcesBuildPhase section */ 412 | 753F083F223618AB00175107 /* Sources */ = { 413 | isa = PBXSourcesBuildPhase; 414 | buildActionMask = 2147483647; 415 | files = ( 416 | 753C8FBA2236BD80005FF13B /* TrainInfoCell.swift in Sources */, 417 | 753C8FA822361FC0005FF13B /* SearchTrainProtocols.swift in Sources */, 418 | 753C8FAA2236228C005FF13B /* SearchTrainPresenter.swift in Sources */, 419 | 753C8FAE22362463005FF13B /* SearchTrainViewController.swift in Sources */, 420 | 753C8FB722367DF4005FF13B /* ProgressIndicator.swift in Sources */, 421 | 753C8FB522367D1C005FF13B /* AppUtils.swift in Sources */, 422 | 753C8FA522361EC9005FF13B /* StationInfo.swift in Sources */, 423 | 753C8FBC223731E3005FF13B /* Reachability.swift in Sources */, 424 | 0301C30125A3875100A5E630 /* Extension.swift in Sources */, 425 | 753C8FA622361EC9005FF13B /* Stations.swift in Sources */, 426 | 753C8FAC223623C2005FF13B /* SearchTrainInteractor.swift in Sources */, 427 | 753F087122361A6500175107 /* AppConstants.swift in Sources */, 428 | 753F0847223618AB00175107 /* AppDelegate.swift in Sources */, 429 | 753C8FA422361EC9005FF13B /* TrainMovements.swift in Sources */, 430 | 753C8FB022365E9A005FF13B /* SearchTrainRouter.swift in Sources */, 431 | ); 432 | runOnlyForDeploymentPostprocessing = 0; 433 | }; 434 | 753F0853223618AE00175107 /* Sources */ = { 435 | isa = PBXSourcesBuildPhase; 436 | buildActionMask = 2147483647; 437 | files = ( 438 | 753C8FBE2237367E005FF13B /* SearchTrainPresenterTests.swift in Sources */, 439 | ); 440 | runOnlyForDeploymentPostprocessing = 0; 441 | }; 442 | /* End PBXSourcesBuildPhase section */ 443 | 444 | /* Begin PBXTargetDependency section */ 445 | 753F0859223618AE00175107 /* PBXTargetDependency */ = { 446 | isa = PBXTargetDependency; 447 | target = 753F0842223618AB00175107 /* MyTravelHelper */; 448 | targetProxy = 753F0858223618AE00175107 /* PBXContainerItemProxy */; 449 | }; 450 | /* End PBXTargetDependency section */ 451 | 452 | /* Begin PBXVariantGroup section */ 453 | 753F084A223618AB00175107 /* Main.storyboard */ = { 454 | isa = PBXVariantGroup; 455 | children = ( 456 | 753F084B223618AB00175107 /* Base */, 457 | ); 458 | name = Main.storyboard; 459 | sourceTree = ""; 460 | }; 461 | 753F084F223618AD00175107 /* LaunchScreen.storyboard */ = { 462 | isa = PBXVariantGroup; 463 | children = ( 464 | 753F0850223618AD00175107 /* Base */, 465 | ); 466 | name = LaunchScreen.storyboard; 467 | sourceTree = ""; 468 | }; 469 | /* End PBXVariantGroup section */ 470 | 471 | /* Begin XCBuildConfiguration section */ 472 | 753F085E223618AE00175107 /* Debug */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | ALWAYS_SEARCH_USER_PATHS = NO; 476 | CLANG_ANALYZER_NONNULL = YES; 477 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 478 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 479 | CLANG_CXX_LIBRARY = "libc++"; 480 | CLANG_ENABLE_MODULES = YES; 481 | CLANG_ENABLE_OBJC_ARC = YES; 482 | CLANG_ENABLE_OBJC_WEAK = YES; 483 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 484 | CLANG_WARN_BOOL_CONVERSION = YES; 485 | CLANG_WARN_COMMA = YES; 486 | CLANG_WARN_CONSTANT_CONVERSION = YES; 487 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 488 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 489 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 490 | CLANG_WARN_EMPTY_BODY = YES; 491 | CLANG_WARN_ENUM_CONVERSION = YES; 492 | CLANG_WARN_INFINITE_RECURSION = YES; 493 | CLANG_WARN_INT_CONVERSION = YES; 494 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 495 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 496 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 497 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 498 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 499 | CLANG_WARN_STRICT_PROTOTYPES = YES; 500 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 501 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 502 | CLANG_WARN_UNREACHABLE_CODE = YES; 503 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 504 | CODE_SIGN_IDENTITY = "iPhone Developer"; 505 | COPY_PHASE_STRIP = NO; 506 | DEBUG_INFORMATION_FORMAT = dwarf; 507 | ENABLE_STRICT_OBJC_MSGSEND = YES; 508 | ENABLE_TESTABILITY = YES; 509 | GCC_C_LANGUAGE_STANDARD = gnu11; 510 | GCC_DYNAMIC_NO_PIC = NO; 511 | GCC_NO_COMMON_BLOCKS = YES; 512 | GCC_OPTIMIZATION_LEVEL = 0; 513 | GCC_PREPROCESSOR_DEFINITIONS = ( 514 | "DEBUG=1", 515 | "$(inherited)", 516 | ); 517 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 518 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 519 | GCC_WARN_UNDECLARED_SELECTOR = YES; 520 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 521 | GCC_WARN_UNUSED_FUNCTION = YES; 522 | GCC_WARN_UNUSED_VARIABLE = YES; 523 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 524 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 525 | MTL_FAST_MATH = YES; 526 | ONLY_ACTIVE_ARCH = YES; 527 | SDKROOT = iphoneos; 528 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 529 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 530 | }; 531 | name = Debug; 532 | }; 533 | 753F085F223618AE00175107 /* Release */ = { 534 | isa = XCBuildConfiguration; 535 | buildSettings = { 536 | ALWAYS_SEARCH_USER_PATHS = NO; 537 | CLANG_ANALYZER_NONNULL = YES; 538 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 539 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 540 | CLANG_CXX_LIBRARY = "libc++"; 541 | CLANG_ENABLE_MODULES = YES; 542 | CLANG_ENABLE_OBJC_ARC = YES; 543 | CLANG_ENABLE_OBJC_WEAK = YES; 544 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 545 | CLANG_WARN_BOOL_CONVERSION = YES; 546 | CLANG_WARN_COMMA = YES; 547 | CLANG_WARN_CONSTANT_CONVERSION = YES; 548 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 549 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 550 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 551 | CLANG_WARN_EMPTY_BODY = YES; 552 | CLANG_WARN_ENUM_CONVERSION = YES; 553 | CLANG_WARN_INFINITE_RECURSION = YES; 554 | CLANG_WARN_INT_CONVERSION = YES; 555 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 556 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 557 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 558 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 559 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 560 | CLANG_WARN_STRICT_PROTOTYPES = YES; 561 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 562 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 563 | CLANG_WARN_UNREACHABLE_CODE = YES; 564 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 565 | CODE_SIGN_IDENTITY = "iPhone Developer"; 566 | COPY_PHASE_STRIP = NO; 567 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 568 | ENABLE_NS_ASSERTIONS = NO; 569 | ENABLE_STRICT_OBJC_MSGSEND = YES; 570 | GCC_C_LANGUAGE_STANDARD = gnu11; 571 | GCC_NO_COMMON_BLOCKS = YES; 572 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 573 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 574 | GCC_WARN_UNDECLARED_SELECTOR = YES; 575 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 576 | GCC_WARN_UNUSED_FUNCTION = YES; 577 | GCC_WARN_UNUSED_VARIABLE = YES; 578 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 579 | MTL_ENABLE_DEBUG_INFO = NO; 580 | MTL_FAST_MATH = YES; 581 | SDKROOT = iphoneos; 582 | SWIFT_COMPILATION_MODE = wholemodule; 583 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 584 | VALIDATE_PRODUCT = YES; 585 | }; 586 | name = Release; 587 | }; 588 | 753F0861223618AE00175107 /* Debug */ = { 589 | isa = XCBuildConfiguration; 590 | baseConfigurationReference = 4A3D05BF450D92647BC863F2 /* Pods-MyTravelHelper.debug.xcconfig */; 591 | buildSettings = { 592 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 593 | CODE_SIGN_STYLE = Automatic; 594 | INFOPLIST_FILE = MyTravelHelper/Info.plist; 595 | LD_RUNPATH_SEARCH_PATHS = ( 596 | "$(inherited)", 597 | "@executable_path/Frameworks", 598 | ); 599 | PRODUCT_BUNDLE_IDENTIFIER = com.example.com.MyTravelHelper; 600 | PRODUCT_NAME = "$(TARGET_NAME)"; 601 | SWIFT_VERSION = 5.0; 602 | TARGETED_DEVICE_FAMILY = "1,2"; 603 | }; 604 | name = Debug; 605 | }; 606 | 753F0862223618AE00175107 /* Release */ = { 607 | isa = XCBuildConfiguration; 608 | baseConfigurationReference = 2F515F7A370B032163D445BC /* Pods-MyTravelHelper.release.xcconfig */; 609 | buildSettings = { 610 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 611 | CODE_SIGN_STYLE = Automatic; 612 | INFOPLIST_FILE = MyTravelHelper/Info.plist; 613 | LD_RUNPATH_SEARCH_PATHS = ( 614 | "$(inherited)", 615 | "@executable_path/Frameworks", 616 | ); 617 | PRODUCT_BUNDLE_IDENTIFIER = com.example.com.MyTravelHelper; 618 | PRODUCT_NAME = "$(TARGET_NAME)"; 619 | SWIFT_VERSION = 5.0; 620 | TARGETED_DEVICE_FAMILY = "1,2"; 621 | }; 622 | name = Release; 623 | }; 624 | 753F0864223618AE00175107 /* Debug */ = { 625 | isa = XCBuildConfiguration; 626 | baseConfigurationReference = 11F481535B44216B54872EF2 /* Pods-MyTravelHelperTests.debug.xcconfig */; 627 | buildSettings = { 628 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 629 | BUNDLE_LOADER = "$(TEST_HOST)"; 630 | CODE_SIGN_STYLE = Automatic; 631 | INFOPLIST_FILE = MyTravelHelperTests/Info.plist; 632 | LD_RUNPATH_SEARCH_PATHS = ( 633 | "$(inherited)", 634 | "@executable_path/Frameworks", 635 | "@loader_path/Frameworks", 636 | ); 637 | PRODUCT_BUNDLE_IDENTIFIER = com.example.com.MyTravelHelperTests; 638 | PRODUCT_NAME = "$(TARGET_NAME)"; 639 | SWIFT_VERSION = 5.0; 640 | TARGETED_DEVICE_FAMILY = "1,2"; 641 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyTravelHelper.app/MyTravelHelper"; 642 | }; 643 | name = Debug; 644 | }; 645 | 753F0865223618AE00175107 /* Release */ = { 646 | isa = XCBuildConfiguration; 647 | baseConfigurationReference = 849A0A29A57A433EA5AB15FB /* Pods-MyTravelHelperTests.release.xcconfig */; 648 | buildSettings = { 649 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 650 | BUNDLE_LOADER = "$(TEST_HOST)"; 651 | CODE_SIGN_STYLE = Automatic; 652 | INFOPLIST_FILE = MyTravelHelperTests/Info.plist; 653 | LD_RUNPATH_SEARCH_PATHS = ( 654 | "$(inherited)", 655 | "@executable_path/Frameworks", 656 | "@loader_path/Frameworks", 657 | ); 658 | PRODUCT_BUNDLE_IDENTIFIER = com.example.com.MyTravelHelperTests; 659 | PRODUCT_NAME = "$(TARGET_NAME)"; 660 | SWIFT_VERSION = 5.0; 661 | TARGETED_DEVICE_FAMILY = "1,2"; 662 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyTravelHelper.app/MyTravelHelper"; 663 | }; 664 | name = Release; 665 | }; 666 | /* End XCBuildConfiguration section */ 667 | 668 | /* Begin XCConfigurationList section */ 669 | 753F083E223618AB00175107 /* Build configuration list for PBXProject "MyTravelHelper" */ = { 670 | isa = XCConfigurationList; 671 | buildConfigurations = ( 672 | 753F085E223618AE00175107 /* Debug */, 673 | 753F085F223618AE00175107 /* Release */, 674 | ); 675 | defaultConfigurationIsVisible = 0; 676 | defaultConfigurationName = Release; 677 | }; 678 | 753F0860223618AE00175107 /* Build configuration list for PBXNativeTarget "MyTravelHelper" */ = { 679 | isa = XCConfigurationList; 680 | buildConfigurations = ( 681 | 753F0861223618AE00175107 /* Debug */, 682 | 753F0862223618AE00175107 /* Release */, 683 | ); 684 | defaultConfigurationIsVisible = 0; 685 | defaultConfigurationName = Release; 686 | }; 687 | 753F0863223618AE00175107 /* Build configuration list for PBXNativeTarget "MyTravelHelperTests" */ = { 688 | isa = XCConfigurationList; 689 | buildConfigurations = ( 690 | 753F0864223618AE00175107 /* Debug */, 691 | 753F0865223618AE00175107 /* Release */, 692 | ); 693 | defaultConfigurationIsVisible = 0; 694 | defaultConfigurationName = Release; 695 | }; 696 | /* End XCConfigurationList section */ 697 | }; 698 | rootObject = 753F083B223618AB00175107 /* Project object */; 699 | } 700 | -------------------------------------------------------------------------------- /MyTravelHelperTests/MockResponses.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockResponses.swift 3 | // MyTravelHelperTests 4 | // 5 | // Created by Navroz on 10/01/21. 6 | // Copyright © 2021 Sample. All rights reserved. 7 | // 8 | 9 | import XMLParsing 10 | @testable import MyTravelHelper 11 | //TODO: Construct modals with init 12 | struct Responses { 13 | //MARK: TrainMovements 14 | static let trainMovements = "\r\n\r\n \r\n A867 \r\n 10 Jan 2021\r\n BALNA\r\n Ballina\r\n 1\r\n O\r\n Ballina\r\n Manulla Junction\r\n 00:00:00\r\n 15:35:00\r\n 00:00:00\r\n 15:35:00\r\n \r\n \r\n \r\n \r\n C\r\n \r\n \r\n A867 \r\n 10 Jan 2021\r\n FXFRD\r\n Foxford\r\n 2\r\n S\r\n Ballina\r\n Manulla Junction\r\n 15:46:30\r\n 15:47:00\r\n 15:46:30\r\n 15:47:00\r\n \r\n \r\n \r\n \r\n N\r\n \r\n \r\n A867 \r\n 10 Jan 2021\r\n MNLAJ\r\n Manulla Junction\r\n 3\r\n D\r\n Ballina\r\n Manulla Junction\r\n 16:03:00\r\n 00:00:00\r\n 16:03:00\r\n 00:00:00\r\n \r\n \r\n \r\n \r\n -\r\n \r\n" 15 | static func TrainMovements() -> TrainMovementsData { 16 | guard let object = try? XMLDecoder().decode(TrainMovementsData.self, from: Data(Responses.trainMovements.utf8)) 17 | else { 18 | fatalError("Unable to decode static responses") 19 | } 20 | 21 | return object 22 | } 23 | 24 | 25 | //MARK: TrainList 26 | static let trainList = "\r\n\r\n \r\n 2021-01-10T14:07:28.81\r\n A864 \r\n Ballina\r\n BALNA\r\n 14:07:28\r\n 10 Jan 2021\r\n Manulla Junction\r\n Ballina\r\n 13:43\r\n 14:11\r\n No Information\r\n \r\n 4\r\n 0\r\n 14:11\r\n 00:00\r\n 14:11\r\n 00:00\r\n To Ballina\r\n DMU\r\n D\r\n \r\n \r\n 2021-01-10T14:07:28.81\r\n A867 \r\n Ballina\r\n BALNA\r\n 14:07:28\r\n 10 Jan 2021\r\n Ballina\r\n Manulla Junction\r\n 15:35\r\n 16:03\r\n No Information\r\n \r\n 88\r\n 0\r\n 00:00\r\n 15:35\r\n 00:00\r\n 15:35\r\n To Manulla Junction\r\n DMU\r\n O\r\n \r\n" 27 | 28 | static func TrainList() -> [StationTrain] { 29 | guard var object = try? XMLDecoder().decode(StationData.self, from: Data(Responses.trainList.utf8)) 30 | else { 31 | fatalError("Unable to decode static responses") 32 | } 33 | object.trainsList[0].destinationDetails = self.TrainMovements().trainMovements[0] 34 | return object.trainsList 35 | } 36 | 37 | //MARK: ALL Station 38 | static let allStation = 39 | "\r\n\r\n \r\n Belfast\r\n \r\n 54.6123\r\n -5.91744\r\n BFSTC\r\n 228\r\n \r\n \r\n Lisburn\r\n \r\n 54.514\r\n -6.04327\r\n LBURN\r\n 238\r\n \r\n \r\n Lurgan\r\n \r\n 54.4672\r\n -6.33547\r\n LURGN\r\n 241\r\n \r\n \r\n Portadown\r\n \r\n 54.4295\r\n -6.43868\r\n PDOWN\r\n 242\r\n \r\n \r\n Sligo\r\n \r\n 54.2723\r\n -8.48249\r\n SLIGO\r\n 180\r\n \r\n \r\n Newry\r\n \r\n 54.1911\r\n -6.36225\r\n NEWRY\r\n 260\r\n \r\n \r\n Collooney\r\n \r\n 54.1871\r\n -8.49453\r\n COLNY\r\n 177\r\n \r\n \r\n Ballina\r\n \r\n 54.1085\r\n -9.16146\r\n BALNA\r\n 167\r\n \r\n \r\n Ballymote\r\n \r\n 54.0887\r\n -8.52088\r\n BMOTE\r\n 176\r\n \r\n \r\n Dundalk\r\n \r\n 54.0007\r\n -6.41291\r\n DDALK\r\n 123\r\n \r\n \r\n Foxford\r\n \r\n 53.983\r\n -9.1364\r\n FXFRD\r\n 193\r\n \r\n \r\n Boyle\r\n \r\n 53.9676\r\n -8.30438\r\n BOYLE\r\n 175\r\n \r\n \r\n Carrick on Shannon\r\n \r\n 53.9383\r\n -8.10657\r\n CKOSH\r\n 174\r\n \r\n \r\n Dromod\r\n \r\n 53.8591\r\n -7.9164\r\n DRMOD\r\n 173\r\n \r\n \r\n Castlebar\r\n \r\n 53.8471\r\n -9.2873\r\n CLBAR\r\n 168\r\n \r\n \r\n Manulla Junction\r\n \r\n 53.828\r\n -9.19296\r\n MNLAJ\r\n 194\r\n \r\n \r\n Westport\r\n \r\n 53.7955\r\n -9.50885\r\n WPORT\r\n 169\r\n \r\n \r\n Ballyhaunis\r\n \r\n 53.7616\r\n -8.7584\r\n BYHNS\r\n 165\r\n \r\n \r\n Castlerea\r\n \r\n 53.7612\r\n -8.48448\r\n CSREA\r\n 164\r\n \r\n \r\n Longford\r\n \r\n 53.7243\r\n -7.79574\r\n LFORD\r\n 172\r\n \r\n \r\n Claremorris\r\n \r\n 53.7204\r\n -9.00222\r\n CLMRS\r\n 166\r\n \r\n \r\n Drogheda\r\n \r\n 53.712\r\n -6.33538\r\n DGHDA\r\n 120\r\n \r\n \r\n Edgeworthstown\r\n \r\n 53.6888\r\n -7.60299\r\n ETOWN\r\n 171\r\n \r\n \r\n Laytown\r\n \r\n 53.6794\r\n -6.24253\r\n LTOWN\r\n 119\r\n \r\n \r\n Gormanston\r\n \r\n 53.638\r\n -6.21705\r\n GSTON\r\n 117\r\n \r\n \r\n Roscommon\r\n \r\n 53.6243\r\n -8.19631\r\n RSCMN\r\n 163\r\n \r\n \r\n Balbriggan\r\n \r\n 53.6118\r\n -6.18226\r\n BBRGN\r\n 116\r\n \r\n \r\n Skerries\r\n \r\n 53.5741\r\n -6.11933\r\n SKRES\r\n 115\r\n \r\n \r\n Mullingar\r\n \r\n 53.523\r\n -7.34608\r\n MLGAR\r\n 153\r\n \r\n \r\n Rush and Lusk\r\n \r\n 53.5201\r\n -6.1439\r\n RLUSK\r\n 114\r\n \r\n \r\n Donabate\r\n \r\n 53.4855\r\n -6.15134\r\n DBATE\r\n 113\r\n \r\n \r\n Malahide\r\n \r\n 53.4509\r\n -6.15649\r\n MHIDE\r\n 112\r\n \r\n \r\n M3 Parkway\r\n \r\n 53.4349\r\n -6.46898\r\n M3WAY\r\n 86\r\n \r\n \r\n Athlone\r\n \r\n 53.4273\r\n -7.93683\r\n ATLNE\r\n 156\r\n \r\n \r\n Dunboyne\r\n \r\n 53.4175\r\n -6.46483\r\n DBYNE\r\n 85\r\n \r\n \r\n Portmarnock\r\n \r\n 53.4169\r\n -6.1512\r\n PMNCK\r\n 111\r\n \r\n \r\n Enfield\r\n \r\n 53.4157\r\n -6.83395\r\n ENFLD\r\n 83\r\n \r\n \r\n Kilcock\r\n \r\n 53.4043\r\n -6.67892\r\n KCOCK\r\n 90\r\n \r\n \r\n Clongriffin\r\n \r\n 53.4032\r\n -6.14839\r\n GRGRD\r\n 187\r\n \r\n \r\n Sutton\r\n \r\n 53.392\r\n -6.11448\r\n SUTTN\r\n 107\r\n \r\n \r\n Bayside\r\n \r\n 53.3917\r\n -6.13678\r\n BYSDE\r\n 106\r\n \r\n \r\n Howth Junction\r\n Donaghmede ( Howth Junction )\r\n 53.3909\r\n -6.15672\r\n HWTHJ\r\n 105\r\n \r\n \r\n Howth\r\n \r\n 53.3891\r\n -6.07401\r\n HOWTH\r\n 108\r\n \r\n \r\n Kilbarrack\r\n \r\n 53.387\r\n -6.16163\r\n KBRCK\r\n 104\r\n \r\n \r\n Hansfield\r\n \r\n 53.3853\r\n -6.44205\r\n HAFLD\r\n 87\r\n \r\n \r\n Clonsilla\r\n \r\n 53.3831\r\n -6.4242\r\n CLSLA\r\n 94\r\n \r\n \r\n Castleknock\r\n \r\n 53.3816\r\n -6.37149\r\n CNOCK\r\n 96\r\n \r\n \r\n Raheny\r\n \r\n 53.3815\r\n -6.17699\r\n RAHNY\r\n 103\r\n \r\n \r\n Harmonstown\r\n \r\n 53.3786\r\n -6.19131\r\n HTOWN\r\n 102\r\n \r\n \r\n Maynooth\r\n \r\n 53.378\r\n -6.58993\r\n MYNTH\r\n 91\r\n \r\n \r\n Navan Road Parkway\r\n Phoenix Park\r\n 53.3777\r\n -6.34591\r\n PHNPK\r\n 89\r\n \r\n \r\n Coolmine\r\n \r\n 53.3776\r\n -6.39072\r\n CMINE\r\n 95\r\n \r\n \r\n Ashtown\r\n \r\n 53.3755\r\n -6.33135\r\n ASHTN\r\n 97\r\n \r\n \r\n Leixlip (Confey)\r\n \r\n 53.3743\r\n -6.48624\r\n LXCON\r\n 93\r\n \r\n \r\n Killester\r\n \r\n 53.373\r\n -6.20442\r\n KLSTR\r\n 101\r\n \r\n \r\n Broombridge\r\n \r\n 53.3725\r\n -6.29869\r\n BBRDG\r\n 98\r\n \r\n \r\n Leixlip (Louisa Bridge)\r\n \r\n 53.3704\r\n -6.50598\r\n LXLSA\r\n 92\r\n \r\n \r\n Drumcondra\r\n \r\n 53.3632\r\n -6.25908\r\n DCDRA\r\n 99\r\n \r\n \r\n Clontarf Road\r\n \r\n 53.3629\r\n -6.22753\r\n CTARF\r\n 109\r\n \r\n \r\n Dublin Connolly\r\n Connolly\r\n 53.3531\r\n -6.24591\r\n CNLLY\r\n 100\r\n \r\n \r\n Docklands\r\n \r\n 53.3509\r\n -6.23929\r\n DCKLS\r\n 84\r\n \r\n \r\n Tara Street\r\n \r\n 53.347\r\n -6.25425\r\n TARA \r\n 124\r\n \r\n \r\n Dublin Heuston\r\n Heuston\r\n 53.3464\r\n -6.29461\r\n HSTON\r\n 1\r\n \r\n \r\n Dublin Pearse\r\n Pearse\r\n 53.3433\r\n -6.24829\r\n PERSE\r\n 150\r\n \r\n \r\n Woodlawn\r\n \r\n 53.3432\r\n -8.47231\r\n WLAWN\r\n 158\r\n \r\n \r\n Grand Canal Dock\r\n \r\n 53.3397\r\n -6.23773\r\n GCDK \r\n 110\r\n \r\n \r\n Clara\r\n \r\n 53.3395\r\n -7.61596\r\n CLARA\r\n 73\r\n \r\n \r\n Ballinasloe\r\n \r\n 53.3363\r\n -8.24081\r\n BSLOE\r\n 157\r\n \r\n \r\n Adamstown\r\n \r\n 53.3353\r\n -7\r\n ADAMS\r\n 1075\r\n \r\n \r\n Adamstown\r\n \r\n 53.3353\r\n -6.45233\r\n ADAMF\r\n 975\r\n \r\n \r\n Adamstown\r\n \r\n 53.3353\r\n -6.45233\r\n ADMTN\r\n 75\r\n \r\n \r\n Lansdowne Road\r\n \r\n 53.3347\r\n -6.22979\r\n LDWNE\r\n 125\r\n \r\n \r\n Park West and Cherry Orchard\r\n Park West (Cherry Orchard )\r\n 53.334\r\n -6.37868\r\n CHORC\r\n 76\r\n \r\n \r\n Park West and Cherry Orchard\r\n Park West (Cherry Orchard )\r\n 53.334\r\n -6.37868\r\n PWESF\r\n 976\r\n \r\n \r\n PARK WEST\r\n Park West (Cherry Orchard )\r\n 53.334\r\n -6.37868\r\n PWESS\r\n 1076\r\n \r\n \r\n Clondalkin\r\n Fonthill ( Clondalkin )\r\n 53.3334\r\n -6.40628\r\n CLONS\r\n 1077\r\n \r\n \r\n Clondalkin\r\n Fonthill ( Clondalkin )\r\n 53.3334\r\n -6.40628\r\n CLONF\r\n 977\r\n \r\n \r\n Clondalkin\r\n Fonthill ( Clondalkin )\r\n 53.3334\r\n -6.40628\r\n CLDKN\r\n 77\r\n \r\n \r\n Sandymount\r\n \r\n 53.3281\r\n -6.22116\r\n SMONT\r\n 188\r\n \r\n \r\n Hazelhatch\r\n Celbridge (Hazelhatch ) \r\n 53.3223\r\n -6.52356\r\n HZLCH\r\n 78\r\n \r\n \r\n Hazelhatch\r\n Celbridge (Hazelhatch ) \r\n 53.3223\r\n -6.52356\r\n HAZEF\r\n 978\r\n \r\n \r\n Hazelhatch\r\n Celbridge (Hazelhatch ) \r\n 53.3223\r\n -6.52356\r\n HAZES\r\n 1078\r\n \r\n \r\n Attymon\r\n \r\n 53.3212\r\n -8.60608\r\n ATMON\r\n 159\r\n \r\n \r\n Sydney Parade\r\n \r\n 53.3206\r\n -6.21112\r\n SIDNY\r\n 126\r\n \r\n \r\n Booterstown\r\n \r\n 53.3099\r\n -6.19498\r\n BTSTN\r\n 127\r\n \r\n \r\n Blackrock\r\n \r\n 53.3027\r\n -6.17833\r\n BROCK\r\n 128\r\n \r\n \r\n Athenry\r\n \r\n 53.3015\r\n -8.74855\r\n ATHRY\r\n 162\r\n \r\n \r\n Seapoint\r\n \r\n 53.2991\r\n -6.16512\r\n SEAPT\r\n 129\r\n \r\n \r\n Salthill and Monkstown\r\n Monkstown ( Salthill )\r\n 53.2954\r\n -6.15206\r\n SHILL\r\n 130\r\n \r\n \r\n Dun Laoghaire\r\n \r\n 53.2951\r\n -6.13498\r\n DLERY\r\n 131\r\n \r\n \r\n Sandycove\r\n Glasthule (Sandycove ) \r\n 53.2878\r\n -6.12712\r\n SCOVE\r\n 132\r\n \r\n \r\n Glenageary\r\n \r\n 53.2812\r\n -6.12289\r\n GLGRY\r\n 133\r\n \r\n \r\n Dalkey\r\n \r\n 53.2756\r\n -6.10333\r\n DLKEY\r\n 134\r\n \r\n \r\n Oranmore\r\n \r\n 53.2751\r\n -8.94792\r\n ORNMR\r\n 161\r\n \r\n \r\n Galway\r\n \r\n 53.2736\r\n -9.04696\r\n GALWY\r\n 170\r\n \r\n \r\n Tullamore\r\n \r\n 53.2704\r\n -7.49985\r\n TMORE\r\n 72\r\n \r\n \r\n Killiney\r\n \r\n 53.2557\r\n -6.11317\r\n KILNY\r\n 135\r\n \r\n \r\n Sallins\r\n \r\n 53.2469\r\n -6.66386\r\n SALNS\r\n 79\r\n \r\n \r\n Shankill\r\n \r\n 53.2364\r\n -6.11691\r\n SKILL\r\n 136\r\n \r\n \r\n Craughwell\r\n \r\n 53.2252\r\n -8.7359\r\n CRGHW\r\n 184\r\n \r\n \r\n Woodbrook\r\n \r\n 53.22\r\n -6.1101\r\n WBROK\r\n 801\r\n \r\n \r\n Bray\r\n \r\n 53.2043\r\n -6.10046\r\n BRAY \r\n 140\r\n \r\n \r\n Newbridge\r\n \r\n 53.1855\r\n -6.80807\r\n NBRGE\r\n 4\r\n \r\n \r\n Curragh\r\n \r\n 53.1725\r\n -6.86245\r\n CURAH\r\n 5\r\n \r\n \r\n Kildare\r\n \r\n 53.163\r\n -6.90802\r\n KDARE\r\n 6\r\n \r\n \r\n Ardrahan\r\n \r\n 53.1572\r\n -8.81483\r\n ARHAN\r\n 183\r\n \r\n \r\n Portarlington\r\n \r\n 53.146\r\n -7.18055\r\n PTRTN\r\n 8\r\n \r\n \r\n Monasterevin\r\n \r\n 53.1454\r\n -7.06361\r\n MONVN\r\n 7\r\n \r\n \r\n Greystones\r\n \r\n 53.1442\r\n -6.06085\r\n GSTNS\r\n 141\r\n \r\n \r\n Kilcoole\r\n \r\n 53.107\r\n -6.04112\r\n KCOOL\r\n 139\r\n \r\n \r\n Gort\r\n \r\n 53.0653\r\n -8.81595\r\n GORT\r\n 182\r\n \r\n \r\n Portlaoise\r\n \r\n 53.0371\r\n -7.30086\r\n PTLSE\r\n 9\r\n \r\n \r\n Athy\r\n \r\n 52.992\r\n -6.9762\r\n ATHY \r\n 45\r\n \r\n \r\n Wicklow\r\n \r\n 52.9882\r\n -6.05338\r\n WLOW \r\n 142\r\n \r\n \r\n Roscrea\r\n \r\n 52.9607\r\n -7.7941\r\n RCREA\r\n 31\r\n \r\n \r\n Cloughjordan\r\n \r\n 52.9363\r\n -8.0246\r\n CJRDN\r\n 32\r\n \r\n \r\n Rathdrum\r\n \r\n 52.9295\r\n -6.22641\r\n RDRUM\r\n 143\r\n \r\n \r\n Ballybrophy\r\n \r\n 52.8999\r\n -7.60259\r\n BBRHY\r\n 11\r\n \r\n \r\n Nenagh\r\n \r\n 52.8605\r\n -8.19471\r\n NNAGH\r\n 33\r\n \r\n \r\n Carlow\r\n \r\n 52.8407\r\n -6.92217\r\n CRLOW\r\n 46\r\n \r\n \r\n Ennis\r\n \r\n 52.8386\r\n -8.97491\r\n ENNIS\r\n 181\r\n \r\n \r\n Arklow\r\n \r\n 52.7932\r\n -6.15994\r\n ARKLW\r\n 144\r\n \r\n \r\n Templemore\r\n \r\n 52.7878\r\n -7.82293\r\n TPMOR\r\n 12\r\n \r\n \r\n Birdhill\r\n \r\n 52.7656\r\n -8.44247\r\n BHILL\r\n 34\r\n \r\n \r\n Sixmilebridge\r\n \r\n 52.7376\r\n -8.78427\r\n SXMBR\r\n 185\r\n \r\n \r\n Castleconnell\r\n \r\n 52.7128\r\n -8.49794\r\n CCONL\r\n 35\r\n \r\n \r\n Muine Bheag\r\n Bagenalstown\r\n 52.699\r\n -6.95213\r\n MNEBG\r\n 47\r\n \r\n \r\n Thurles\r\n \r\n 52.6766\r\n -7.82189\r\n THRLS\r\n 13\r\n \r\n \r\n Gorey\r\n \r\n 52.6712\r\n -6.29195\r\n GOREY\r\n 145\r\n \r\n \r\n Limerick\r\n \r\n 52.6587\r\n -8.62397\r\n LMRCK\r\n 40\r\n \r\n \r\n Kilkenny\r\n \r\n 52.655\r\n -7.24498\r\n KKNNY\r\n 48\r\n \r\n \r\n Thomastown\r\n \r\n 52.523\r\n -7.14891\r\n THTWN\r\n 49\r\n \r\n \r\n Enniscorthy\r\n \r\n 52.5046\r\n -6.56627\r\n ECRTY\r\n 147\r\n \r\n \r\n Limerick Junction\r\n \r\n 52.5009\r\n -8.20003\r\n LMRKJ\r\n 16\r\n \r\n \r\n Tipperary\r\n \r\n 52.4701\r\n -8.1625\r\n TIPRY\r\n 41\r\n \r\n \r\n Cahir\r\n \r\n 52.3777\r\n -7.92181\r\n CAHIR\r\n 42\r\n \r\n \r\n Clonmel\r\n \r\n 52.3611\r\n -7.69936\r\n CLMEL\r\n 43\r\n \r\n \r\n Carrick on Suir\r\n \r\n 52.3487\r\n -7.40354\r\n CKOSR\r\n 44\r\n \r\n \r\n Charleville\r\n \r\n 52.3468\r\n -8.65362\r\n CVILL\r\n 19\r\n \r\n \r\n Wexford\r\n \r\n 52.3434\r\n -6.4636\r\n WXFRD\r\n 148\r\n \r\n \r\n Campile\r\n \r\n 52.2855\r\n -6.93896\r\n CPILE\r\n 52\r\n \r\n \r\n Ballycullane\r\n \r\n 52.2834\r\n -6.83958\r\n BCLAN\r\n 53\r\n \r\n \r\n Rosslare Strand\r\n \r\n 52.2726\r\n -6.39254\r\n RLSTD\r\n 58\r\n \r\n \r\n Tralee\r\n \r\n 52.271\r\n -9.69846\r\n TRLEE\r\n 28\r\n \r\n \r\n Wellingtonbridge\r\n \r\n 52.2678\r\n -6.75392\r\n WBDGE\r\n 54\r\n \r\n \r\n Waterford\r\n \r\n 52.2667\r\n -7.1183\r\n WFORD\r\n 50\r\n \r\n \r\n Rosslare Europort\r\n Rosslare Harbour\r\n 52.2531\r\n -6.33493\r\n RLEPT\r\n 60\r\n \r\n \r\n Bridgetown\r\n \r\n 52.2312\r\n -6.54918\r\n BRGTN\r\n 56\r\n \r\n \r\n Farranfore\r\n \r\n 52.1733\r\n -9.55278\r\n FFORE\r\n 27\r\n \r\n \r\n Mallow\r\n \r\n 52.1396\r\n -8.65521\r\n MLLOW\r\n 21\r\n \r\n \r\n Banteer\r\n \r\n 52.1287\r\n -8.89793\r\n BTEER\r\n 23\r\n \r\n \r\n Rathmore\r\n \r\n 52.0854\r\n -9.21756\r\n RMORE\r\n 25\r\n \r\n \r\n Millstreet\r\n \r\n 52.0776\r\n -9.06973\r\n MLSRT\r\n 24\r\n \r\n \r\n Killarney\r\n \r\n 52.0595\r\n -9.50198\r\n KLRNY\r\n 26\r\n \r\n \r\n Midleton\r\n \r\n 51.9212\r\n -8.17579\r\n MDLTN\r\n 68\r\n \r\n \r\n Carrigtwohill\r\n \r\n 51.9163\r\n -8.26323\r\n CGTWL\r\n 67\r\n \r\n \r\n Glounthaune\r\n \r\n 51.9112\r\n -8.3254\r\n GHANE\r\n 380\r\n \r\n \r\n LittleIsland\r\n Little Island\r\n 51.9078\r\n -8.35466\r\n LSLND\r\n 61\r\n \r\n \r\n Cork\r\n \r\n 51.9018\r\n -8.4582\r\n CORK \r\n 30\r\n \r\n \r\n Fota\r\n \r\n 51.896\r\n -8.3183\r\n FOTA \r\n 63\r\n \r\n \r\n Carrigaloe\r\n \r\n 51.8688\r\n -8.32417\r\n CGLOE\r\n 64\r\n \r\n \r\n Rushbrooke\r\n \r\n 51.8496\r\n -8.32252\r\n RBROK\r\n 65\r\n \r\n \r\n Cobh\r\n \r\n 51.8491\r\n -8.29956\r\n COBH \r\n 66\r\n \r\n \r\n CITY JUNCTION\r\n Dublin Belfast\r\n 0\r\n 0\r\n CITYJ\r\n 1516\r\n \r\n \r\n CENTRAL JUNCTION\r\n Dublin Belfast\r\n 0\r\n 0\r\n CENTJ\r\n 1517\r\n \r\n \r\n DUNMURRAY\r\n Dublin Belfast\r\n 0\r\n 0\r\n DUNMR\r\n 1518\r\n \r\n \r\n MOIRA\r\n Dublin Belfast\r\n 0\r\n 0\r\n MOIRA\r\n 1519\r\n \r\n" 40 | 41 | static func AllStation() -> Stations { 42 | guard let object = try? XMLDecoder().decode(Stations.self, from: Data(Responses.allStation.utf8)) 43 | else { 44 | fatalError("Unable to decode static responses") 45 | } 46 | return object 47 | } 48 | } 49 | --------------------------------------------------------------------------------