From ba22ed4627bd5787f040c9205d0331e1fcae521c Mon Sep 17 00:00:00 2001 From: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> Date: Wed, 6 Nov 2024 12:13:18 -0500 Subject: [PATCH] Update demo app to latest APIs (#74) * fixed ALS-1665 added collapse option on directions bottomsheet view * removed amplify and map styles * implemented oauth2 sign in * Implementation of sign in and removal of auth sdk * added iot device v2 sdk * added iot device package * Implemented Geodence batch evaluate and refresh mechanism * fixed navigation and direction issues * Update AWSLoginService.swift * Update AWSLoginService.swift * Implemented tracking geofence notification IoT * fix IoT connection * refactoring of IoT mqtt client * fixed unit test cases * Updated E2E test cases * Updated E2E test cases * Update Package.resolved * updating macos github runner * Update test-ios-e2e.yml * Update test-ios-e2e.yml * Update test-ios-e2e.yml * Update test-ios-e2e.yml * Update test-ios-e2e.yml * Update test-ios-e2e.yml * Update test-ios-e2e.yml * Update test-ios-e2e.yml * Update test-ios-e2e.yml * update E2E test cases and workflow * Update Gemfile.lock * optimized test cases * Update MapUITests.swift * Update test-ios-e2e.yml * Implemented disconnect and logout on settings screen * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Bug Fixes * Update SearchVC.swift * Update SearchViewModel.swift * Implemented refresh credentials * updated workflows * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update project.pbxproj * Update test-ios.yml * Update AWSSignatureDelegate.swift * Revert "Update AWSSignatureDelegate.swift" This reverts commit 5ed23529410f6fc1429e0117c7c2b1d02b61ebd6. * renamed AWSSignatureV4Delegate * Update test-ios.yml * Update DateExtensionTests.swift * Update test-ios-e2e.yml * Restricted xcovergage to target projects only * Update test-ios-e2e.yml * Update UITestGeofenceScreen.swift * Update UITestGeofenceScreen.swift * refactoring and removed warnings * Update LocationServices.xctestplan * Removed warnings and reverted back to mac 14 runner * Update CLLocationCoordinate2D+Extension.swift * Update UITestSettingsScreen.swift * updated simulator * Update Fastfile * updated checkout version * Revert "Removed warnings and reverted back to mac 14 runner" This reverts commit c92b75b3a18d9160417656b36667cd53b9f85572. * Update LocationServices.xctestplan * refactoring * fixes * Update LocationServices.xctestplan * fix * add tableview identifier * Update UITestSettingsScreen.swift * increase simulator versions * added a dummy test case * test case fix * Update test-ios-e2e.yml * Update SettingsUITests.swift * Update test-ios-e2e.yml * updated test case * fix * fix * Update test-ios-e2e.yml * Update Fastfile * Update Fastfile * Update Fastfile * Update SettingsUITests.swift * Update SettingsUITests.swift * Update SettingsUITests.swift * Update SettingsVC.swift * Update SettingsVC.swift * Update SettingsVC.swift * Update SettingsVC.swift * Update SettingsVC.swift * fix * Update SettingsVC.swift * Update SettingsVC.swift * Refactor settings button for disconnect and logout * re enabled test cases * Update Fastfile * fixed test cases * Removed unecessary code * downgraded macos runner tier * Update Fastfile * Update Fastfile * Update UITestTrackingScreen.swift * Update LocationServices.xctestplan * Update TrackingUITests.swift * Re-enabled test cases * Update Fastfile * Update LocationServices.xctestplan * Update LocationServices.xctestplan * Update test-ios-e2e.yml * Update LocationServices.xctestplan * ALS-1805 fixed PR feedback * Fixed PR feedback * Create .gitignore * Implemented Map new Style and new API SDK for places, routes and other bug fixes * Update ConfigTestTemplate.xcconfig * Implemented Political View E2E test case * Removed pak political view * Refactoring of auth helper files and removed unused codes * Fixed ipad settings map style UI * Update MapStyleVC.swift * Update SettingsVC.swift * removed blurstatusbar * Update ExploreVC.swift * Update LocationSearchService.swift * Fixed PR Feedback * Renamed destionation typo to destination * reverted the bundle identifier * disable bottomsheet grab on tracking and geofences for mapstyles * removed commented code * Implemented multi route leg details * Fixed post login api Key issue and dismiss login screen after login * Update AWSLoginServiceMock.swift * Fixed coordinates search by replacing nearby with reverse geocode * Implemented lat, long and long, lat in reverse geocode * Update SearchCell.swift * Implemented new version of Swift sdks such as places & routes * refactoring * removed local AWSGeoservices package * Optimization and Show loader * added loader on sign in button --------- Co-authored-by: Zeeshan Sheikh <64203935+zeeshanmakeen@users.noreply.github.com> Co-authored-by: Zeeshan Sheikh Co-authored-by: wadhawh <130486914+wadhawh@users.noreply.github.com> --- LocationServices/.xcovignore | 16 +- LocationServices/ConfigTemplate.xcconfig | 1 + LocationServices/ConfigTestTemplate.xcconfig | 1 + .../project.pbxproj | 229 +++---- .../xcshareddata/swiftpm/Package.resolved | 25 +- .../mapStyleTintColor.colorset/Contents.json | 38 ++ .../Contents.json | 38 ++ .../Constants/AppColorsConstants.swift | 2 + .../Constants/AppConstants.swift | 79 ++- .../Constants/ViewsIdentifiers.swift | 8 +- .../ExploreCoordinator.swift | 26 +- .../GeofenceCoordinator.swift | 4 +- .../SettingsCoordinator.swift | 3 + .../SplitViewExploreMapCoordinator.swift | 20 +- .../SplitViewSettingsCoordinator.swift | 3 +- .../TabBarCoordinator/TabBarCoordinator.swift | 6 +- .../TrackingCoordinator.swift | 8 +- .../Extensions/Notificaton+Extension.swift | 1 + ....swift => RouteTravelMode+Extension.swift} | 8 +- .../Extensions/String+Extensions.swift | 2 +- .../UIViewController+Extension.swift | 24 +- .../Helpers/AWSSignerV4.swift | 557 ------------------ ...AmazonLocationApiCredentialsProvider.swift | 11 - .../Helpers/AmazonLocationClient.swift | 82 --- ...onLocationCognitoCredentialsProvider.swift | 67 --- .../Helpers/ApiAuthHelper.swift | 22 - .../LocationServices/Helpers/AuthHelper.swift | 59 -- .../AuthHelpers/AmazonLocationClient.swift | 40 ++ .../Helpers/AuthHelpers/ApiAuthHelper.swift | 43 ++ .../AuthHelpers/CognitoAuthHelper.swift | 40 ++ .../Helpers/CognitoAuthHelper.swift | 34 -- .../Helpers/CognitoCredentialsProvider.swift | 49 -- .../Helpers/GeneralHelper.swift | 59 +- .../Helpers/KeyChainHelper.swift | 7 + .../Helpers/LocationCredentialsProvider.swift | 75 --- .../Helpers/SettingsDefaultValueHelper.swift | 4 + .../Helpers/UserDefaultsHelper.swift | 2 + .../LocationServices/Models/MapModel.swift | 11 +- .../Services/LocationServiceable.swift | 30 +- .../Services/RoutingServiceable.swift | 26 +- .../Services/TrackingServiceable.swift | 2 +- .../Controller/AttributionVC.swift | 19 +- .../Explore/Contracts/ExploreContracts.swift | 6 +- .../Scenes/Explore/Controller/ExploreVC.swift | 39 +- .../Controller/DirectionVC+Tableview.swift | 13 +- .../Direction/Controller/DirectionVC.swift | 203 ++++--- .../Presentation/DirectionPresentation.swift | 221 ++++++- .../ViewModel/DirectionViewModel.swift | 66 ++- .../Views/Common Views/RouteTypeView.swift | 8 +- .../Direction/Views/DirectionView.swift | 26 +- .../Contracts/ExploreMapStyleContract.swift | 1 - .../ExploreMapStyleVC+TableView.swift | 5 +- .../Controller/ExploreMapStyleVC.swift | 37 +- .../ExploreMapStyleCellViewModel.swift | 11 +- .../View Model/ExploreMapStyleViewModel.swift | 29 +- .../Views/Cell/ExploreMapStyleCell.swift | 59 +- .../Views/Cell/PoliticalViewCell.swift | 91 +++ .../Views/ColorSegmentControl.swift | 62 ++ .../Views/ExploreMapStyleHeaderView.swift | 23 +- .../Views/PoliticalView.swift | 126 ++++ .../Views/PoliticalViewController.swift | 216 +++++++ .../Builder/NavigationBuilder.swift | 4 +- .../Navigation/Controller/NavigationVC.swift | 20 +- .../Presentation/NavigationPresentation.swift | 3 +- .../View/Cell/NavigationVCCell.swift | 23 +- .../ViewModel/NavigationViewModel.swift | 51 +- .../POICard/Controller/POICardVC.swift | 4 +- .../POICard/ViewModel/POICardViewModel.swift | 18 +- .../Presentation/SearchPresentation.swift | 160 ++--- .../Search/ViewModel/SearchViewModel.swift | 6 +- .../Search/Views/Cell/SearchCell.swift | 2 +- .../Explore/ViewModel/ExploreViewModel.swift | 8 +- .../Scenes/Explore/Views/ExploreView.swift | 166 +++--- .../Geofence/Controller/GeofenceVC.swift | 1 - .../ViewModel/AddGeofenceViewModel.swift | 6 +- .../Scenes/Geofence/Views/GeofenceView.swift | 3 +- .../Login/Contracts/LoginContracts.swift | 1 + .../Scenes/Login/Controller/LoginVC.swift | 42 +- .../Login/ViewModel/LoginViewModel.swift | 13 +- .../Settings/Controller/SettingsVC.swift | 1 - .../ViewModel/DataProviderViewModel.swift | 50 +- .../MapStyleVC+CollectionView.swift | 15 +- .../MapStyle/Controller/MapStyleVC.swift | 33 +- .../MapStyle/Models/MapStyleModel.swift | 1 - .../MapStyle/Models/MapStyleTypes+Image.swift | 22 +- .../MapStyle/Models/MapStyleTypes.swift | 78 +-- .../ViewModel/MapStyleViewModel.swift | 33 +- .../Views/MapStyleSectionHeaderView.swift | 7 - .../ViewModel/SettingsViewModel.swift | 8 +- .../Splash/ViewModel/SplashViewModel.swift | 1 + .../Tracking/Controller/TrackingVC.swift | 3 +- .../View/TrackHistoryCell.swift | 6 +- .../View/TrackHistoryHeaderView.swift | 6 +- .../ViewModel/TrackingHistoryViewModel.swift | 6 +- .../Scenes/Tracking/Views/TrackingViews.swift | 3 +- .../Services/AWSLoginService.swift | 5 +- .../Services/AWSSignatureV4Delegate.swift | 43 -- .../Services/GeofenceService.swift | 8 +- .../Services/LocationSearchService.swift | 63 +- .../Services/RoutingService.swift | 38 +- .../Services/TrackingService.swift | 6 +- .../Common/MapView/DefaultCommonMapView.swift | 28 +- .../AWSLocationTravelModeTests.swift | 10 +- .../DataProviderViewModelTests.swift | 52 -- .../DirectionViewModelTests.swift | 10 +- .../ExploreMapStyleViewModelTests.swift | 5 - .../ExploreViewModelTests.swift | 8 +- .../GeofenceViewModelTests.swift | 20 +- .../LocationManagerTests.swift | 20 +- .../LoginViewModelTests.swift | 4 +- .../MapStyleViewModelTests.swift | 12 +- .../Mocks/AWSLoginServiceMock.swift | 4 + .../Mocks/LocationAPIServiceMock.swift | 5 +- .../Mocks/RoutingAPIServiceMock.swift | 8 +- .../NavigationVCViewModelTests.swift | 116 ++-- .../POICardViewModelTests.swift | 25 +- .../UserDefaultsHelperTests.swift | 2 +- .../LocationServicesUITests/MapUITests.swift | 4 +- .../Models/UITestRouteType.swift | 6 +- .../NavigationUITests.swift | 43 +- .../Screens/UITestMapStyleScreen.swift | 36 +- .../Screens/UITestPoliticalViewScreen.swift | 50 ++ .../UITestSettingsDataProviderScreen.swift | 49 -- .../Screens/UITestSettingsScreen.swift | 20 +- .../SearchUITests.swift | 10 - .../SettingsUITests.swift | 29 +- 126 files changed, 2129 insertions(+), 2437 deletions(-) create mode 100644 LocationServices/LocationServices/Assets.xcassets/Colors/mapStyleTintColor.colorset/Contents.json create mode 100644 LocationServices/LocationServices/Assets.xcassets/Colors/politicalListViewBackgroundColor.colorset/Contents.json rename LocationServices/LocationServices/Extensions/{AWSLocationTravelMode+Extension.swift => RouteTravelMode+Extension.swift} (74%) delete mode 100644 LocationServices/LocationServices/Helpers/AWSSignerV4.swift delete mode 100644 LocationServices/LocationServices/Helpers/AmazonLocationApiCredentialsProvider.swift delete mode 100644 LocationServices/LocationServices/Helpers/AmazonLocationClient.swift delete mode 100644 LocationServices/LocationServices/Helpers/AmazonLocationCognitoCredentialsProvider.swift delete mode 100644 LocationServices/LocationServices/Helpers/ApiAuthHelper.swift delete mode 100644 LocationServices/LocationServices/Helpers/AuthHelper.swift create mode 100644 LocationServices/LocationServices/Helpers/AuthHelpers/AmazonLocationClient.swift create mode 100644 LocationServices/LocationServices/Helpers/AuthHelpers/ApiAuthHelper.swift create mode 100644 LocationServices/LocationServices/Helpers/AuthHelpers/CognitoAuthHelper.swift delete mode 100644 LocationServices/LocationServices/Helpers/CognitoAuthHelper.swift delete mode 100644 LocationServices/LocationServices/Helpers/CognitoCredentialsProvider.swift delete mode 100644 LocationServices/LocationServices/Helpers/LocationCredentialsProvider.swift create mode 100644 LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/Cell/PoliticalViewCell.swift create mode 100644 LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/ColorSegmentControl.swift create mode 100644 LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/PoliticalView.swift create mode 100644 LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/PoliticalViewController.swift delete mode 100644 LocationServices/LocationServices/Services/AWSSignatureV4Delegate.swift delete mode 100644 LocationServices/LocationServicesTests/DataProviderViewModelTests.swift create mode 100644 LocationServices/LocationServicesUITests/Screens/UITestPoliticalViewScreen.swift delete mode 100644 LocationServices/LocationServicesUITests/Screens/UITestSettingsDataProviderScreen.swift diff --git a/LocationServices/.xcovignore b/LocationServices/.xcovignore index a9f9b879..4799d2ce 100644 --- a/LocationServices/.xcovignore +++ b/LocationServices/.xcovignore @@ -91,7 +91,19 @@ - DataProviderViewModel.swift - NavigationViewModel.swift -# Commenting partial code coverage files due to some code is not able to covered in unit test cases. Need refactoring +# Commenting partial code coverage files due to some code is not able to covered in unit test cases. - GeofenceDashboardViewModel.swift - TrackingViewModel.swift -- ExploreViewModel.swift \ No newline at end of file +- ExploreViewModel.swift + +# Ignoring AuthHelper files as it will be accessed via Auth SDK when it is available +- AuthHelper.swift +- ApiAuthHelper.swift +- CognitoAuthHelper.swift +- LocationCredentialsProvider.swift +- CognitoCredentialsProvider.swift +- AmazonLocationCognitoCredentialsProvider.swift +- AmazonLocationApiCredentialsProvider.swift +- AmazonLocationClient.swift +- ApiKeyAuthScheme.swift +- KeyChainHelper.swift \ No newline at end of file diff --git a/LocationServices/ConfigTemplate.xcconfig b/LocationServices/ConfigTemplate.xcconfig index c6c36dab..503d052a 100644 --- a/LocationServices/ConfigTemplate.xcconfig +++ b/LocationServices/ConfigTemplate.xcconfig @@ -10,3 +10,4 @@ IDENTITY_POOL_ID = REGION:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX AWS_REGION = REGION +API_KEY = XXXX diff --git a/LocationServices/ConfigTestTemplate.xcconfig b/LocationServices/ConfigTestTemplate.xcconfig index 36ab74e3..32305024 100644 --- a/LocationServices/ConfigTestTemplate.xcconfig +++ b/LocationServices/ConfigTestTemplate.xcconfig @@ -10,6 +10,7 @@ IDENTITY_POOL_ID = REGION:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX AWS_REGION = REGION +API_KEY = XXXX TEST_IDENTITY_POOL_ID = REGION:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX TEST_USER_POOL_CLIENT_ID = USER_POOL_CLIENT_ID diff --git a/LocationServices/LocationServices.xcodeproj/project.pbxproj b/LocationServices/LocationServices.xcodeproj/project.pbxproj index 2b526572..d2b1424b 100644 --- a/LocationServices/LocationServices.xcodeproj/project.pbxproj +++ b/LocationServices/LocationServices.xcodeproj/project.pbxproj @@ -47,7 +47,6 @@ 8A47805929C9BDC800B098CA /* UITestScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A47805829C9BDC800B098CA /* UITestScreen.swift */; }; 8A4D5FB329D6F64A00A79D3C /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4D5FB229D6F64A00A79D3C /* SettingsUITests.swift */; }; 8A4D5FB529D6FA5000A79D3C /* UITestSettingsMapStyleScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4D5FB429D6FA5000A79D3C /* UITestSettingsMapStyleScreen.swift */; }; - 8A4D5FB729D719AE00A79D3C /* UITestSettingsDataProviderScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4D5FB629D719AE00A79D3C /* UITestSettingsDataProviderScreen.swift */; }; 8A4EE62E29D4B0EE00B699A9 /* NavigationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4EE62D29D4B0EE00B699A9 /* NavigationUITests.swift */; }; 8A4EE63029D4B45D00B699A9 /* UITestRouteOptionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4EE62F29D4B45D00B699A9 /* UITestRouteOptionsScreen.swift */; }; 8A50572329A0329B0047A203 /* AuthActionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A50572229A0329B0047A203 /* AuthActionsHelper.swift */; }; @@ -110,7 +109,7 @@ 8AFBAFDA296F002A0022A7E3 /* RouteModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFBAFD9296F002A0022A7E3 /* RouteModel.swift */; }; 8AFBAFDC296F13F40022A7E3 /* CLLocationCoordinate2D+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFBAFDB296F13F40022A7E3 /* CLLocationCoordinate2D+Extension.swift */; }; 8AFBAFDE296F14A30022A7E3 /* CLLocation+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFBAFDD296F14A30022A7E3 /* CLLocation+Extension.swift */; }; - 8AFBAFE0296F19BC0022A7E3 /* AWSLocationTravelMode+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFBAFDF296F19BC0022A7E3 /* AWSLocationTravelMode+Extension.swift */; }; + 8AFBAFE0296F19BC0022A7E3 /* RouteTravelMode+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFBAFDF296F19BC0022A7E3 /* RouteTravelMode+Extension.swift */; }; 8AFDA09429DDBB3600114BB7 /* VersionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDA09329DDBB3600114BB7 /* VersionVC.swift */; }; 8AFDA09629DDBC3800114BB7 /* VersionVCBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFDA09529DDBC3800114BB7 /* VersionVCBuilder.swift */; }; 963EBF5229AF92F7001961F2 /* LocationServicesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 963EBF5129AF92F7001961F2 /* LocationServicesTests.swift */; }; @@ -246,7 +245,6 @@ ADB0B9A0295AF59600C219EC /* DirectionPresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADB0B99F295AF59600C219EC /* DirectionPresentation.swift */; }; ADB0B9A5295BAE2B00C219EC /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADB0B9A4295BAE2B00C219EC /* Double+Extension.swift */; }; ADB46519295D57A6004088AE /* DirectionTextFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADB46518295D57A6004088AE /* DirectionTextFieldModel.swift */; }; - ADBD7FAA2947BFF6005DB4E4 /* AWSSignatureV4Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADBD7FA92947BFF6005DB4E4 /* AWSSignatureV4Delegate.swift */; }; ADBD7FB229493A9B005DB4E4 /* Int+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADBD7FB129493A9B005DB4E4 /* Int+Extension.swift */; }; ADBD7FB62949B579005DB4E4 /* POICardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADBD7FB52949B579005DB4E4 /* POICardView.swift */; }; ADBD7FBB2949C687005DB4E4 /* POICardVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADBD7FBA2949C687005DB4E4 /* POICardVC.swift */; }; @@ -356,13 +354,15 @@ F10994F829D1AB2B001D3464 /* AWSConnectUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F10994F729D1AB2B001D3464 /* AWSConnectUITests.swift */; }; F10994FA29D1C834001D3464 /* UITestSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F10994F929D1C834001D3464 /* UITestSettingsScreen.swift */; }; F111DF0F29F0214B00D08641 /* MapStyleViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F111DF0E29F0214B00D08641 /* MapStyleViewModelTests.swift */; }; + F11E045E2CD509A2003F1641 /* AmazonLocationiOSAuthSDK in Frameworks */ = {isa = PBXBuildFile; productRef = F11E045D2CD509A2003F1641 /* AmazonLocationiOSAuthSDK */; }; + F11E04612CD515C0003F1641 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F11E04602CD515C0003F1641 /* KeychainSwift */; }; + F11E04632CD516DD003F1641 /* KeyChainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11E04622CD516DD003F1641 /* KeyChainHelper.swift */; }; F139208829EE918E0042CBC9 /* AWSLocationTravelModeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F139208429EE918E0042CBC9 /* AWSLocationTravelModeTests.swift */; }; F139208929EE918E0042CBC9 /* CLLocationCoordinate2DExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F139208529EE918E0042CBC9 /* CLLocationCoordinate2DExtensionTests.swift */; }; F139208A29EE918E0042CBC9 /* CLLocationExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F139208629EE918E0042CBC9 /* CLLocationExtensionTests.swift */; }; F139208B29EE918E0042CBC9 /* DateExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F139208729EE918E0042CBC9 /* DateExtensionTests.swift */; }; F139208D29EEA19D0042CBC9 /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F139208C29EEA19D0042CBC9 /* LoginViewModelTests.swift */; }; F139208F29EEB7640042CBC9 /* ExploreMapStyleViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F139208E29EEB7640042CBC9 /* ExploreMapStyleViewModelTests.swift */; }; - F139DBC829F0283900D4BA98 /* DataProviderViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F139DBC729F0283900D4BA98 /* DataProviderViewModelTests.swift */; }; F139DBCA29F0346300D4BA98 /* RouteOptionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F139DBC929F0346300D4BA98 /* RouteOptionViewModelTests.swift */; }; F139DBD029F0401400D4BA98 /* ExploreViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F139DBCF29F0401400D4BA98 /* ExploreViewModelTests.swift */; }; F166819C29D3218D00FBD27C /* UITestTabBarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F166819B29D3218D00FBD27C /* UITestTabBarScreen.swift */; }; @@ -371,33 +371,31 @@ F17BE2AE29F7F564001A4ADF /* RoutingAPIServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17BE2AD29F7F564001A4ADF /* RoutingAPIServiceMock.swift */; }; F17BE2B029F819C2001A4ADF /* SearchViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17BE2AF29F819C2001A4ADF /* SearchViewModelTests.swift */; }; F18BD50A2AC481DA008FD008 /* AWSLoginServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18BD5092AC481DA008FD008 /* AWSLoginServiceMock.swift */; }; - F18DEEDC2C75908200335F3E /* AWSLocation in Frameworks */ = {isa = PBXBuildFile; productRef = F18DEEDB2C75908200335F3E /* AWSLocation */; }; - F18DEEDE2C75929C00335F3E /* AuthHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEDD2C75929C00335F3E /* AuthHelper.swift */; }; - F18DEEE02C7592BB00335F3E /* LocationCredentialsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEDF2C7592BB00335F3E /* LocationCredentialsProvider.swift */; }; - F18DEEE22C75932800335F3E /* AmazonLocationCognitoCredentialsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEE12C75932800335F3E /* AmazonLocationCognitoCredentialsProvider.swift */; }; F18DEEE42C75942E00335F3E /* CognitoCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEE32C75942E00335F3E /* CognitoCredentials.swift */; }; F18DEEE62C75947000335F3E /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEE52C75947000335F3E /* Logger.swift */; }; - F18DEEEA2C75EC3400335F3E /* AWSSignerV4.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEE92C75EC3300335F3E /* AWSSignerV4.swift */; }; - F18DEEEC2C75F0C800335F3E /* AmazonLocationApiCredentialsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEEB2C75F0C800335F3E /* AmazonLocationApiCredentialsProvider.swift */; }; - F18DEEEE2C75F2D300335F3E /* CognitoCredentialsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEED2C75F2D300335F3E /* CognitoCredentialsProvider.swift */; }; - F18DEEF02C76178A00335F3E /* KeyChainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEEF2C76178A00335F3E /* KeyChainHelper.swift */; }; - F18DEEF32C76189300335F3E /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F18DEEF22C76189300335F3E /* KeychainSwift */; }; - F18DEEFA2C77568900335F3E /* AwsCommonRuntimeKit in Frameworks */ = {isa = PBXBuildFile; productRef = F18DEEF92C77568900335F3E /* AwsCommonRuntimeKit */; }; F18DEEFC2C7774B500335F3E /* MqttIoTContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18DEEFB2C7774B500335F3E /* MqttIoTContext.swift */; }; F19616BE2AC45E530070172F /* GeofenceDashboardViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F19616BD2AC45E530070172F /* GeofenceDashboardViewModelTests.swift */; }; + F1AD36F82CCA7CA000E1BB7B /* UITestPoliticalViewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AD36F72CCA7C9000E1BB7B /* UITestPoliticalViewScreen.swift */; }; F1B2388329EFD402001E2066 /* DirectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B2388229EFD402001E2066 /* DirectionViewModelTests.swift */; }; F1B2388529F011C5001E2066 /* POICardViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B2388429F011C5001E2066 /* POICardViewModelTests.swift */; }; F1B2388729F01B88001E2066 /* AboutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B2388629F01B88001E2066 /* AboutViewModelTests.swift */; }; F1B642692A2F330D00FB40E3 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B642682A2F330D00FB40E3 /* UIColor+Extension.swift */; }; F1BA942B2C510CAC002FEB51 /* MapLibre in Frameworks */ = {isa = PBXBuildFile; productRef = F1BA942A2C510CAC002FEB51 /* MapLibre */; }; F1BA943B2C526646002FEB51 /* AmazonLocationClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BA943A2C526646002FEB51 /* AmazonLocationClient.swift */; }; - F1BA943E2C53BAB7002FEB51 /* AWSIoT in Frameworks */ = {isa = PBXBuildFile; productRef = F1BA943D2C53BAB7002FEB51 /* AWSIoT */; }; - F1BA94402C57A1DB002FEB51 /* AWSIoTEvents in Frameworks */ = {isa = PBXBuildFile; productRef = F1BA943F2C57A1DB002FEB51 /* AWSIoTEvents */; }; - F1BA94422C57B111002FEB51 /* AWSCognitoIdentity in Frameworks */ = {isa = PBXBuildFile; productRef = F1BA94412C57B111002FEB51 /* AWSCognitoIdentity */; }; F1BA94442C624B63002FEB51 /* CognitoAuthHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BA94432C624B63002FEB51 /* CognitoAuthHelper.swift */; }; F1BA94462C6258F0002FEB51 /* ApiAuthHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BA94452C6258F0002FEB51 /* ApiAuthHelper.swift */; }; + F1C4DC542CC1614800AA235A /* ColorSegmentControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C4DC532CC1614800AA235A /* ColorSegmentControl.swift */; }; + F1C4DC642CC7ADD800AA235A /* PoliticalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C4DC632CC7ADCE00AA235A /* PoliticalViewController.swift */; }; + F1C4DC662CC7BAA000AA235A /* PoliticalViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C4DC652CC7BA9D00AA235A /* PoliticalViewCell.swift */; }; + F1C4DC682CC7BD7600AA235A /* PoliticalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C4DC672CC7BD7100AA235A /* PoliticalView.swift */; }; F1CB5B5C2A4486F200A346F5 /* GridBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1CB5B5B2A4486F200A346F5 /* GridBackgroundView.swift */; }; F1CE147229EFC63600680447 /* NavigationVCViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1CE147129EFC63500680447 /* NavigationVCViewModelTests.swift */; }; + F1E368602CB6C25E00AF3A5B /* AWSClientRuntime in Frameworks */ = {isa = PBXBuildFile; productRef = F1E3685F2CB6C25E00AF3A5B /* AWSClientRuntime */; }; + F1E368622CB6C25E00AF3A5B /* AWSCognitoIdentity in Frameworks */ = {isa = PBXBuildFile; productRef = F1E368612CB6C25E00AF3A5B /* AWSCognitoIdentity */; }; + F1E368642CB6C25E00AF3A5B /* AWSLocation in Frameworks */ = {isa = PBXBuildFile; productRef = F1E368632CB6C25E00AF3A5B /* AWSLocation */; }; + F1E3688F2CB6C8D800AF3A5B /* AwsCommonRuntimeKit in Frameworks */ = {isa = PBXBuildFile; productRef = F1E3688E2CB6C8D800AF3A5B /* AwsCommonRuntimeKit */; }; + F1E368912CB6C93100AF3A5B /* AWSIoT in Frameworks */ = {isa = PBXBuildFile; productRef = F1E368902CB6C93100AF3A5B /* AWSIoT */; }; + F1E368932CB6C93B00AF3A5B /* AWSIoTEvents in Frameworks */ = {isa = PBXBuildFile; productRef = F1E368922CB6C93B00AF3A5B /* AWSIoTEvents */; }; F1E5F8EC29E94EF000639EEC /* NSMutableAttributedStringExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E5F8E929E94EEF00639EEC /* NSMutableAttributedStringExtensionTests.swift */; }; F1E5F8ED29E94EF000639EEC /* SettingsDefaultValueHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E5F8EA29E94EEF00639EEC /* SettingsDefaultValueHelperTests.swift */; }; F1E5F8F029E986F300639EEC /* UserDefaultsHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E5F8EF29E986F300639EEC /* UserDefaultsHelperTests.swift */; }; @@ -467,7 +465,6 @@ 8A47805829C9BDC800B098CA /* UITestScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestScreen.swift; sourceTree = ""; }; 8A4D5FB229D6F64A00A79D3C /* SettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUITests.swift; sourceTree = ""; }; 8A4D5FB429D6FA5000A79D3C /* UITestSettingsMapStyleScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestSettingsMapStyleScreen.swift; sourceTree = ""; }; - 8A4D5FB629D719AE00A79D3C /* UITestSettingsDataProviderScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestSettingsDataProviderScreen.swift; sourceTree = ""; }; 8A4EE62D29D4B0EE00B699A9 /* NavigationUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationUITests.swift; sourceTree = ""; }; 8A4EE62F29D4B45D00B699A9 /* UITestRouteOptionsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestRouteOptionsScreen.swift; sourceTree = ""; }; 8A50572229A0329B0047A203 /* AuthActionsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthActionsHelper.swift; sourceTree = ""; }; @@ -532,7 +529,7 @@ 8AFBAFD9296F002A0022A7E3 /* RouteModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteModel.swift; sourceTree = ""; }; 8AFBAFDB296F13F40022A7E3 /* CLLocationCoordinate2D+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CLLocationCoordinate2D+Extension.swift"; sourceTree = ""; }; 8AFBAFDD296F14A30022A7E3 /* CLLocation+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CLLocation+Extension.swift"; sourceTree = ""; }; - 8AFBAFDF296F19BC0022A7E3 /* AWSLocationTravelMode+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AWSLocationTravelMode+Extension.swift"; sourceTree = ""; }; + 8AFBAFDF296F19BC0022A7E3 /* RouteTravelMode+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RouteTravelMode+Extension.swift"; sourceTree = ""; }; 8AFDA09329DDBB3600114BB7 /* VersionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionVC.swift; sourceTree = ""; }; 8AFDA09529DDBC3800114BB7 /* VersionVCBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionVCBuilder.swift; sourceTree = ""; }; 963EBF4F29AF92F5001961F2 /* LocationServicesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LocationServicesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -672,7 +669,6 @@ ADB0B99F295AF59600C219EC /* DirectionPresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectionPresentation.swift; sourceTree = ""; }; ADB0B9A4295BAE2B00C219EC /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; ADB46518295D57A6004088AE /* DirectionTextFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectionTextFieldModel.swift; sourceTree = ""; }; - ADBD7FA92947BFF6005DB4E4 /* AWSSignatureV4Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSSignatureV4Delegate.swift; sourceTree = ""; }; ADBD7FB129493A9B005DB4E4 /* Int+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Extension.swift"; sourceTree = ""; }; ADBD7FB52949B579005DB4E4 /* POICardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POICardView.swift; sourceTree = ""; }; ADBD7FBA2949C687005DB4E4 /* POICardVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POICardVC.swift; sourceTree = ""; }; @@ -782,13 +778,13 @@ F10994F729D1AB2B001D3464 /* AWSConnectUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSConnectUITests.swift; sourceTree = ""; }; F10994F929D1C834001D3464 /* UITestSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestSettingsScreen.swift; sourceTree = ""; }; F111DF0E29F0214B00D08641 /* MapStyleViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapStyleViewModelTests.swift; sourceTree = ""; }; + F11E04622CD516DD003F1641 /* KeyChainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChainHelper.swift; sourceTree = ""; }; F139208429EE918E0042CBC9 /* AWSLocationTravelModeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AWSLocationTravelModeTests.swift; sourceTree = ""; }; F139208529EE918E0042CBC9 /* CLLocationCoordinate2DExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CLLocationCoordinate2DExtensionTests.swift; sourceTree = ""; }; F139208629EE918E0042CBC9 /* CLLocationExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CLLocationExtensionTests.swift; sourceTree = ""; }; F139208729EE918E0042CBC9 /* DateExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateExtensionTests.swift; sourceTree = ""; }; F139208C29EEA19D0042CBC9 /* LoginViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelTests.swift; sourceTree = ""; }; F139208E29EEB7640042CBC9 /* ExploreMapStyleViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreMapStyleViewModelTests.swift; sourceTree = ""; }; - F139DBC729F0283900D4BA98 /* DataProviderViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProviderViewModelTests.swift; sourceTree = ""; }; F139DBC929F0346300D4BA98 /* RouteOptionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteOptionViewModelTests.swift; sourceTree = ""; }; F139DBCF29F0401400D4BA98 /* ExploreViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewModelTests.swift; sourceTree = ""; }; F166819B29D3218D00FBD27C /* UITestTabBarScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestTabBarScreen.swift; sourceTree = ""; }; @@ -797,17 +793,11 @@ F17BE2AD29F7F564001A4ADF /* RoutingAPIServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingAPIServiceMock.swift; sourceTree = ""; }; F17BE2AF29F819C2001A4ADF /* SearchViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModelTests.swift; sourceTree = ""; }; F18BD5092AC481DA008FD008 /* AWSLoginServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSLoginServiceMock.swift; sourceTree = ""; }; - F18DEEDD2C75929C00335F3E /* AuthHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthHelper.swift; sourceTree = ""; }; - F18DEEDF2C7592BB00335F3E /* LocationCredentialsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationCredentialsProvider.swift; sourceTree = ""; }; - F18DEEE12C75932800335F3E /* AmazonLocationCognitoCredentialsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmazonLocationCognitoCredentialsProvider.swift; sourceTree = ""; }; F18DEEE32C75942E00335F3E /* CognitoCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CognitoCredentials.swift; sourceTree = ""; }; F18DEEE52C75947000335F3E /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; - F18DEEE92C75EC3300335F3E /* AWSSignerV4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSSignerV4.swift; sourceTree = ""; }; - F18DEEEB2C75F0C800335F3E /* AmazonLocationApiCredentialsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmazonLocationApiCredentialsProvider.swift; sourceTree = ""; }; - F18DEEED2C75F2D300335F3E /* CognitoCredentialsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CognitoCredentialsProvider.swift; sourceTree = ""; }; - F18DEEEF2C76178A00335F3E /* KeyChainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChainHelper.swift; sourceTree = ""; }; F18DEEFB2C7774B500335F3E /* MqttIoTContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttIoTContext.swift; sourceTree = ""; }; F19616BD2AC45E530070172F /* GeofenceDashboardViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeofenceDashboardViewModelTests.swift; sourceTree = ""; }; + F1AD36F72CCA7C9000E1BB7B /* UITestPoliticalViewScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestPoliticalViewScreen.swift; sourceTree = ""; }; F1B060962AD0209C0020CD8C /* LocationServicesUITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = LocationServicesUITests.xctestplan; sourceTree = ""; }; F1B2388229EFD402001E2066 /* DirectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectionViewModelTests.swift; sourceTree = ""; }; F1B2388429F011C5001E2066 /* POICardViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POICardViewModelTests.swift; sourceTree = ""; }; @@ -816,6 +806,10 @@ F1BA943A2C526646002FEB51 /* AmazonLocationClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmazonLocationClient.swift; sourceTree = ""; }; F1BA94432C624B63002FEB51 /* CognitoAuthHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CognitoAuthHelper.swift; sourceTree = ""; }; F1BA94452C6258F0002FEB51 /* ApiAuthHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiAuthHelper.swift; sourceTree = ""; }; + F1C4DC532CC1614800AA235A /* ColorSegmentControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSegmentControl.swift; sourceTree = ""; }; + F1C4DC632CC7ADCE00AA235A /* PoliticalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoliticalViewController.swift; sourceTree = ""; }; + F1C4DC652CC7BA9D00AA235A /* PoliticalViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoliticalViewCell.swift; sourceTree = ""; }; + F1C4DC672CC7BD7100AA235A /* PoliticalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoliticalView.swift; sourceTree = ""; }; F1CB5B5B2A4486F200A346F5 /* GridBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridBackgroundView.swift; sourceTree = ""; }; F1CE147129EFC63500680447 /* NavigationVCViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationVCViewModelTests.swift; sourceTree = ""; }; F1E5F8E929E94EEF00639EEC /* NSMutableAttributedStringExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSMutableAttributedStringExtensionTests.swift; sourceTree = ""; }; @@ -853,16 +847,18 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F18DEEF32C76189300335F3E /* KeychainSwift in Frameworks */, + F1E3688F2CB6C8D800AF3A5B /* AwsCommonRuntimeKit in Frameworks */, 96F6B00A299C2B6A009DD9C4 /* SnapKit in Frameworks */, + F1E368622CB6C25E00AF3A5B /* AWSCognitoIdentity in Frameworks */, + F1E368642CB6C25E00AF3A5B /* AWSLocation in Frameworks */, + F1E368912CB6C93100AF3A5B /* AWSIoT in Frameworks */, + F11E045E2CD509A2003F1641 /* AmazonLocationiOSAuthSDK in Frameworks */, + F11E04612CD515C0003F1641 /* KeychainSwift in Frameworks */, ADF26F6529300E59000147A0 /* SafariServices.framework in Frameworks */, - F1BA94422C57B111002FEB51 /* AWSCognitoIdentity in Frameworks */, - F1BA943E2C53BAB7002FEB51 /* AWSIoT in Frameworks */, - F18DEEFA2C77568900335F3E /* AwsCommonRuntimeKit in Frameworks */, + F1E368932CB6C93B00AF3A5B /* AWSIoTEvents in Frameworks */, ADF26F6329300E53000147A0 /* Security.framework in Frameworks */, + F1E368602CB6C25E00AF3A5B /* AWSClientRuntime in Frameworks */, F1BA942B2C510CAC002FEB51 /* MapLibre in Frameworks */, - F1BA94402C57A1DB002FEB51 /* AWSIoTEvents in Frameworks */, - F18DEEDC2C75908200335F3E /* AWSLocation in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1092,6 +1088,7 @@ 8A47805529C9BD8E00B098CA /* Screens */ = { isa = PBXGroup; children = ( + F1AD36F72CCA7C9000E1BB7B /* UITestPoliticalViewScreen.swift */, 8A47805829C9BDC800B098CA /* UITestScreen.swift */, 8A47805629C9BDA900B098CA /* UITestExploreScreen.swift */, F10994F529D1A908001D3464 /* UITestAWSScreen.swift */, @@ -1106,7 +1103,6 @@ F166819D29D3230B00FBD27C /* UITestTrackingScreen.swift */, F166819F29D323B200FBD27C /* UITestGeofenceScreen.swift */, 8A4EE62F29D4B45D00B699A9 /* UITestRouteOptionsScreen.swift */, - 8A4D5FB629D719AE00A79D3C /* UITestSettingsDataProviderScreen.swift */, ); path = Screens; sourceTree = ""; @@ -1235,7 +1231,6 @@ F139208429EE918E0042CBC9 /* AWSLocationTravelModeTests.swift */, F139208529EE918E0042CBC9 /* CLLocationCoordinate2DExtensionTests.swift */, F139208629EE918E0042CBC9 /* CLLocationExtensionTests.swift */, - F139DBC729F0283900D4BA98 /* DataProviderViewModelTests.swift */, F139208729EE918E0042CBC9 /* DateExtensionTests.swift */, F1E5F8F129E9880600639EEC /* DebounceManagerTests.swift */, F1B2388229EFD402001E2066 /* DirectionViewModelTests.swift */, @@ -1270,15 +1265,9 @@ AD16A42029357F2B006AEA8E /* Helpers */ = { isa = PBXGroup; children = ( - F18DEEDD2C75929C00335F3E /* AuthHelper.swift */, - F1BA94452C6258F0002FEB51 /* ApiAuthHelper.swift */, - F1BA94432C624B63002FEB51 /* CognitoAuthHelper.swift */, - F18DEEDF2C7592BB00335F3E /* LocationCredentialsProvider.swift */, - F18DEEED2C75F2D300335F3E /* CognitoCredentialsProvider.swift */, - F18DEEE12C75932800335F3E /* AmazonLocationCognitoCredentialsProvider.swift */, - F18DEEEB2C75F0C800335F3E /* AmazonLocationApiCredentialsProvider.swift */, - F1BA943A2C526646002FEB51 /* AmazonLocationClient.swift */, + F1AD36F92CCBABC700E1BB7B /* AuthHelpers */, AD16A42129357F38006AEA8E /* UserDefaultsHelper.swift */, + F11E04622CD516DD003F1641 /* KeyChainHelper.swift */, 8A3CC5EB2968510600A542DC /* DebounceManager.swift */, 8AF7D2AA2976B3F700E1563E /* LocationManager.swift */, 8AF7D2AC2976C02100E1563E /* AlertPresenter.swift */, @@ -1292,8 +1281,6 @@ F1F0C5882A13C19E009C7151 /* GeneralHelper.swift */, F18DEEE32C75942E00335F3E /* CognitoCredentials.swift */, F18DEEE52C75947000335F3E /* Logger.swift */, - F18DEEE92C75EC3300335F3E /* AWSSignerV4.swift */, - F18DEEEF2C76178A00335F3E /* KeyChainHelper.swift */, ); path = Helpers; sourceTree = ""; @@ -1888,13 +1875,6 @@ path = "Common Views"; sourceTree = ""; }; - AD3E370D2987199F0041D952 /* New Group */ = { - isa = PBXGroup; - children = ( - ); - path = "New Group"; - sourceTree = ""; - }; AD625E92293BCECF00C67B44 /* Search */ = { isa = PBXGroup; children = ( @@ -2278,9 +2258,11 @@ ADBDDF0029842CF000696EC6 /* Views */ = { isa = PBXGroup; children = ( - AD3E370D2987199F0041D952 /* New Group */, ADBDDF062984392A00696EC6 /* Cell */, ADBDDF022984386400696EC6 /* ExploreMapStyleHeaderView.swift */, + F1C4DC672CC7BD7100AA235A /* PoliticalView.swift */, + F1C4DC632CC7ADCE00AA235A /* PoliticalViewController.swift */, + F1C4DC532CC1614800AA235A /* ColorSegmentControl.swift */, ); path = Views; sourceTree = ""; @@ -2297,6 +2279,7 @@ ADBDDF062984392A00696EC6 /* Cell */ = { isa = PBXGroup; children = ( + F1C4DC652CC7BA9D00AA235A /* PoliticalViewCell.swift */, ADBDDF072984394500696EC6 /* ExploreMapStyleCell.swift */, ); path = Cell; @@ -2889,7 +2872,7 @@ 8AFBAFDB296F13F40022A7E3 /* CLLocationCoordinate2D+Extension.swift */, 8AFBAFDD296F14A30022A7E3 /* CLLocation+Extension.swift */, ADC1249D2979BB8300B08C20 /* Notificaton+Extension.swift */, - 8AFBAFDF296F19BC0022A7E3 /* AWSLocationTravelMode+Extension.swift */, + 8AFBAFDF296F19BC0022A7E3 /* RouteTravelMode+Extension.swift */, 8A3471CB2975D30B00BAF374 /* Locale+Extension.swift */, 8A76327B297EAE270064D60F /* MLNMapViewDelegate+Extension.swift */, ADB0B187299221A800282814 /* Date+Extension.swift */, @@ -2920,7 +2903,6 @@ children = ( ADF26F71293028BD000147A0 /* AWSLoginService.swift */, ADABECD4293FD44200E66FC5 /* LocationSearchService.swift */, - ADBD7FA92947BFF6005DB4E4 /* AWSSignatureV4Delegate.swift */, ADB0B99A295AF23C00C219EC /* RoutingService.swift */, ADC124A12979EA5F00B08C20 /* GeofenceService.swift */, ADCF384B29902815008289BB /* TrackingService.swift */, @@ -2972,6 +2954,16 @@ path = iot; sourceTree = ""; }; + F1AD36F92CCBABC700E1BB7B /* AuthHelpers */ = { + isa = PBXGroup; + children = ( + F1BA94452C6258F0002FEB51 /* ApiAuthHelper.swift */, + F1BA94432C624B63002FEB51 /* CognitoAuthHelper.swift */, + F1BA943A2C526646002FEB51 /* AmazonLocationClient.swift */, + ); + path = AuthHelpers; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -3028,12 +3020,14 @@ packageProductDependencies = ( 96F6B009299C2B6A009DD9C4 /* SnapKit */, F1BA942A2C510CAC002FEB51 /* MapLibre */, - F1BA943D2C53BAB7002FEB51 /* AWSIoT */, - F1BA943F2C57A1DB002FEB51 /* AWSIoTEvents */, - F1BA94412C57B111002FEB51 /* AWSCognitoIdentity */, - F18DEEDB2C75908200335F3E /* AWSLocation */, - F18DEEF22C76189300335F3E /* KeychainSwift */, - F18DEEF92C77568900335F3E /* AwsCommonRuntimeKit */, + F1E3685F2CB6C25E00AF3A5B /* AWSClientRuntime */, + F1E368612CB6C25E00AF3A5B /* AWSCognitoIdentity */, + F1E368632CB6C25E00AF3A5B /* AWSLocation */, + F1E3688E2CB6C8D800AF3A5B /* AwsCommonRuntimeKit */, + F1E368902CB6C93100AF3A5B /* AWSIoT */, + F1E368922CB6C93B00AF3A5B /* AWSIoTEvents */, + F11E045D2CD509A2003F1641 /* AmazonLocationiOSAuthSDK */, + F11E04602CD515C0003F1641 /* KeychainSwift */, ); productName = LocationServices; productReference = ADAE4FFA2935611C0000F7FC /* LocationServices.app */; @@ -3074,9 +3068,10 @@ packageReferences = ( 96F6B008299C2B6A009DD9C4 /* XCRemoteSwiftPackageReference "SnapKit" */, 96F6B00B299C2BC0009DD9C4 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */, - F1BA943C2C53BAB7002FEB51 /* XCRemoteSwiftPackageReference "aws-sdk-swift" */, - F18DEEF12C76189300335F3E /* XCRemoteSwiftPackageReference "keychain-swift" */, - F18DEEF82C77568900335F3E /* XCRemoteSwiftPackageReference "aws-crt-swift" */, + F1E3685E2CB6C25E00AF3A5B /* XCRemoteSwiftPackageReference "aws-sdk-swift" */, + F1E3688D2CB6C8D800AF3A5B /* XCRemoteSwiftPackageReference "aws-crt-swift" */, + F11E045C2CD509A2003F1641 /* XCRemoteSwiftPackageReference "amazon-location-mobile-auth-sdk-ios" */, + F11E045F2CD515C0003F1641 /* XCRemoteSwiftPackageReference "keychain-swift" */, ); productRefGroup = AD2AF023292E892000149904; projectDirPath = ""; @@ -3165,6 +3160,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F1AD36F82CCA7CA000E1BB7B /* UITestPoliticalViewScreen.swift in Sources */, 8AD0A6E329DDFC9700B79078 /* XCTWaiter+Extension.swift in Sources */, 8A063E9F29C9C238001FF40C /* UserDefaultsHelper.swift in Sources */, 8A063EA029C9C245001FF40C /* ViewsIdentifiers.swift in Sources */, @@ -3194,7 +3190,6 @@ F16681A029D323B200FBD27C /* UITestGeofenceScreen.swift in Sources */, 8A4D5FB329D6F64A00A79D3C /* SettingsUITests.swift in Sources */, 8AD67F9C29D1D594004A5F7D /* UITestRoutingScreen.swift in Sources */, - 8A4D5FB729D719AE00A79D3C /* UITestSettingsDataProviderScreen.swift in Sources */, F166819C29D3218D00FBD27C /* UITestTabBarScreen.swift in Sources */, 8AD67FA029D1E39C004A5F7D /* UITestRouteType.swift in Sources */, 8AD67FA229D1E5B3004A5F7D /* UITestNavigationScreen.swift in Sources */, @@ -3208,7 +3203,6 @@ files = ( F139208D29EEA19D0042CBC9 /* LoginViewModelTests.swift in Sources */, F1B2388529F011C5001E2066 /* POICardViewModelTests.swift in Sources */, - F139DBC829F0283900D4BA98 /* DataProviderViewModelTests.swift in Sources */, F17BE2AE29F7F564001A4ADF /* RoutingAPIServiceMock.swift in Sources */, 8ADB62A429F2A4B6002C7971 /* AddGeofenceViewModelOutputProtocolMock.swift in Sources */, 8ADB629E29F2A051002C7971 /* GeofenceAPIServiceMock.swift in Sources */, @@ -3256,7 +3250,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F18DEEEE2C75F2D300335F3E /* CognitoCredentialsProvider.swift in Sources */, ADC475002971E9E900AAE7A3 /* AmazonLocationButton.swift in Sources */, DF8005E32A0AA47500758BBE /* UIDevice+Extensions.swift in Sources */, AD2061772968A871005F8BBB /* LoginDefaultInformationView.swift in Sources */, @@ -3284,6 +3277,7 @@ ADABECD9293FD65B00E66FC5 /* AppConstants.swift in Sources */, 8AEFDDC72A262F5300090D9E /* GradientView.swift in Sources */, ADD5D7CE296F843D0022480B /* GeofenceDashboardCell.swift in Sources */, + F11E04632CD516DD003F1641 /* KeyChainHelper.swift in Sources */, AD2AF06C292E99B700149904 /* TabBarCoordinator.swift in Sources */, ADBDDF082984394500696EC6 /* ExploreMapStyleCell.swift in Sources */, F1CB5B5C2A4486F200A346F5 /* GridBackgroundView.swift in Sources */, @@ -3298,7 +3292,6 @@ AD3E370C2986DDD00041D952 /* ExploreMapStyleVC+TableView.swift in Sources */, ADBDDEF12983176300696EC6 /* DataProviderVC+TableView.swift in Sources */, ADABECDB293FD9CC00E66FC5 /* SearchPresentation.swift in Sources */, - F18DEEF02C76178A00335F3E /* KeyChainHelper.swift in Sources */, 8A85A75C29A40A1E0022ABE0 /* TrackingHistorySectionHeaderView.swift in Sources */, 8A10E37F29E97950005165CD /* SplashContracts.swift in Sources */, ADD139AB2981D72C0003F917 /* MapStyleModel.swift in Sources */, @@ -3333,10 +3326,12 @@ AD33F4CC29518A08005719D3 /* RouteOptionView.swift in Sources */, AD2AF069292E98C700149904 /* LoginCoordinator.swift in Sources */, F1BA94462C6258F0002FEB51 /* ApiAuthHelper.swift in Sources */, + F1C4DC682CC7BD7600AA235A /* PoliticalView.swift in Sources */, ADD1395C298087840003F917 /* UnitSceneBuilder.swift in Sources */, ADBD7FC22949CBC2005DB4E4 /* BottomSheetTransitioningDelegate.swift in Sources */, 8A276E2629DFF9FD00B74136 /* TermsAndConditionsVCBuilder.swift in Sources */, AD989734298B2B2D0041E867 /* TrackingDashboard.swift in Sources */, + F1C4DC542CC1614800AA235A /* ColorSegmentControl.swift in Sources */, F1021F482A1B6C9D00B84312 /* UIImageView+Extension.swift in Sources */, ADD139902980A2590003F917 /* RouteOptionContracts.swift in Sources */, AD625E9C293BEFAC00C67B44 /* SearchCell.swift in Sources */, @@ -3363,7 +3358,6 @@ F18DEEE62C75947000335F3E /* Logger.swift in Sources */, AD989748298C56EE0041E867 /* TrackingHistoryBuilder.swift in Sources */, 8A76327C297EAE270064D60F /* MLNMapViewDelegate+Extension.swift in Sources */, - F18DEEEA2C75EC3400335F3E /* AWSSignerV4.swift in Sources */, 8A85A75A29A3A9C80022ABE0 /* TrackingEventModel.swift in Sources */, AD625E94293BCEE200C67B44 /* SearchVCBuilder.swift in Sources */, AD989742298C521E0041E867 /* TrackingHistoryVC.swift in Sources */, @@ -3394,7 +3388,6 @@ ADCF384A298FF68D008289BB /* TrackHistoryCell.swift in Sources */, ADD5D7D9296F87710022480B /* AddGeofenceSearchView.swift in Sources */, ADC124A22979EA5F00B08C20 /* GeofenceService.swift in Sources */, - ADBD7FAA2947BFF6005DB4E4 /* AWSSignatureV4Delegate.swift in Sources */, F1F0C5872A0E5401009C7151 /* ErrorHandler.swift in Sources */, 8AFDA09629DDBC3800114BB7 /* VersionVCBuilder.swift in Sources */, ADAF3653294A6A0700DC4774 /* POICardViewModel.swift in Sources */, @@ -3412,7 +3405,6 @@ ADABECD7293FD56B00E66FC5 /* SearchModel.swift in Sources */, ADC124A02979E3C200B08C20 /* AddGeofenceVC+TableView.swift in Sources */, ADAF3657294A841000DC4774 /* Array+Extension.swift in Sources */, - F18DEEEC2C75F0C800335F3E /* AmazonLocationApiCredentialsProvider.swift in Sources */, 8ACAAD7629F14B9D00523256 /* CLLocationManager+Extension.swift in Sources */, AD2AF0EB292EAEDF00149904 /* AppColorsConstants.swift in Sources */, AD2AF030292E892000149904 /* AppDelegate.swift in Sources */, @@ -3425,8 +3417,7 @@ AD33F4B92950F42A005719D3 /* DirectionViewModel.swift in Sources */, AD625EAE293CDFA900C67B44 /* NetworkCore.swift in Sources */, ADD1394A298069F50003F917 /* SettingVC+TableView.swift in Sources */, - 8AFBAFE0296F19BC0022A7E3 /* AWSLocationTravelMode+Extension.swift in Sources */, - F18DEEE22C75932800335F3E /* AmazonLocationCognitoCredentialsProvider.swift in Sources */, + 8AFBAFE0296F19BC0022A7E3 /* RouteTravelMode+Extension.swift in Sources */, 8A439F0429C0A7D900B59D0B /* WelcomeVC.swift in Sources */, 8A1110D6297AB20300BC86E1 /* LSFaux3DUserLocationAnnotationView.swift in Sources */, ADC97383295EDA17005AE918 /* NavigationVC.swift in Sources */, @@ -3466,6 +3457,7 @@ 8ACD71C82A08F6F200EC23F3 /* SplitViewExploreMapCoordinator.swift in Sources */, 8A3471CC2975D30B00BAF374 /* Locale+Extension.swift in Sources */, 8AE83A912A274B2F00CF6E7C /* UIView+Extension.swift in Sources */, + F1C4DC662CC7BAA000AA235A /* PoliticalViewCell.swift in Sources */, AD2AF0DB292EABFE00149904 /* AboutContracts.swift in Sources */, 8A54FFDA2A02722400694CE6 /* SplitViewAboutCoordinator.swift in Sources */, AD2AF07D292EA57A00149904 /* ExploreContracts.swift in Sources */, @@ -3482,6 +3474,7 @@ AD2AF077292EA54D00149904 /* ExploreVC.swift in Sources */, ADD5D7B4296E94240022480B /* PostLoginContracts.swift in Sources */, ADD5D7A9296DFF8D0022480B /* PostLoginBuilder.swift in Sources */, + F1C4DC642CC7ADD800AA235A /* PoliticalViewController.swift in Sources */, ADC474FA2971E11E00AAE7A3 /* InitialGeofenceView.swift in Sources */, ADB46519295D57A6004088AE /* DirectionTextFieldModel.swift in Sources */, ADBDDEED29830D9E00696EC6 /* UnitVC+TableView.swift in Sources */, @@ -3539,13 +3532,11 @@ ADD1397B298098CF0003F917 /* ResetPasswordView.swift in Sources */, ADD1396C298097020003F917 /* DataProviderBuilder.swift in Sources */, ADD139A22981C5050003F917 /* MapStyleVC.swift in Sources */, - F18DEEDE2C75929C00335F3E /* AuthHelper.swift in Sources */, 8A439EF929BB965100B59D0B /* AttributionVC.swift in Sources */, AD2AF0AA292EAA2700149904 /* TrackingViewModel.swift in Sources */, ADF26F7829303E16000147A0 /* AWSUserModel.swift in Sources */, 8A0AF02A2A03CBE9003E503D /* UISplitViewController+Extension.swift in Sources */, 8A3FD14C29A69CE000DFA6F6 /* PlaceholderAnimator.swift in Sources */, - F18DEEE02C7592BB00335F3E /* LocationCredentialsProvider.swift in Sources */, ADD139582980867D0003F917 /* UnitSceneContracts.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4024,40 +4015,48 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SnapKit/SnapKit.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 5.0.0; + kind = upToNextMinorVersion; + minimumVersion = 5.7.1; }; }; 96F6B00B299C2BC0009DD9C4 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/maplibre/maplibre-gl-native-distribution"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 6.3.3; + kind = exactVersion; + version = 6.7.1; }; }; - F18DEEF12C76189300335F3E /* XCRemoteSwiftPackageReference "keychain-swift" */ = { + F11E045C2CD509A2003F1641 /* XCRemoteSwiftPackageReference "amazon-location-mobile-auth-sdk-ios" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/evgenyneu/keychain-swift.git"; + repositoryURL = "https://github.com/aws-geospatial/amazon-location-mobile-auth-sdk-ios.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 24.0.0; + kind = upToNextMinorVersion; + minimumVersion = 1.0.0; }; }; - F18DEEF82C77568900335F3E /* XCRemoteSwiftPackageReference "aws-crt-swift" */ = { + F11E045F2CD515C0003F1641 /* XCRemoteSwiftPackageReference "keychain-swift" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/awslabs/aws-crt-swift"; + repositoryURL = "https://github.com/evgenyneu/keychain-swift.git"; requirement = { - branch = mqtt_test_app; - kind = branch; + kind = upToNextMinorVersion; + minimumVersion = 20.0.0; }; }; - F1BA943C2C53BAB7002FEB51 /* XCRemoteSwiftPackageReference "aws-sdk-swift" */ = { + F1E3685E2CB6C25E00AF3A5B /* XCRemoteSwiftPackageReference "aws-sdk-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/awslabs/aws-sdk-swift"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.0.18; + kind = upToNextMinorVersion; + minimumVersion = 1.0.32; + }; + }; + F1E3688D2CB6C8D800AF3A5B /* XCRemoteSwiftPackageReference "aws-crt-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/awslabs/aws-crt-swift"; + requirement = { + branch = mqtt_test_app; + kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -4068,40 +4067,50 @@ package = 96F6B008299C2B6A009DD9C4 /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; - F18DEEDB2C75908200335F3E /* AWSLocation */ = { + F11E045D2CD509A2003F1641 /* AmazonLocationiOSAuthSDK */ = { isa = XCSwiftPackageProductDependency; - package = F1BA943C2C53BAB7002FEB51 /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; - productName = AWSLocation; + package = F11E045C2CD509A2003F1641 /* XCRemoteSwiftPackageReference "amazon-location-mobile-auth-sdk-ios" */; + productName = AmazonLocationiOSAuthSDK; }; - F18DEEF22C76189300335F3E /* KeychainSwift */ = { + F11E04602CD515C0003F1641 /* KeychainSwift */ = { isa = XCSwiftPackageProductDependency; - package = F18DEEF12C76189300335F3E /* XCRemoteSwiftPackageReference "keychain-swift" */; + package = F11E045F2CD515C0003F1641 /* XCRemoteSwiftPackageReference "keychain-swift" */; productName = KeychainSwift; }; - F18DEEF92C77568900335F3E /* AwsCommonRuntimeKit */ = { - isa = XCSwiftPackageProductDependency; - package = F18DEEF82C77568900335F3E /* XCRemoteSwiftPackageReference "aws-crt-swift" */; - productName = AwsCommonRuntimeKit; - }; F1BA942A2C510CAC002FEB51 /* MapLibre */ = { isa = XCSwiftPackageProductDependency; package = 96F6B00B299C2BC0009DD9C4 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */; productName = MapLibre; }; - F1BA943D2C53BAB7002FEB51 /* AWSIoT */ = { + F1E3685F2CB6C25E00AF3A5B /* AWSClientRuntime */ = { isa = XCSwiftPackageProductDependency; - package = F1BA943C2C53BAB7002FEB51 /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; - productName = AWSIoT; + package = F1E3685E2CB6C25E00AF3A5B /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; + productName = AWSClientRuntime; }; - F1BA943F2C57A1DB002FEB51 /* AWSIoTEvents */ = { + F1E368612CB6C25E00AF3A5B /* AWSCognitoIdentity */ = { isa = XCSwiftPackageProductDependency; - package = F1BA943C2C53BAB7002FEB51 /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; - productName = AWSIoTEvents; + package = F1E3685E2CB6C25E00AF3A5B /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; + productName = AWSCognitoIdentity; }; - F1BA94412C57B111002FEB51 /* AWSCognitoIdentity */ = { + F1E368632CB6C25E00AF3A5B /* AWSLocation */ = { isa = XCSwiftPackageProductDependency; - package = F1BA943C2C53BAB7002FEB51 /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; - productName = AWSCognitoIdentity; + package = F1E3685E2CB6C25E00AF3A5B /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; + productName = AWSLocation; + }; + F1E3688E2CB6C8D800AF3A5B /* AwsCommonRuntimeKit */ = { + isa = XCSwiftPackageProductDependency; + package = F1E3688D2CB6C8D800AF3A5B /* XCRemoteSwiftPackageReference "aws-crt-swift" */; + productName = AwsCommonRuntimeKit; + }; + F1E368902CB6C93100AF3A5B /* AWSIoT */ = { + isa = XCSwiftPackageProductDependency; + package = F1E3685E2CB6C25E00AF3A5B /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; + productName = AWSIoT; + }; + F1E368922CB6C93B00AF3A5B /* AWSIoTEvents */ = { + isa = XCSwiftPackageProductDependency; + package = F1E3685E2CB6C25E00AF3A5B /* XCRemoteSwiftPackageReference "aws-sdk-swift" */; + productName = AWSIoTEvents; }; /* End XCSwiftPackageProductDependency section */ diff --git a/LocationServices/LocationServices.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/LocationServices/LocationServices.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 482a7e97..7ad84720 100644 --- a/LocationServices/LocationServices.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/LocationServices/LocationServices.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,13 +1,22 @@ { - "originHash" : "33fa476c44ec13ba9dfb107bc55a239c88b17c3ebefc9a581f9498da82ded5fd", + "originHash" : "37fc4666dc940bc2b2a2f41d16bb81b2d1c617bbb6ce9c52d9cf2662479f17b0", "pins" : [ + { + "identity" : "amazon-location-mobile-auth-sdk-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/aws-geospatial/amazon-location-mobile-auth-sdk-ios.git", + "state" : { + "revision" : "787edfff4d6cc7573dc351d3bc851675114838e6", + "version" : "1.0.0" + } + }, { "identity" : "aws-crt-swift", "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-crt-swift", "state" : { "branch" : "mqtt_test_app", - "revision" : "e7e603d3aadd1749b3e5cc554832ab337fdfd8f3" + "revision" : "599ce744efbba6cc992bf0c59de2c6e4077bb04d" } }, { @@ -15,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/awslabs/aws-sdk-swift", "state" : { - "revision" : "8ec5c0c820ca912a6bad1fc31c287d321385d698", - "version" : "1.0.18" + "revision" : "54a459ed4d9af2cb2d8e6fdcd96b72543bebb88a", + "version" : "1.0.33" } }, { @@ -24,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/evgenyneu/keychain-swift.git", "state" : { - "revision" : "5e1b02b6a9dac2a759a1d5dbc175c86bd192a608", - "version" : "24.0.0" + "revision" : "d108a1fa6189e661f91560548ef48651ed8d93b9", + "version" : "20.0.0" } }, { @@ -42,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/smithy-lang/smithy-swift", "state" : { - "revision" : "bf096bf5c507e2c3f9ecc23c0c65dba2e1ffe3b2", - "version" : "0.79.0" + "revision" : "0d4d3eae8cfb04f3e0cbc4e7740e7344cc9fac55", + "version" : "0.87.0" } }, { diff --git a/LocationServices/LocationServices/Assets.xcassets/Colors/mapStyleTintColor.colorset/Contents.json b/LocationServices/LocationServices/Assets.xcassets/Colors/mapStyleTintColor.colorset/Contents.json new file mode 100644 index 00000000..4d7ba2c7 --- /dev/null +++ b/LocationServices/LocationServices/Assets.xcassets/Colors/mapStyleTintColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x98", + "green" : "0x84", + "red" : "0x01" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x98", + "green" : "0x84", + "red" : "0x01" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocationServices/LocationServices/Assets.xcassets/Colors/politicalListViewBackgroundColor.colorset/Contents.json b/LocationServices/LocationServices/Assets.xcassets/Colors/politicalListViewBackgroundColor.colorset/Contents.json new file mode 100644 index 00000000..45b3c750 --- /dev/null +++ b/LocationServices/LocationServices/Assets.xcassets/Colors/politicalListViewBackgroundColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF0", + "green" : "0xF2", + "red" : "0xF2" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF0", + "green" : "0xF2", + "red" : "0xF2" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocationServices/LocationServices/Constants/AppColorsConstants.swift b/LocationServices/LocationServices/Constants/AppColorsConstants.swift index 3bce8400..f1df4be3 100644 --- a/LocationServices/LocationServices/Constants/AppColorsConstants.swift +++ b/LocationServices/LocationServices/Constants/AppColorsConstants.swift @@ -21,6 +21,8 @@ extension UIColor { //Map Colors static let mapDarkBlackColor = UIColor(named: "mapdarkblackcolor")! static let maplightGrayColor = UIColor(named: "maplightGrayColor")! + static let mapStyleTintColor = UIColor(named: "mapStyleTintColor")! + static let politicalListViewBackgroundColor = UIColor(named: "politicalListViewBackgroundColor")! static let mapElementDiverColor = UIColor(named: "dividerColor")! static let closeButtonBackgroundColor = UIColor(named: "closeButtonBackgroundColor")! static let closeButtonTintColor = UIColor(named: "closeButtonTintColor")! diff --git a/LocationServices/LocationServices/Constants/AppConstants.swift b/LocationServices/LocationServices/Constants/AppConstants.swift index 8ba5ca07..5f0e6ec3 100644 --- a/LocationServices/LocationServices/Constants/AppConstants.swift +++ b/LocationServices/LocationServices/Constants/AppConstants.swift @@ -8,62 +8,49 @@ import Foundation final class DefaultUserSettings { - static let mapStyle = MapStyleModel(title: "Light" , - imageType: .light, - type: .esri, - isSelected: true) - static let mapHereStyle = MapStyleModel(title: "Explore", - imageType: .explore, - type: .here, + static let mapStyle = MapStyleModel(title: "Standard" , + imageType: .standard, isSelected: true) + static let mapStyleColorType = MapStyleColorType.light static let unitValue = "Metric" } final class DefaultMapStyles { + + static func getMapStyleUrl() -> URL? { + if let apiKey = AmazonLocationClient.getApiKey(), let regionName = AmazonLocationClient.getApiKeyRegion() { + var colorType = UserDefaultsHelper.getObject(value: MapStyleColorType.self, key: .mapStyleColorType) ?? MapStyleColorType.light + let style = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) ?? mapStyles.first! + if style.imageType == .hybrid || style.imageType == .satellite { + colorType = .light + } + + let politicalView = UserDefaultsHelper.getObject(value: PoliticalViewType.self, key: .politicalView) + let urlString = "https://maps.geo.\(regionName).amazonaws.com/v2/styles/\(style.title)/descriptor?key=\(apiKey)&color-scheme=\(colorType.colorName)\(politicalView != nil ? "&political-view=\(politicalView!.countryCode)" : "")" + + return URL(string: urlString) + } + else { + return nil + } + } + static let mapStyles: [MapStyleModel] = [ - MapStyleModel(title: "Light" , - imageType: .light , - type: .esri, + MapStyleModel(title: "Standard" , + imageType: .standard , isSelected: true), - MapStyleModel(title: "Streets" , - imageType: .street , - type: .esri, - isSelected: false), - MapStyleModel(title: "Navigation" , - imageType: .navigation , - type: .esri, - isSelected: false), - MapStyleModel(title: "Dark Gray" , - imageType: .darkGray , - type: .esri, - isSelected: false), - MapStyleModel(title: "Light Gray" , - imageType: .lightGray , - type: .esri, - isSelected: false), - MapStyleModel(title: "Imagery" , - imageType: .Imagery , - type: .esri, - isSelected: false), - MapStyleModel(title: "Explore" , - imageType: .explore , - type: .here, - isSelected: false), - MapStyleModel(title: "Contrast" , - imageType: .contrast , - type: .here, - isSelected: false), - MapStyleModel(title: "ExploreTruck" , - imageType: .exploreTruck , - type: .here, - isSelected: false), - MapStyleModel(title: "Imagery" , - imageType: .hereImagery , - type: .here, + MapStyleModel(title: "Monochrome" , + imageType: .monochrome , isSelected: false), MapStyleModel(title: "Hybrid" , imageType: .hybrid , - type: .here, + isSelected: false), + MapStyleModel(title: "Satellite" , + imageType: .satellite , isSelected: false), ] } + +enum AppConstants { + static let amazonHqMapPosition = (latitude: 47.61506909519956, longitude: -122.33826750882835) +} diff --git a/LocationServices/LocationServices/Constants/ViewsIdentifiers.swift b/LocationServices/LocationServices/Constants/ViewsIdentifiers.swift index 70b62be9..467c4cbd 100644 --- a/LocationServices/LocationServices/Constants/ViewsIdentifiers.swift +++ b/LocationServices/LocationServices/Constants/ViewsIdentifiers.swift @@ -29,6 +29,12 @@ struct ViewsIdentifiers { static let imageAnnotationView = "ImageAnnotationView" static let bottomGrabberView = "BottomGrabberView" static let sideBarTableView = "SideBarTableView" + static let mapStyleRow = "MapStyleRow" + static let politicalViewButton = "politicalViewButton" + static let politicalViewTable = "politicalViewTable" + static let politicalViewCell = "politicalViewCell" + static let politicalViewSubtitle = "politicalViewSubtitle" + static let politicalViewCloseButton = "politicalViewCloseButton" } struct Explore { @@ -59,7 +65,7 @@ struct ViewsIdentifiers { static let routeTypesContainer = "RouteTypesContainer" static let carContainer = "CarContainer" - static let walkContainer = "WalkContainer" + static let pedestrianContainer = "pedestrianContainer" static let truckContainer = "TruckContainer" static let routeEstimatedTime = "RouteEstimatedTime" diff --git a/LocationServices/LocationServices/Coordinators/ChildCoordinators/ExploreCoordinator.swift b/LocationServices/LocationServices/Coordinators/ChildCoordinators/ExploreCoordinator.swift index 8d87bdf2..f8e03d72 100644 --- a/LocationServices/LocationServices/Coordinators/ChildCoordinators/ExploreCoordinator.swift +++ b/LocationServices/LocationServices/Coordinators/ChildCoordinators/ExploreCoordinator.swift @@ -44,13 +44,13 @@ extension ExploreCoordinator: ExploreNavigationDelegate { } currentBottomSheet?.dismissBottomSheet() controller.presentBottomSheet(parentController: ExploreCoordinator.exploreController!) - controller.enableBottomSheetGrab() + controller.setBottomSheetHeight(to: controller.getLargeDetentHeight()) currentBottomSheet = controller } func showDirections(isRouteOptionEnabled: Bool?, - firstDestionation: MapModel?, - secondDestionation: MapModel?, + firstDestination: MapModel?, + secondDestination: MapModel?, lat: Double?, long: Double? ) { @@ -63,22 +63,22 @@ extension ExploreCoordinator: ExploreNavigationDelegate { NotificationCenter.default.post(name: Notification.Name("DirectionViewDismissed"), object: nil, userInfo: nil) NotificationCenter.default.post(name: Notification.Name("updateMapViewButtons"), object: nil, userInfo: nil) - guard let secondDestionation, firstDestionation == nil else { return } - let userInfo = ["place" : secondDestionation] + guard let secondDestination, firstDestination == nil else { return } + let userInfo = ["place" : secondDestination] NotificationCenter.default.post(name: Notification.selectedPlace, object: nil, userInfo: userInfo) } - if let firstDestionation { - controller.firstDestionation = DirectionTextFieldModel(placeName: firstDestionation.placeName ?? "", placeAddress: firstDestionation.placeAddress, lat: firstDestionation.placeLat, long: firstDestionation.placeLong) + if let firstDestination { + controller.firstDestination = DirectionTextFieldModel(placeName: firstDestination.placeName ?? "", placeAddress: firstDestination.placeAddress, lat: firstDestination.placeLat, long: firstDestination.placeLong) } // first location as my current location - if controller.firstDestionation == nil, let lat, let long { - controller.firstDestionation = DirectionTextFieldModel(placeName: "My Location", placeAddress: nil, lat: lat, long: long) + if controller.firstDestination == nil, let lat, let long { + controller.firstDestination = DirectionTextFieldModel(placeName: "My Location", placeAddress: nil, lat: lat, long: long) } - if let secondDestionation { - controller.secondDestionation = DirectionTextFieldModel(placeName: secondDestionation.placeName ?? "", placeAddress: secondDestionation.placeAddress, lat: secondDestionation.placeLat, long: secondDestionation.placeLong) + if let secondDestination { + controller.secondDestination = DirectionTextFieldModel(placeName: secondDestination.placeName ?? "", placeAddress: secondDestination.placeAddress, lat: secondDestination.placeLat, long: secondDestination.placeLong) } controller.userLocation = (lat, long) @@ -127,8 +127,8 @@ extension ExploreCoordinator: ExploreNavigationDelegate { } - func showNavigationview(steps: [NavigationSteps], summaryData: (totalDistance: Double, totalDuration: Double), firstDestionation: MapModel?, secondDestionation: MapModel?) { - let controller = NavigationBuilder.create(steps: steps, summaryData: summaryData, firstDestionation: firstDestionation, secondDestionation: secondDestionation) + func showNavigationview(routeLegDetails: [RouteLegDetails], summaryData: (totalDistance: Double, totalDuration: Double), firstDestination: MapModel?, secondDestination: MapModel?) { + let controller = NavigationBuilder.create(routeLegDetails: routeLegDetails, summaryData: summaryData, firstDestination: firstDestination, secondDestination: secondDestination) controller.delegate = self currentBottomSheet?.dismissBottomSheet() diff --git a/LocationServices/LocationServices/Coordinators/ChildCoordinators/GeofenceCoordinator.swift b/LocationServices/LocationServices/Coordinators/ChildCoordinators/GeofenceCoordinator.swift index 8c8c2896..b26f6110 100644 --- a/LocationServices/LocationServices/Coordinators/ChildCoordinators/GeofenceCoordinator.swift +++ b/LocationServices/LocationServices/Coordinators/ChildCoordinators/GeofenceCoordinator.swift @@ -70,7 +70,7 @@ extension GeofenceCoordinator: GeofenceNavigationDelegate { self.userLocation = (lat: lat, long: long) } let controller = GeofenceDashboardBuilder.create(lat: lat ?? self.userLocation.lat, long: long ?? self.userLocation.long, geofences: geofences) - + controller.delegate = self controller.addGeofence = { [weak self] parameters in self?.showAddGeofenceFlow(activeGeofencesLists: parameters.activeGeofences, isEditingSceneEnabled: parameters.isEditingSceneEnabled, @@ -116,7 +116,7 @@ extension GeofenceCoordinator: GeofenceNavigationDelegate { currentBottomSheet?.dismissBottomSheet() controller.presentBottomSheet(parentController: geofenceController!) - controller.enableBottomSheetGrab() + controller.setBottomSheetHeight(to: controller.getLargeDetentHeight()) currentBottomSheet = controller } diff --git a/LocationServices/LocationServices/Coordinators/ChildCoordinators/SettingsCoordinator.swift b/LocationServices/LocationServices/Coordinators/ChildCoordinators/SettingsCoordinator.swift index e2207efa..28d719d4 100644 --- a/LocationServices/LocationServices/Coordinators/ChildCoordinators/SettingsCoordinator.swift +++ b/LocationServices/LocationServices/Coordinators/ChildCoordinators/SettingsCoordinator.swift @@ -76,6 +76,9 @@ extension SettingsCoordinator: SettingsNavigationDelegate { private func showAwsCloudFormationscene() { let controller = LoginVCBuilder.create(from: true) + controller.dismissHandler = { [weak self] in + self?.navigationController.popViewController(animated: true) + } controller.isFromSettingScene = true navigationController.pushViewController(controller, animated: true) } diff --git a/LocationServices/LocationServices/Coordinators/ChildCoordinators/SplitView/SplitViewExploreMapCoordinator.swift b/LocationServices/LocationServices/Coordinators/ChildCoordinators/SplitView/SplitViewExploreMapCoordinator.swift index e04c2b30..4c05bf69 100644 --- a/LocationServices/LocationServices/Coordinators/ChildCoordinators/SplitView/SplitViewExploreMapCoordinator.swift +++ b/LocationServices/LocationServices/Coordinators/ChildCoordinators/SplitView/SplitViewExploreMapCoordinator.swift @@ -82,8 +82,8 @@ extension SplitViewExploreMapCoordinator: ExploreNavigationDelegate { } func showDirections(isRouteOptionEnabled: Bool?, - firstDestionation: MapModel?, - secondDestionation: MapModel?, + firstDestination: MapModel?, + secondDestination: MapModel?, lat: Double?, long: Double? ) { @@ -95,17 +95,17 @@ extension SplitViewExploreMapCoordinator: ExploreNavigationDelegate { self?.supplementaryNavigationController?.popViewController(animated: true) } - if let firstDestionation { - controller.firstDestionation = DirectionTextFieldModel(placeName: firstDestionation.placeName ?? "", placeAddress: firstDestionation.placeAddress, lat: firstDestionation.placeLat, long: firstDestionation.placeLong) + if let firstDestination { + controller.firstDestination = DirectionTextFieldModel(placeName: firstDestination.placeName ?? "", placeAddress: firstDestination.placeAddress, lat: firstDestination.placeLat, long: firstDestination.placeLong) } // first location as my current location - if controller.firstDestionation == nil, let lat, let long { - controller.firstDestionation = DirectionTextFieldModel(placeName: "My Location", placeAddress: nil, lat: lat, long: long) + if controller.firstDestination == nil, let lat, let long { + controller.firstDestination = DirectionTextFieldModel(placeName: "My Location", placeAddress: nil, lat: lat, long: long) } - if let secondDestionation { - controller.secondDestionation = DirectionTextFieldModel(placeName: secondDestionation.placeName ?? "", placeAddress: secondDestionation.placeAddress, lat: secondDestionation.placeLat, long: secondDestionation.placeLong) + if let secondDestination { + controller.secondDestination = DirectionTextFieldModel(placeName: secondDestination.placeName ?? "", placeAddress: secondDestination.placeAddress, lat: secondDestination.placeLat, long: secondDestination.placeLong) } controller.userLocation = (lat, long) @@ -143,8 +143,8 @@ extension SplitViewExploreMapCoordinator: ExploreNavigationDelegate { splitDelegate?.showSupplementary() } - func showNavigationview(steps: [NavigationSteps], summaryData: (totalDistance: Double, totalDuration: Double), firstDestionation: MapModel?, secondDestionation: MapModel?) { - let controller = NavigationBuilder.create(steps: steps, summaryData: summaryData, firstDestionation: firstDestionation, secondDestionation: secondDestionation) + func showNavigationview(routeLegDetails: [RouteLegDetails], summaryData: (totalDistance: Double, totalDuration: Double), firstDestination: MapModel?, secondDestination: MapModel?) { + let controller = NavigationBuilder.create(routeLegDetails: routeLegDetails, summaryData: summaryData, firstDestination: firstDestination, secondDestination: secondDestination) controller.delegate = self isSearchHidden = true diff --git a/LocationServices/LocationServices/Coordinators/ChildCoordinators/SplitView/SplitViewSettingsCoordinator.swift b/LocationServices/LocationServices/Coordinators/ChildCoordinators/SplitView/SplitViewSettingsCoordinator.swift index eee91333..154791fa 100644 --- a/LocationServices/LocationServices/Coordinators/ChildCoordinators/SplitView/SplitViewSettingsCoordinator.swift +++ b/LocationServices/LocationServices/Coordinators/ChildCoordinators/SplitView/SplitViewSettingsCoordinator.swift @@ -30,8 +30,9 @@ private extension SplitViewSettingsCoordinator { controller.delegate = self splitViewController.setViewController(controller, for: .supplementary) splitViewController.show(.supplementary) - showNextScene(type: .dataProvider) + showNextScene(type: .mapStyle) splitViewController.viewController(for: .secondary)?.navigationController?.navigationBar.isHidden = false + splitViewController.hide(.primary) } } diff --git a/LocationServices/LocationServices/Coordinators/ChildCoordinators/TabBarCoordinator/TabBarCoordinator.swift b/LocationServices/LocationServices/Coordinators/ChildCoordinators/TabBarCoordinator/TabBarCoordinator.swift index f05c2661..d251d70a 100644 --- a/LocationServices/LocationServices/Coordinators/ChildCoordinators/TabBarCoordinator/TabBarCoordinator.swift +++ b/LocationServices/LocationServices/Coordinators/ChildCoordinators/TabBarCoordinator/TabBarCoordinator.swift @@ -94,7 +94,7 @@ private extension TabBarCoordinator { let exploreCoordinator = ExploreCoordinator(navigationController: navigationController) exploreCoordinator.delegate = self self.childCoordinators.append(exploreCoordinator) - exploreCoordinator.showDirections(isRouteOptionEnabled: nil, firstDestionation: nil, secondDestionation: nil, lat: nil, long: nil) + exploreCoordinator.showDirections(isRouteOptionEnabled: nil, firstDestination: nil, secondDestination: nil, lat: nil, long: nil) } childCoordinators.append(exploreCoordinator) @@ -111,8 +111,8 @@ private extension TabBarCoordinator { let exploreCoordinator = ExploreCoordinator(navigationController: navigationController) exploreCoordinator.delegate = self self.childCoordinators.append(exploreCoordinator) - exploreCoordinator.showDirections(isRouteOptionEnabled: nil, firstDestionation: nil, - secondDestionation: nil, lat: nil, long: nil) + exploreCoordinator.showDirections(isRouteOptionEnabled: nil, firstDestination: nil, + secondDestination: nil, lat: nil, long: nil) } childCoordinators.append(exploreCoordinator) exploreCoordinator.start() diff --git a/LocationServices/LocationServices/Coordinators/ChildCoordinators/TrackingCoordinator.swift b/LocationServices/LocationServices/Coordinators/ChildCoordinators/TrackingCoordinator.swift index 8faef692..bf074bef 100644 --- a/LocationServices/LocationServices/Coordinators/ChildCoordinators/TrackingCoordinator.swift +++ b/LocationServices/LocationServices/Coordinators/ChildCoordinators/TrackingCoordinator.swift @@ -41,14 +41,10 @@ extension TrackingCoordinator: TrackingNavigationDelegate { func showDashboardFlow() { let controller = TrackingDashboardBuilder.create() - + controller.delegate = self controller.trackingHistoryHandler = { [weak self] in self?.showTrackingHistory(isTrackingActive: true) } - -// controller.closeHandler = { [weak self] in -// currentBottomSheet?.dismissBottomSheet() -// } currentBottomSheet?.dismissBottomSheet() controller.presentBottomSheet(parentController: trackingController!) controller.enableBottomSheetGrab(smallHeight: 0.48) @@ -79,7 +75,7 @@ extension TrackingCoordinator: TrackingNavigationDelegate { } currentBottomSheet?.dismissBottomSheet() controller.presentBottomSheet(parentController: trackingController!) - controller.enableBottomSheetGrab() + controller.setBottomSheetHeight(to: controller.getLargeDetentHeight()) currentBottomSheet = controller } diff --git a/LocationServices/LocationServices/Extensions/Notificaton+Extension.swift b/LocationServices/LocationServices/Extensions/Notificaton+Extension.swift index b65dd2b9..a8e47411 100644 --- a/LocationServices/LocationServices/Extensions/Notificaton+Extension.swift +++ b/LocationServices/LocationServices/Extensions/Notificaton+Extension.swift @@ -30,6 +30,7 @@ extension Notification { static let wasResetToDefaultConfig = Notification.Name("WasResetToDefaultConfig") static let tabSelected = Notification.Name("TabSelected") static let grantedLocationPermissions = Notification.Name("GrantedLocationPermissions") + static let validateMapColor = Notification.Name("ValidateMapColor") static let searchAppearanceChanged = Notification.Name("SearchAppearanceChanged") static let trackingAppearanceChanged = Notification.Name("TrackingAppearanceChanged") diff --git a/LocationServices/LocationServices/Extensions/AWSLocationTravelMode+Extension.swift b/LocationServices/LocationServices/Extensions/RouteTravelMode+Extension.swift similarity index 74% rename from LocationServices/LocationServices/Extensions/AWSLocationTravelMode+Extension.swift rename to LocationServices/LocationServices/Extensions/RouteTravelMode+Extension.swift index 3a61d1f1..d5bd5e82 100644 --- a/LocationServices/LocationServices/Extensions/AWSLocationTravelMode+Extension.swift +++ b/LocationServices/LocationServices/Extensions/RouteTravelMode+Extension.swift @@ -6,13 +6,13 @@ // SPDX-License-Identifier: MIT-0 import Foundation -import AWSLocation +import AWSGeoRoutes -extension LocationClientTypes.TravelMode { +extension GeoRoutesClientTypes.RouteTravelMode { init?(routeType: RouteTypes) { switch routeType { - case .walking: - self = .walking + case .pedestrian: + self = .pedestrian case .car: self = .car case .truck: diff --git a/LocationServices/LocationServices/Extensions/String+Extensions.swift b/LocationServices/LocationServices/Extensions/String+Extensions.swift index 99cc3364..21133d0b 100755 --- a/LocationServices/LocationServices/Extensions/String+Extensions.swift +++ b/LocationServices/LocationServices/Extensions/String+Extensions.swift @@ -63,7 +63,7 @@ extension String { func isCoordinate() -> Bool { let regularExpression = #""" -^(-?\d+(\.\d+)?),\s*(-?\d+(\.\d+)?)$ +^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$ """# guard let regex = try? NSRegularExpression(pattern: regularExpression) else { return false diff --git a/LocationServices/LocationServices/Extensions/UIViewController+Extension.swift b/LocationServices/LocationServices/Extensions/UIViewController+Extension.swift index e33d8e74..d5ed931e 100644 --- a/LocationServices/LocationServices/Extensions/UIViewController+Extension.swift +++ b/LocationServices/LocationServices/Extensions/UIViewController+Extension.swift @@ -20,28 +20,6 @@ extension UIViewController { view.endEditing(true) } - func blurStatusBar(includeAdditionalSafeArea: Bool = false) { - guard view.viewWithTag(Self.statusBarBlurViewTag) == nil else { return } - - let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) - blurView.tag = Self.statusBarBlurViewTag - view.addSubview(blurView) - - var offset: CGFloat = 0 - - if let navigationController, !navigationController.navigationBar.isHidden { - offset += navigationController.navigationBar.frame.height - } - if !includeAdditionalSafeArea { - offset += navigationController?.additionalSafeAreaInsets.top ?? 0 - } - - blurView.snp.makeConstraints { - $0.top.trailing.leading.equalToSuperview() - $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.top).offset(-offset) - } - } - @objc func keyboardWillShow(notification: NSNotification) { // Handle keyboard show event self.updateBottomSheetHeight(to: getLargeDetentHeight()) @@ -138,7 +116,7 @@ extension UIViewController { self.view.addGestureRecognizer(panGestureRecognizer) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:))) - grabberView.addGestureRecognizer(tapGestureRecognizer) + grabberView.addGestureRecognizer(tapGestureRecognizer) NotificationCenter.default.post(name: Notification.updateMapLayerItems, object:nil, userInfo: ["height": height+8]) } diff --git a/LocationServices/LocationServices/Helpers/AWSSignerV4.swift b/LocationServices/LocationServices/Helpers/AWSSignerV4.swift deleted file mode 100644 index cecde569..00000000 --- a/LocationServices/LocationServices/Helpers/AWSSignerV4.swift +++ /dev/null @@ -1,557 +0,0 @@ -// AWSSignerV4.swift - -import CryptoKit -import Foundation - -public struct TimeAmount: Hashable { - @available(*, deprecated, message: "This typealias doesn't serve any purpose. Please use Int64 directly.") - public typealias Value = Int64 - - /// The nanoseconds representation of the `TimeAmount`. - public let nanoseconds: Int64 - - //@inlinable - init(_ nanoseconds: Int64) { - self.nanoseconds = nanoseconds - } - - /// Creates a new `TimeAmount` for the given amount of nanoseconds. - /// - /// - parameters: - /// - amount: the amount of nanoseconds this `TimeAmount` represents. - /// - returns: the `TimeAmount` for the given amount. - //@inlinable - public static func nanoseconds(_ amount: Int64) -> TimeAmount { - return TimeAmount(amount) - } - - /// Creates a new `TimeAmount` for the given amount of microseconds. - /// - /// - parameters: - /// - amount: the amount of microseconds this `TimeAmount` represents. - /// - returns: the `TimeAmount` for the given amount. - /// - /// - note: returns `TimeAmount(.max)` if the amount overflows when converted to nanoseconds and `TimeAmount(.min)` if it underflows. - //@inlinable - public static func microseconds(_ amount: Int64) -> TimeAmount { - return TimeAmount(_cappedNanoseconds(amount: amount, multiplier: 1000)) - } - - /// Creates a new `TimeAmount` for the given amount of milliseconds. - /// - /// - parameters: - /// - amount: the amount of milliseconds this `TimeAmount` represents. - /// - returns: the `TimeAmount` for the given amount. - /// - /// - note: returns `TimeAmount(.max)` if the amount overflows when converted to nanoseconds and `TimeAmount(.min)` if it underflows. - //@inlinable - public static func milliseconds(_ amount: Int64) -> TimeAmount { - return TimeAmount(_cappedNanoseconds(amount: amount, multiplier: 1000 * 1000)) - } - - /// Creates a new `TimeAmount` for the given amount of seconds. - /// - /// - parameters: - /// - amount: the amount of seconds this `TimeAmount` represents. - /// - returns: the `TimeAmount` for the given amount. - /// - /// - note: returns `TimeAmount(.max)` if the amount overflows when converted to nanoseconds and `TimeAmount(.min)` if it underflows. - //@inlinable - public static func seconds(_ amount: Int64) -> TimeAmount { - return TimeAmount(_cappedNanoseconds(amount: amount, multiplier: 1000 * 1000 * 1000)) - } - - /// Creates a new `TimeAmount` for the given amount of minutes. - /// - /// - parameters: - /// - amount: the amount of minutes this `TimeAmount` represents. - /// - returns: the `TimeAmount` for the given amount. - /// - /// - note: returns `TimeAmount(.max)` if the amount overflows when converted to nanoseconds and `TimeAmount(.min)` if it underflows. - //@inlinable - public static func minutes(_ amount: Int64) -> TimeAmount { - return TimeAmount(_cappedNanoseconds(amount: amount, multiplier: 1000 * 1000 * 1000 * 60)) - } - - /// Creates a new `TimeAmount` for the given amount of hours. - /// - /// - parameters: - /// - amount: the amount of hours this `TimeAmount` represents. - /// - returns: the `TimeAmount` for the given amount. - /// - /// - note: returns `TimeAmount(.max)` if the amount overflows when converted to nanoseconds and `TimeAmount(.min)` if it underflows. - //@inlinable - public static func hours(_ amount: Int64) -> TimeAmount { - return TimeAmount(_cappedNanoseconds(amount: amount, multiplier: 1000 * 1000 * 1000 * 60 * 60)) - } - - /// Converts `amount` to nanoseconds multiplying it by `multiplier`. The return value is capped to `Int64.max` if the multiplication overflows and `Int64.min` if it underflows. - /// - /// - parameters: - /// - amount: the amount to be converted to nanoseconds. - /// - multiplier: the multiplier that converts the given amount to nanoseconds. - /// - returns: the amount converted to nanoseconds within [Int64.min, Int64.max]. - //@inlinable - static func _cappedNanoseconds(amount: Int64, multiplier: Int64) -> Int64 { - let nanosecondsMultiplication = amount.multipliedReportingOverflow(by: multiplier) - if nanosecondsMultiplication.overflow { - return amount >= 0 ? .max : .min - } else { - return nanosecondsMultiplication.partialValue - } - } -} - -/// Amazon request V4 Signer (This signer is for external aws signing such as Maps cognito signing) -public struct AWSSignerV4 { - /// Security credentials for accessing AWS services - public let credentials: CognitoCredentials - /// Service signing name. In general this is the same as the service name - public let serviceName: String - /// AWS region you are working in - public let region: String - - static let hashedEmptyBody = SHA256.hash(data: [UInt8]()).hexDigest() - - private static let timeStampDateFormatter: DateFormatter = createTimeStampDateFormatter() - - /// Initialise the Signer class with AWS credentials - public init(credentials: CognitoCredentials, serviceName: String, region: String) { - self.credentials = credentials - self.serviceName = serviceName - self.region = region - } - - /// Enum for holding request payload - public enum BodyData { - /// String - case string(String) - /// Data - case data(Data) - /// Don't use body when signing request - case unsignedPayload - /// Internally used when S3 streamed payloads - case s3chunked - } - - - - /// Process URL before signing - /// - /// `signURL` and `signHeaders` make assumptions about the URLs they are provided, this function cleans up a URL so it is ready - /// to be signed by either of these functions. It sorts the query params and ensures they are properly percent encoded - public func processURL(url: URL) -> URL? { - guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil } - let urlQueryString = urlComponents.queryItems? - .sorted { - if $0.name < $1.name { return true } - if $0.name > $1.name { return false } - guard let value1 = $0.value, let value2 = $1.value else { return false } - return value1 < value2 - } - .map { item in item.value.map { "\(item.name)=\($0.uriEncode())" } ?? "\(item.name)=" } - .joined(separator: "&") - urlComponents.percentEncodedQuery = urlQueryString - // S3 requires "+" encoded in the URL - if self.serviceName == "s3" { - urlComponents.percentEncodedPath = urlComponents.path.s3PathEncode() - } - return urlComponents.url - } - - /// Generate signed headers, for a HTTP request - /// - Parameters: - /// - url: Request URL - /// - method: Request HTTP method - /// - headers: Request headers - /// - body: Request body - /// - omitSecurityToken: Should we include security token in the query parameters - /// - date: Date that URL is valid from, defaults to now - /// - Returns: Request headers with added "authorization" header that contains request signature - public func signHeaders( - url: URL, - method: HTTPMethod = .GET, - headers: HTTPHeaders = HTTPHeaders(), - body: BodyData? = nil, - omitSecurityToken: Bool = false, - date: Date = Date() - ) -> HTTPHeaders { - let bodyHash = AWSSignerV4.hashedPayload(body) - let dateString = AWSSignerV4.timestamp(date) - var headers = headers - // add date, host, sha256 and if available security token headers - headers.add(name: "host", value: Self.hostname(from: url)) - headers.add(name: "x-amz-date", value: dateString) - headers.add(name: "x-amz-content-sha256", value: bodyHash) - if !omitSecurityToken, let sessionToken = credentials.sessionToken { - headers.add(name: "x-amz-security-token", value: sessionToken) - } - // construct signing data. Do this after adding the headers as it uses data from the headers - let signingData = AWSSignerV4.SigningData(url: url, method: method, headers: headers, body: body, bodyHash: bodyHash, date: dateString, signer: self) - - // construct authorization string - let authorization = "AWS4-HMAC-SHA256 " + - "Credential=\(credentials.accessKeyId)/\(signingData.date)/\(self.region)/\(self.serviceName)/aws4_request," + - "SignedHeaders=\(signingData.signedHeaders)," + - "Signature=\(self.signature(signingData: signingData))" - - // add Authorization header - headers.add(name: "authorization", value: authorization) - // now we have signed the request we can add the security token if required - if omitSecurityToken, let sessionToken = credentials.sessionToken { - headers.add(name: "x-amz-security-token", value: sessionToken) - } - - return headers - } - - /// Generate a signed URL, for a HTTP request - /// - Parameters: - /// - url: Request URL - /// - method: Request HTTP method - /// - headers: Request headers - /// - body: Request body - /// - expires: How long before the signed URL expires - /// - omitSecurityToken: Should we include security token in the query parameters - /// - date: Date that URL is valid from, defaults to now - /// - Returns: Signed URL - public func signURL( - url: URL, - method: HTTPMethod = .GET, - headers: HTTPHeaders = HTTPHeaders(), - body: BodyData? = nil, - expires: TimeAmount, - omitSecurityToken: Bool = false, - date: Date = Date() - ) -> URL { - var headers = headers - headers.add(name: "host", value: Self.hostname(from: url)) - // Create signing data - var signingData = AWSSignerV4.SigningData(url: url, method: method, headers: headers, body: body, date: AWSSignerV4.timestamp(date), signer: self) - // Construct query string. Start with original query strings and append all the signing info. - var query = url.query ?? "" - if query.count > 0 { - query += "&" - } - query += "X-Amz-Algorithm=AWS4-HMAC-SHA256" - query += "&X-Amz-Credential=\(self.credentials.accessKeyId)/\(signingData.date)/\(self.region)/\(self.serviceName)/aws4_request" - query += "&X-Amz-Date=\(signingData.datetime)" - query += "&X-Amz-Expires=\(expires.nanoseconds / 1_000_000_000)" - query += "&X-Amz-SignedHeaders=\(signingData.signedHeaders)" - if !omitSecurityToken, let sessionToken = credentials.sessionToken { - query += "&X-Amz-Security-Token=\(sessionToken.uriEncode())" - } - // Split the string and sort to ensure the order of query strings is the same as AWS - query = query.split(separator: "&") - .sorted() - .joined(separator: "&") - .queryEncode() - - // update unsignedURL in the signingData so when the canonical request is constructed it includes all the signing query items - signingData.unsignedURL = URL(string: url.absoluteString.split(separator: "?")[0] + "?" + query)! // NEED TO DEAL WITH SITUATION WHERE THIS FAILS - query += "&X-Amz-Signature=\(self.signature(signingData: signingData))" - if omitSecurityToken, let sessionToken = credentials.sessionToken { - query += "&X-Amz-Security-Token=\(sessionToken.uriEncode())" - } - - // Add signature to query items and build a new Request - let signedURL = URL(string: url.absoluteString.split(separator: "?")[0] + "?" + query)! - - return signedURL - } - - /// Temporary structure passed from calls to `startSigningChunks` and - /// subsequent calls to `signChunk` - public struct ChunkedSigningData { - /// signature for streamed data - public let signature: String - let datetime: String - let signingKey: SymmetricKey - } - - /// Start the process of signing a s3 chunked upload. - /// - /// Update headers and generate first signature. See https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html - /// for more details - /// - Parameters: - /// - url: url - /// - method: http method - /// - headers: original headers - /// - date: date to use for signing - /// - Returns: Tuple of updated headers and signing data to use in first call to `signChunk` - public func startSigningChunks(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), date: Date = Date()) -> (headers: HTTPHeaders, signingData: ChunkedSigningData) { - let bodyHash = AWSSignerV4.hashedPayload(.s3chunked) - let dateString = AWSSignerV4.timestamp(date) - var headers = headers - // add date, host, sha256 and if available security token headers - headers.add(name: "host", value: Self.hostname(from: url)) - headers.add(name: "x-amz-date", value: dateString) - headers.add(name: "x-amz-content-sha256", value: bodyHash) - if let sessionToken = credentials.sessionToken { - headers.add(name: "x-amz-security-token", value: sessionToken) - } - // remove content-length header - headers.remove(name: "content-length") - - // construct signing data. Do this after adding the headers as it uses data from the headers - let signingData = AWSSignerV4.SigningData(url: url, method: method, headers: headers, bodyHash: bodyHash, date: dateString, signer: self) - let signingKey = self.signingKey(date: signingData.date) - let signature = self.signature(signingData: signingData) - let chunkedSigningData = ChunkedSigningData(signature: signature, datetime: signingData.datetime, signingKey: signingKey) - - // construct authorization string - let authorization = "AWS4-HMAC-SHA256 " + - "Credential=\(credentials.accessKeyId)/\(signingData.date)/\(self.region)/\(self.serviceName)/aws4_request," + - "SignedHeaders=\(signingData.signedHeaders)," + - "Signature=\(signature)" - - // add Authorization header - headers.add(name: "authorization", value: authorization) - - return (headers: headers, signingData: chunkedSigningData) - } - - /// Generate the signature for a chunk in a s3 chunked upload - /// - Parameters: - /// - body: Body of chunk - /// - signingData: Signing data returned from previous `signChunk` or `startSigningChunk` if this is the first call - /// - Returns: signing data that includes the signature and other data that is required for signing the next chunk - public func signChunk(body: BodyData, signingData: ChunkedSigningData) -> ChunkedSigningData { - let stringToSign = self.chunkStringToSign(body: body, previousSignature: signingData.signature, datetime: signingData.datetime) - let signature = HMAC.authenticationCode(for: [UInt8](stringToSign.utf8), using: signingData.signingKey).hexDigest() - return ChunkedSigningData(signature: signature, datetime: signingData.datetime, signingKey: signingData.signingKey) - } - - /// structure used to store data used throughout the signing process - struct SigningData { - let url: URL - let method: HTTPMethod - let hashedPayload: String - let datetime: String - let headersToSign: [String: String] - let signedHeaders: String - var unsignedURL: URL - - var date: String { return String(self.datetime.prefix(8)) } - - init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: BodyData? = nil, bodyHash: String? = nil, date: String, signer: AWSSignerV4) { - self.url = url - self.method = method - self.datetime = date - self.unsignedURL = self.url - - if let hash = bodyHash { - self.hashedPayload = hash - } else if signer.serviceName == "s3" { - self.hashedPayload = "UNSIGNED-PAYLOAD" - } else { - self.hashedPayload = AWSSignerV4.hashedPayload(body) - } - - // from S3 V4 signed documentation - // The CanonicalHeaders list must include the following: - // - HTTP host header. - // If the Content-Type header is present in the request, you must add it to the CanonicalHeaders list. - // Any x-amz-* headers that you plan to include in your request must also be added. For example, if - // you are using temporary security credentials, you need to include x-amz-security-token in your request. - // You must add this header in the list of CanonicalHeaders. - let headersNotToSign: Set = [ - "authorization", - "content-length", - "expect", - "user-agent", - ] - var headersToSign: [String: String] = [:] - var signedHeadersArray: [String] = [] - for header in headers.allHeaders() { - let lowercasedHeaderName = header.key.lowercased() - if headersNotToSign.contains(lowercasedHeaderName) { - continue - } - headersToSign[lowercasedHeaderName] = header.value - signedHeadersArray.append(lowercasedHeaderName) - } - self.headersToSign = headersToSign - self.signedHeaders = signedHeadersArray.sorted().joined(separator: ";") - } - } - - // Stage 3 Calculating signature as in https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html - func signature(signingData: SigningData) -> String { - let signingKey = self.signingKey(date: signingData.date) - let kSignature = HMAC.authenticationCode(for: [UInt8](self.stringToSign(signingData: signingData).utf8), using: signingKey) - return kSignature.hexDigest() - } - - /// Stage 2 Create the string to sign as in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html - func stringToSign(signingData: SigningData) -> String { - let stringToSign = "AWS4-HMAC-SHA256\n" + - "\(signingData.datetime)\n" + - "\(signingData.date)/\(self.region)/\(self.serviceName)/aws4_request\n" + - SHA256.hash(data: [UInt8](self.canonicalRequest(signingData: signingData).utf8)).hexDigest() - return stringToSign - } - - /// Stage 1 Create the canonical request as in https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html - func canonicalRequest(signingData: SigningData) -> String { - let canonicalHeaders = signingData.headersToSign - .map { (key: $0.key.lowercased(), value: $0.value) } - .sorted { $0.key < $1.key } - .map { return "\($0.key):\($0.value.trimmingCharacters(in: CharacterSet.whitespaces).removeSequentialWhitespace())" } - .joined(separator: "\n") - let canonicalPath: String - let urlComps = URLComponents(url: signingData.unsignedURL, resolvingAgainstBaseURL: false)! - if self.serviceName == "s3" { - canonicalPath = urlComps.path.uriEncodeWithSlash() - } else { - // non S3 paths need to be encoded twice - canonicalPath = urlComps.percentEncodedPath.uriEncodeWithSlash() - } - let canonicalRequest = "\(signingData.method.rawValue)\n" + - "\(canonicalPath)\n" + - "\(signingData.unsignedURL.query ?? "")\n" + // assuming query parameters have are already percent encoded correctly - "\(canonicalHeaders)\n\n" + - "\(signingData.signedHeaders)\n" + - signingData.hashedPayload - return canonicalRequest - } - - /// get signing key - func signingKey(date: String) -> SymmetricKey { - let kDate = HMAC.authenticationCode(for: [UInt8](date.utf8), using: SymmetricKey(data: Array("AWS4\(self.credentials.secretKey)".utf8))) - let kRegion = HMAC.authenticationCode(for: [UInt8](self.region.utf8), using: SymmetricKey(data: kDate)) - let kService = HMAC.authenticationCode(for: [UInt8](self.serviceName.utf8), using: SymmetricKey(data: kRegion)) - let kSigning = HMAC.authenticationCode(for: [UInt8]("aws4_request".utf8), using: SymmetricKey(data: kService)) - return SymmetricKey(data: kSigning) - } - - /// chunked upload string to sign - func chunkStringToSign(body: BodyData, previousSignature: String, datetime: String) -> String { - let date = String(datetime.prefix(8)) - let stringToSign = "AWS4-HMAC-SHA256-PAYLOAD\n" + - "\(datetime)\n" + - "\(date)/\(region)/\(serviceName)/aws4_request\n" + - "\(previousSignature)\n" + - "\(Self.hashedEmptyBody)\n" + - Self.hashedPayload(body) - return stringToSign - } - - /// Create a SHA256 hash of the Requests body - static func hashedPayload(_ payload: BodyData?) -> String { - guard let payload else { return self.hashedEmptyBody } - let hash: String? - switch payload { - case .string(let string): - hash = SHA256.hash(data: [UInt8](string.utf8)).hexDigest() - case .data(let data): - hash = SHA256.hash(data: data).hexDigest() - case .unsignedPayload: - return "UNSIGNED-PAYLOAD" - case .s3chunked: - return "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" - } - if let hash { - return hash - } else { - return self.hashedEmptyBody - } - } - - /// create timestamp dateformatter - private static func createTimeStampDateFormatter() -> DateFormatter { - let formatter = DateFormatter() - formatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" - formatter.timeZone = TimeZone(abbreviation: "UTC") - formatter.locale = Locale(identifier: "en_US_POSIX") - return formatter - } - - /// return a timestamp formatted for signing requests - static func timestamp(_ date: Date) -> String { - return self.timeStampDateFormatter.string(from: date) - } - - /// returns port from URL. If port is set to 80 on an http url or 443 on an https url nil is returned - private static func port(from url: URL) -> Int? { - guard let port = url.port else { return nil } - guard url.scheme != "http" || port != 80 else { return nil } - guard url.scheme != "https" || port != 443 else { return nil } - return port - } - - private static func hostname(from url: URL) -> String { - "\(url.host ?? "")\(self.port(from: url).map { ":\($0)" } ?? "")" - } -} - -@_spi(AmazonLocationiOSAuthSDKInternal) -extension String { - func queryEncode() -> String { - return addingPercentEncoding(withAllowedCharacters: String.queryAllowedCharacters) ?? self - } - - func s3PathEncode() -> String { - return addingPercentEncoding(withAllowedCharacters: String.s3PathAllowedCharacters) ?? self - } - - func uriEncode() -> String { - return addingPercentEncoding(withAllowedCharacters: String.uriAllowedCharacters) ?? self - } - - func uriEncodeWithSlash() -> String { - return addingPercentEncoding(withAllowedCharacters: String.uriAllowedWithSlashCharacters) ?? self - } - - static let s3PathAllowedCharacters = CharacterSet.urlPathAllowed.subtracting(.init(charactersIn: "+@()&$=:,'!*")) - static let uriAllowedWithSlashCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/") - static let uriAllowedCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~") - static let queryAllowedCharacters = CharacterSet(charactersIn: "/;+").inverted -} - -@_spi(AmazonLocationiOSAuthSDKInternal) -public extension Sequence { - /// return a hexEncoded string buffer from an array of bytes - func hexDigest() -> String { - return self.map { String(format: "%02x", $0) }.joined(separator: "") - } -} - -@_spi(AmazonLocationiOSAuthSDKInternal) -public extension URL { - /// return URL path, but do not remove the slash at the end if it exists. - /// - /// There doesn't seem to be anyway to do this without parsing the path myself - /// If I could guarantee macOS 10.11 then I could use `hasDirectoryPath`. - var pathWithSlash: String { - let relativeString = self.relativeString - let doesPathEndInSlash: Bool - // does path end in "/" - if let questionMark = relativeString.firstIndex(of: "?") { - let prevCharacter = relativeString.index(before: questionMark) - doesPathEndInSlash = (relativeString[prevCharacter] == "/") - } else if let hashCharacter = relativeString.firstIndex(of: "#") { - let prevCharacter = relativeString.index(before: hashCharacter) - doesPathEndInSlash = (relativeString[prevCharacter] == "/") - } else { - let prevCharacter = relativeString.index(before: relativeString.endIndex) - doesPathEndInSlash = (relativeString[prevCharacter] == "/") - } - var path = self.path - if doesPathEndInSlash, path != "/" { - path += "/" - } - return path - } -} - -@_spi(AmazonLocationiOSAuthSDKInternal) -private extension String { - func removeSequentialWhitespace() -> String { - return reduce(into: "") { result, character in - if result.last?.isWhitespace != true || character.isWhitespace == false { - result.append(character) - } - } - } -} diff --git a/LocationServices/LocationServices/Helpers/AmazonLocationApiCredentialsProvider.swift b/LocationServices/LocationServices/Helpers/AmazonLocationApiCredentialsProvider.swift deleted file mode 100644 index bf2226d8..00000000 --- a/LocationServices/LocationServices/Helpers/AmazonLocationApiCredentialsProvider.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -public class AmazonLocationApiCredentialsProvider: NSObject { - internal var apiKey: String? - internal var region: String? - - public init(apiKey: String, region: String?) { - self.apiKey = apiKey - self.region = region - } -} diff --git a/LocationServices/LocationServices/Helpers/AmazonLocationClient.swift b/LocationServices/LocationServices/Helpers/AmazonLocationClient.swift deleted file mode 100644 index 9dfebb91..00000000 --- a/LocationServices/LocationServices/Helpers/AmazonLocationClient.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// AmazonLocationClient.swift -// LocationServices -// -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 - -import Foundation -import AWSLocation -import SmithyIdentity -import SmithyIdentityAPI -import AWSSDKIdentity - -public enum HTTPMethod: String { - case GET - case POST - case PUT - case DELETE -} - -public struct HTTPHeaders { - private var headers: [String: String] - - public init() { - headers = [:] - } - - mutating func add(name: String, value: String) { - headers[name] = value - } - - mutating func remove(name: String) { - headers.removeValue(forKey: name) - } - - func value(forName name: String) -> String? { - return headers[name] - } - - func allHeaders() -> [String: String] { - return headers - } -} - -public class AmazonLocationClient { - public let locationProvider: LocationCredentialsProvider - public var locationClient: LocationClient? - private var credentials: CognitoCredentials? - public init(locationCredentialsProvider: LocationCredentialsProvider) { - self.locationProvider = locationCredentialsProvider - } - - public func initialiseLocationClient() async throws { - if let credentials = locationProvider.getCognitoProvider()?.getCognitoCredentials() { - self.credentials = credentials - try await setLocationClient(accessKey: credentials.accessKeyId, secret: credentials.secretKey, expiration: credentials.expiration, sessionToken: credentials.sessionToken) - } - } - - public func setLocationClient(accessKey: String, secret: String, expiration: Date?, sessionToken: String?) async throws { - let identity = AWSCredentialIdentity(accessKey: accessKey, secret: secret, expiration: expiration, sessionToken: sessionToken) - let resolver = try StaticAWSCredentialIdentityResolver(identity) - let cachedResolver: CachedAWSCredentialIdentityResolver? = try CachedAWSCredentialIdentityResolver(source: resolver, refreshTime: 3540) - let clientConfig = try await LocationClient.LocationClientConfiguration(awsCredentialIdentityResolver: cachedResolver, region: locationProvider.getRegion(), signingRegion: locationProvider.getRegion()) - - self.locationClient = LocationClient(config: clientConfig) - - } -} - -public extension AmazonLocationClient { - static func defaultCognito() async throws -> AmazonLocationClient? { - if let credentialsExpiration = CognitoAuthHelper.default().amazonLocationClient?.credentials?.expiration, credentialsExpiration < Date() { - try await CognitoAuthHelper.default().amazonLocationClient?.initialiseLocationClient() - } - return CognitoAuthHelper.default().amazonLocationClient - } - - static func defaultApi() -> AmazonLocationClient? { - return ApiAuthHelper.default().amazonLocationClient - } -} diff --git a/LocationServices/LocationServices/Helpers/AmazonLocationCognitoCredentialsProvider.swift b/LocationServices/LocationServices/Helpers/AmazonLocationCognitoCredentialsProvider.swift deleted file mode 100644 index f7ff0e90..00000000 --- a/LocationServices/LocationServices/Helpers/AmazonLocationCognitoCredentialsProvider.swift +++ /dev/null @@ -1,67 +0,0 @@ -import Foundation - -public class AmazonLocationCognitoCredentialsProvider: LocationCredentialsProtocol { - internal var identityPoolId: String? - internal var region: String? - private var cognitoCredentials: CognitoCredentials? - private var refreshTimer: Timer? - - public init(identityPoolId: String, region: String?) { - self.identityPoolId = identityPoolId - self.region = region - - // Start a background timer to refresh credentials every 59 minutes - startCredentialRefreshTimer() - } - - deinit { - // Invalidate the timer when the instance is deallocated - refreshTimer?.invalidate() - } - - public func getCognitoCredentials() -> CognitoCredentials? { - if self.cognitoCredentials != nil && self.cognitoCredentials!.expiration! > Date() { - return self.cognitoCredentials - } - else if let cognitoCredentialsString = KeyChainHelper.get(key: .cognitoCredentials), let cognitoCredentials = CognitoCredentials.decodeCognitoCredentials(jsonString: cognitoCredentialsString) { - self.cognitoCredentials = cognitoCredentials - return self.cognitoCredentials - } - return self.cognitoCredentials - } - - public func refreshCognitoCredentialsIfExpired() async throws { - if let savedCredentials = getCognitoCredentials(), savedCredentials.expiration! > Date() { - cognitoCredentials = savedCredentials - } else { - try? await refreshCognitoCredentials() - } - } - - public func refreshCognitoCredentials() async throws { - if let identityPoolId = self.identityPoolId, let region = self.region, let cognitoCredentials = try await CognitoCredentialsProvider.generateCognitoCredentials(identityPoolId: identityPoolId, region: region) { - setCognitoCredentials(cognitoCredentials: cognitoCredentials) - } - } - - private func setCognitoCredentials(cognitoCredentials: CognitoCredentials) { - self.cognitoCredentials = cognitoCredentials - KeyChainHelper.save(value: CognitoCredentials.encodeCognitoCredentials(credential: cognitoCredentials)!, key: .cognitoCredentials) - } - - // Start a repeating timer that calls refreshCognitoCredentialsIfExpired every 59 minutes - private func startCredentialRefreshTimer() { - refreshTimer = Timer.scheduledTimer(withTimeInterval: 59 * 60, repeats: true) { [weak self] _ in - Task { - do { - try await self?.refreshCognitoCredentialsIfExpired() - try await AWSLoginService.default().refreshLoginIfExpired() - - } catch { - print("Error refreshing Cognito credentials: \(error)") - } - } - } - } - -} diff --git a/LocationServices/LocationServices/Helpers/ApiAuthHelper.swift b/LocationServices/LocationServices/Helpers/ApiAuthHelper.swift deleted file mode 100644 index fcc2f6ad..00000000 --- a/LocationServices/LocationServices/Helpers/ApiAuthHelper.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation -import AWSLocation - -public class ApiAuthHelper { - - private static var _sharedInstance: ApiAuthHelper? - var locationCredentialsProvider: LocationCredentialsProvider? - var amazonLocationClient: AmazonLocationClient? - var authHelper: AuthHelper? - - static func initialize(apiKey: String, region: String) { - if _sharedInstance == nil { - _sharedInstance = ApiAuthHelper() - _sharedInstance?.authHelper = AuthHelper() - _sharedInstance?.locationCredentialsProvider = _sharedInstance?.authHelper?.authenticateWithApiKey(apiKey: apiKey, region: region) - } - } - - static func `default`() -> ApiAuthHelper { - return _sharedInstance! - } -} diff --git a/LocationServices/LocationServices/Helpers/AuthHelper.swift b/LocationServices/LocationServices/Helpers/AuthHelper.swift deleted file mode 100644 index 039b3d90..00000000 --- a/LocationServices/LocationServices/Helpers/AuthHelper.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// AuthHelper.swift -// LocationServices -// -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 - -import Foundation -import AwsCommonRuntimeKit - -public class AuthHelper { - - private var locationCredentialsProvider: LocationCredentialsProvider? - private var amazonLocationClient: AmazonLocationClient? - - public init() { - } - - public func authenticateWithCognitoIdentityPool(identityPoolId: String) async throws -> LocationCredentialsProvider? { - let region = identityPoolId.toRegionString() - locationCredentialsProvider = try? await authenticateWithCognitoIdentityPoolAndRegion(identityPoolId: identityPoolId, region: region) - return locationCredentialsProvider - } - - public func authenticateWithCognitoIdentityPool(identityPoolId: String, region: String) async throws -> LocationCredentialsProvider? { - locationCredentialsProvider = try? await authenticateWithCognitoIdentityPoolAndRegion(identityPoolId: identityPoolId, region: region) - return locationCredentialsProvider - } - - private func authenticateWithCognitoIdentityPoolAndRegion(identityPoolId: String, region: String) async throws -> LocationCredentialsProvider? { - let credentialProvider = LocationCredentialsProvider(region: region, identityPoolId: identityPoolId) - credentialProvider.setRegion(region: region) - try await credentialProvider.getCognitoProvider()?.refreshCognitoCredentialsIfExpired() - amazonLocationClient = AmazonLocationClient(locationCredentialsProvider: credentialProvider) - return credentialProvider - } - - public func authenticateWithApiKey(apiKey: String, region: String) -> LocationCredentialsProvider { - let credentialProvider = LocationCredentialsProvider(region: region, apiKey: apiKey) - credentialProvider.setAPIKey(apiKey: apiKey) - credentialProvider.setRegion(region: region) - locationCredentialsProvider = credentialProvider - amazonLocationClient = AmazonLocationClient(locationCredentialsProvider: credentialProvider) - return credentialProvider - } - - public func authenticateWithCredentialsProvider(credentialsProvider: CredentialsProvider, region: String) async throws -> LocationCredentialsProvider? { - let credentialProvider = LocationCredentialsProvider(credentialsProvider: credentialsProvider) - credentialProvider.setRegion(region: region) - locationCredentialsProvider = credentialProvider - amazonLocationClient = AmazonLocationClient(locationCredentialsProvider: credentialProvider) - return credentialProvider - } - - public func getLocationClient() -> AmazonLocationClient? - { - return amazonLocationClient - } -} diff --git a/LocationServices/LocationServices/Helpers/AuthHelpers/AmazonLocationClient.swift b/LocationServices/LocationServices/Helpers/AuthHelpers/AmazonLocationClient.swift new file mode 100644 index 00000000..09096d35 --- /dev/null +++ b/LocationServices/LocationServices/Helpers/AuthHelpers/AmazonLocationClient.swift @@ -0,0 +1,40 @@ +// +// AmazonLocationClient.swift +// LocationServices +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +import Foundation +import AWSLocation +import AWSGeoPlaces +import AWSGeoRoutes + +public class AmazonLocationClient { + public init() { + } + + static func getCognitoLocationClient() async throws -> LocationClient? { + return CognitoAuthHelper.default().locationClient + } + + static func getApiLocationClient() -> LocationClient? { + return ApiAuthHelper.default().locationClient + } + + static func getPlacesClient() -> GeoPlacesClient? { + return ApiAuthHelper.default().geoPlacesClient + } + + static func getRoutesClient() -> GeoRoutesClient? { + return ApiAuthHelper.default().geoRoutesClient + } + + static func getApiKey() -> String? { + return ApiAuthHelper.default().apiKey + } + + static func getApiKeyRegion() -> String? { + return ApiAuthHelper.default().region + } +} diff --git a/LocationServices/LocationServices/Helpers/AuthHelpers/ApiAuthHelper.swift b/LocationServices/LocationServices/Helpers/AuthHelpers/ApiAuthHelper.swift new file mode 100644 index 00000000..d6debcc0 --- /dev/null +++ b/LocationServices/LocationServices/Helpers/AuthHelpers/ApiAuthHelper.swift @@ -0,0 +1,43 @@ +// +// ApiAuthHelper.swift +// LocationServices +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +import Foundation +import AWSGeoRoutes +import AWSGeoPlaces +import AWSLocation +import AmazonLocationiOSAuthSDK + +public class ApiAuthHelper { + + private static var _sharedInstance: ApiAuthHelper? + var locationClient: LocationClient? + var geoPlacesClient: GeoPlacesClient? + var geoRoutesClient: GeoRoutesClient? + var authHelper: AuthHelper? + var apiKey: String? + var region: String? + + static func initialise(apiKey: String, region: String) async throws { + if _sharedInstance == nil { + _sharedInstance = ApiAuthHelper() + _sharedInstance?.apiKey = apiKey + _sharedInstance?.region = region + let authHelper = try await AuthHelper.withApiKey(apiKey: apiKey, region: region) + _sharedInstance?.authHelper = authHelper + let locationClientConfig = authHelper.getLocationClientConfig() + _sharedInstance?.locationClient = LocationClient(config: locationClientConfig) + let placesClientConfig = authHelper.getGeoPlacesClientConfig() + _sharedInstance?.geoPlacesClient = GeoPlacesClient(config: placesClientConfig) + let routesClientConfig = authHelper.getGeoRoutesClientConfig() + _sharedInstance?.geoRoutesClient = GeoRoutesClient(config: routesClientConfig) + } + } + + static func `default`() -> ApiAuthHelper { + return _sharedInstance! + } +} diff --git a/LocationServices/LocationServices/Helpers/AuthHelpers/CognitoAuthHelper.swift b/LocationServices/LocationServices/Helpers/AuthHelpers/CognitoAuthHelper.swift new file mode 100644 index 00000000..1daa9b0a --- /dev/null +++ b/LocationServices/LocationServices/Helpers/AuthHelpers/CognitoAuthHelper.swift @@ -0,0 +1,40 @@ +// +// CognitoAuthHelper.swift +// LocationServices +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +import Foundation +import AWSSDKIdentity +import AmazonLocationiOSAuthSDK +import AWSLocation +import AwsCommonRuntimeKit + +public class CognitoAuthHelper { + + private static var _sharedInstance: CognitoAuthHelper? + var locationClient: LocationClient? + + private init() { + } + + static func initialise(identityPoolId: String) async throws { + let region = identityPoolId.toRegionString() + _sharedInstance = CognitoAuthHelper() + + if let credentialsString = KeyChainHelper.get(key: .cognitoCredentials), + let credentials = CognitoCredentials.decodeCognitoCredentials(jsonString: credentialsString) { + + var resolver: StaticAWSCredentialIdentityResolver? + let credentialsIdentity = AWSCredentialIdentity(accessKey: credentials.accessKeyId, secret: credentials.secretKey, expiration: credentials.expiration, sessionToken: credentials.sessionToken) + resolver = try StaticAWSCredentialIdentityResolver(credentialsIdentity) + let locationClientConfig = try await LocationClient.LocationClientConfiguration(awsCredentialIdentityResolver: resolver, region: region, signingRegion: region) + _sharedInstance?.locationClient = LocationClient(config: locationClientConfig) + } + } + + static func `default`() -> CognitoAuthHelper { + return _sharedInstance! + } +} diff --git a/LocationServices/LocationServices/Helpers/CognitoAuthHelper.swift b/LocationServices/LocationServices/Helpers/CognitoAuthHelper.swift deleted file mode 100644 index 76460d0e..00000000 --- a/LocationServices/LocationServices/Helpers/CognitoAuthHelper.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation -import AWSLocation -import AwsCommonRuntimeKit - -public class CognitoAuthHelper { - - private static var _sharedInstance: CognitoAuthHelper? - var locationCredentialsProvider: LocationCredentialsProvider? - var amazonLocationClient: AmazonLocationClient? - var authHelper: AuthHelper? - - private init() { - } - - static func initialise(identityPoolId: String) async throws { - _sharedInstance = CognitoAuthHelper() - _sharedInstance?.authHelper = AuthHelper() - _sharedInstance?.locationCredentialsProvider = try await _sharedInstance?.authHelper?.authenticateWithCognitoIdentityPool(identityPoolId: identityPoolId) - _sharedInstance?.amazonLocationClient = _sharedInstance?.authHelper?.getLocationClient() - try await _sharedInstance?.amazonLocationClient?.initialiseLocationClient() - } - - static func initialise(credentialsProvider: CredentialsProvider, region: String) async throws { - _sharedInstance = CognitoAuthHelper() - _sharedInstance?.authHelper = AuthHelper() - _sharedInstance?.locationCredentialsProvider = try await _sharedInstance?.authHelper?.authenticateWithCredentialsProvider(credentialsProvider: credentialsProvider, region: region) - _sharedInstance?.amazonLocationClient = _sharedInstance?.authHelper?.getLocationClient() - try await _sharedInstance?.amazonLocationClient?.initialiseLocationClient() - } - - static func `default`() -> CognitoAuthHelper { - return _sharedInstance! - } -} diff --git a/LocationServices/LocationServices/Helpers/CognitoCredentialsProvider.swift b/LocationServices/LocationServices/Helpers/CognitoCredentialsProvider.swift deleted file mode 100644 index f01ddc0c..00000000 --- a/LocationServices/LocationServices/Helpers/CognitoCredentialsProvider.swift +++ /dev/null @@ -1,49 +0,0 @@ -import Foundation -import AWSCognitoIdentity - -public class CognitoCredentialsProvider { - private static var cognitoIdentityClient: CognitoIdentityClient? - - private static func getAWSIdentityId(identityPoolId: String, region: String) async throws -> GetIdOutput { - do { - if cognitoIdentityClient == nil { - cognitoIdentityClient = try AWSCognitoIdentity.CognitoIdentityClient(region: region) - } - let idInput = GetIdInput(identityPoolId: identityPoolId) - let identity = try await cognitoIdentityClient!.getId(input: idInput) - return identity - } catch { - throw error - } - } - - private static func getAWSCredentials(identity: GetIdOutput, region: String) async throws -> GetCredentialsForIdentityOutput { - do { - if cognitoIdentityClient == nil { - cognitoIdentityClient = try AWSCognitoIdentity.CognitoIdentityClient(region: region) - } - let credentialsInput = GetCredentialsForIdentityInput(identityId: identity.identityId) - let credentials = try await cognitoIdentityClient!.getCredentialsForIdentity(input: credentialsInput) - return credentials - - } catch { - throw error - } - } - - static func generateCognitoCredentials(identityPoolId: String, region: String) async throws -> CognitoCredentials? - { - let identity = try await getAWSIdentityId(identityPoolId: identityPoolId, region: region) - - if let credentialsOutput = try await getAWSCredentials(identity: identity, region: region).credentials, - let accessKeyId = credentialsOutput.accessKeyId, - let secretKey = credentialsOutput.secretKey, - let sessionToken = credentialsOutput.sessionToken, - let expiration = credentialsOutput.expiration { - - let cognitoCredentials = CognitoCredentials(identityPoolId: identityPoolId, accessKeyId: accessKeyId, secretKey: secretKey, sessionToken: sessionToken, expiration: expiration) - return cognitoCredentials - } - return nil - } -} diff --git a/LocationServices/LocationServices/Helpers/GeneralHelper.swift b/LocationServices/LocationServices/Helpers/GeneralHelper.swift index c99aa25a..dde49ba2 100644 --- a/LocationServices/LocationServices/Helpers/GeneralHelper.swift +++ b/LocationServices/LocationServices/Helpers/GeneralHelper.swift @@ -9,23 +9,50 @@ import Foundation import UIKit class GeneralHelper { - static func getAmazonMapLogo(mapImageType: MapStyleImages?) -> UIColor { - switch mapImageType { - case .darkGray, - .Imagery, - .hereImagery, - .hybrid: + static func getAmazonMapLogo() -> UIColor { + let mapColor = UserDefaultsHelper.getObject(value: MapStyleColorType.self, key: .mapStyleColorType) + let mapStyle = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) + if mapStyle?.imageType == .hybrid || mapStyle?.imageType == .satellite { return UIColor.white - case .light, - .street, - .navigation, - .explore, - .contrast, - .exploreTruck, - .lightGray: - return UIColor.black - default: - return UIColor.black + } + else { + switch mapColor { + case .dark: + return UIColor.white + case .light: + return UIColor.black + default: + return UIColor.black + } + } + } + + static func getImageAndText(image: UIImage, + string: String, + isImageBeforeText: Bool, + segFont: UIFont? = nil) -> UIImage { + let font = segFont ?? UIFont.systemFont(ofSize: 16, weight: .medium) + let expectedTextSize = (string as NSString).size(withAttributes: [.font: font]) + let width = expectedTextSize.width + image.size.width + 5 + let height = max(expectedTextSize.height, image.size.width) + let size = CGSize(width: width, height: height) + + let renderer = UIGraphicsImageRenderer(size: size) + return renderer.image { context in + let fontTopPosition: CGFloat = (height - expectedTextSize.height) / 2 + let textOrigin: CGFloat = isImageBeforeText + ? image.size.width + 5 + : 0 + let textPoint: CGPoint = CGPoint.init(x: textOrigin, y: fontTopPosition) + string.draw(at: textPoint, withAttributes: [.font: font]) + let alignment: CGFloat = isImageBeforeText + ? 0 + : expectedTextSize.width + 5 + let rect = CGRect(x: alignment, + y: (height - image.size.height) / 2, + width: image.size.width, + height: image.size.height) + image.withRenderingMode(.alwaysTemplate).draw(in: rect) } } } diff --git a/LocationServices/LocationServices/Helpers/KeyChainHelper.swift b/LocationServices/LocationServices/Helpers/KeyChainHelper.swift index a27f5f78..65999410 100644 --- a/LocationServices/LocationServices/Helpers/KeyChainHelper.swift +++ b/LocationServices/LocationServices/Helpers/KeyChainHelper.swift @@ -1,3 +1,10 @@ +// +// KeyChainHelper.swift +// LocationServices +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + import KeychainSwift enum KeyChainType: String { diff --git a/LocationServices/LocationServices/Helpers/LocationCredentialsProvider.swift b/LocationServices/LocationServices/Helpers/LocationCredentialsProvider.swift deleted file mode 100644 index 4ed0e82f..00000000 --- a/LocationServices/LocationServices/Helpers/LocationCredentialsProvider.swift +++ /dev/null @@ -1,75 +0,0 @@ -import Foundation -import AwsCommonRuntimeKit - -public protocol LocationCredentialsProtocol { - -} - -extension CredentialsProvider: LocationCredentialsProtocol { - -} - -public class LocationCredentialsProvider: LocationCredentialsProtocol { - private var cognitoProvider: AmazonLocationCognitoCredentialsProvider? - private var apiProvider: AmazonLocationApiCredentialsProvider? - private var customCredentialsProvider: CredentialsProvider? - private var region: String? - - public init(region: String, identityPoolId: String){ - self.region = region - self.cognitoProvider = AmazonLocationCognitoCredentialsProvider(identityPoolId: identityPoolId, region: region) - } - - public init(region: String, apiKey: String){ - self.region = region - self.apiProvider = AmazonLocationApiCredentialsProvider(apiKey: apiKey, region: region) - } - - public init(credentialsProvider: CredentialsProvider){ - self.customCredentialsProvider = credentialsProvider - } - - public func getCognitoProvider() -> AmazonLocationCognitoCredentialsProvider? { - return cognitoProvider - } - - public func getApiProvider() -> AmazonLocationApiCredentialsProvider? { - return apiProvider - } - - public func getCustomCredentialsProvider() -> CredentialsProvider? { - return customCredentialsProvider - } - - public func getCredentialsProvider() -> LocationCredentialsProtocol? { - if let cognitoProvider = self.cognitoProvider { - return cognitoProvider - } else if let customCredentialsProvider = self.customCredentialsProvider { - return customCredentialsProvider - } else { - return nil - } - } - - public func getIdentityPoolId() -> String? { - return self.cognitoProvider?.identityPoolId - } - - public func getAPIKey() -> String? { - return self.apiProvider?.apiKey - } - - public func getRegion() -> String? { - return region - } - - internal func setAPIKey(apiKey: String) { - self.apiProvider?.apiKey = apiKey - } - - internal func setRegion(region: String) { - self.apiProvider?.region = region - self.cognitoProvider?.region = region - self.region = region - } -} diff --git a/LocationServices/LocationServices/Helpers/SettingsDefaultValueHelper.swift b/LocationServices/LocationServices/Helpers/SettingsDefaultValueHelper.swift index c364e425..d422e74b 100644 --- a/LocationServices/LocationServices/Helpers/SettingsDefaultValueHelper.swift +++ b/LocationServices/LocationServices/Helpers/SettingsDefaultValueHelper.swift @@ -16,6 +16,10 @@ final class SettingsDefaultValueHelper { UserDefaultsHelper.saveObject(value: DefaultUserSettings.mapStyle, key: .mapStyle) } + if UserDefaultsHelper.getObject(value: MapStyleColorType.self, key: .mapStyleColorType) == nil { + UserDefaultsHelper.saveObject(value: DefaultUserSettings.mapStyleColorType, key: .mapStyleColorType) + } + if UserDefaultsHelper.get(for: String.self, key: .unitType) == nil { UserDefaultsHelper.saveObject(value: DefaultUserSettings.unitValue, key: .unitType) } diff --git a/LocationServices/LocationServices/Helpers/UserDefaultsHelper.swift b/LocationServices/LocationServices/Helpers/UserDefaultsHelper.swift index 0ad264dd..8c68fb5a 100644 --- a/LocationServices/LocationServices/Helpers/UserDefaultsHelper.swift +++ b/LocationServices/LocationServices/Helpers/UserDefaultsHelper.swift @@ -14,6 +14,8 @@ enum UserDefaultKeyType: String { case showSignInOnAppStart case unitType case mapStyle + case mapStyleColorType + case politicalView case tollOptions case ferriesOptions // means we attach the policy to AWSLocation for tracking diff --git a/LocationServices/LocationServices/Models/MapModel.swift b/LocationServices/LocationServices/Models/MapModel.swift index 1e725770..1ba82af0 100644 --- a/LocationServices/LocationServices/Models/MapModel.swift +++ b/LocationServices/LocationServices/Models/MapModel.swift @@ -8,16 +8,22 @@ import Foundation struct MapModel { + let placeId: String? let placeName: String? let placeAddress: String? + let placeCity: String? + let placeCountry: String? let placeLat: Double? let placeLong: Double? var distance: Double? var duration: String? - init(placeName: String? = nil, placeAddress: String? = nil, placeLat: Double? = nil, placeLong: Double? = nil, distance: Double? = nil, duration: String? = nil) { + init(placeId: String? = nil, placeName: String? = nil, placeAddress: String? = nil, placeCity: String? = nil, placeCountry: String? = nil, placeLat: Double? = nil, placeLong: Double? = nil, distance: Double? = nil, duration: String? = nil) { + self.placeId = placeId self.placeName = placeName self.placeAddress = placeAddress + self.placeCity = placeCity + self.placeCountry = placeCountry self.placeLat = placeLat self.placeLong = placeLong self.distance = distance @@ -25,8 +31,11 @@ struct MapModel { } init(model: SearchPresentation) { + self.placeId = model.placeId self.placeName = model.name self.placeAddress = model.fullLocationAddress + self.placeCity = model.cityName + self.placeCountry = model.countryName self.placeLat = model.placeLat self.placeLong = model.placeLong self.distance = model.distance diff --git a/LocationServices/LocationServices/Networking/Services/LocationServiceable.swift b/LocationServices/LocationServices/Networking/Services/LocationServiceable.swift index c8bc1f0c..2c221817 100644 --- a/LocationServices/LocationServices/Networking/Services/LocationServiceable.swift +++ b/LocationServices/LocationServices/Networking/Services/LocationServiceable.swift @@ -11,8 +11,8 @@ import AWSLocation protocol LocationServiceable { func searchText(text: String, userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> - func searchTextWithSuggestion(text: String, userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> - func searchWithPosition(position: [Double], userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> + func searchWithSuggest(text: String, userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> + func reverseGeocode(position: [Double], userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> func getPlace(with placeId: String) async throws -> SearchPresentation? } @@ -26,7 +26,7 @@ struct LocationService: AWSLocationSearchService, LocationServiceable { if let userLat, let userLong { userLocation = CLLocation(latitude: userLat, longitude: userLong) } - let model = result!.results!.map({ SearchPresentation(model: $0, userLocation: userLocation) }) + let model = result!.resultItems!.map({ SearchPresentation(model: $0, userLocation: userLocation) }) return .success(model) } catch { @@ -34,37 +34,29 @@ struct LocationService: AWSLocationSearchService, LocationServiceable { } } - func searchTextWithSuggestion(text: String, userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> { + func searchWithSuggest(text: String, userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> { do { - let result = try await searchTextWithSuggestionRequest(text: text, userLat: userLat, userLong: userLong) - let model = try await result!.results!.asyncMap({ model in - guard let placeId = model.placeId else { return SearchPresentation(model: model) } - - var userLocation: CLLocation? = nil - if let userLat, let userLong { - userLocation = CLLocation(latitude: userLat, longitude: userLong) - } - let place = try await getPlace(with: placeId) - return SearchPresentation(model: model, placeLat: place?.placeLat, placeLong: place?.placeLong, userLocation: userLocation) + let result = try await searchWithSuggestRequest(text: text, userLat: userLat, userLong: userLong) + let model = result!.resultItems!.map({ model in + return SearchPresentation(model: model) }) return .success(model) } catch { + print(error) return .failure(error) } } //@discardableResult - func searchWithPosition(position: [Double], userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> { + func reverseGeocode(position: [Double], userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> { do { - let result = try await searchWithPositionRequest(position: position) - + let result = try await reverseGeocodeRequest(position: position) var userLocation: CLLocation? = nil if let userLat, let userLong { userLocation = CLLocation(latitude: userLat, longitude: userLong) } - - let model = result!.results!.map({ SearchPresentation(model: $0, userLocation: userLocation) }) + let model = result!.resultItems!.map({ SearchPresentation(model: $0, userLocation: userLocation) }) return .success(model) } catch { diff --git a/LocationServices/LocationServices/Networking/Services/RoutingServiceable.swift b/LocationServices/LocationServices/Networking/Services/RoutingServiceable.swift index 8d7e2212..e2c5de60 100644 --- a/LocationServices/LocationServices/Networking/Services/RoutingServiceable.swift +++ b/LocationServices/LocationServices/Networking/Services/RoutingServiceable.swift @@ -7,25 +7,28 @@ import Foundation import CoreLocation -import AWSLocation +import AWSGeoRoutes protocol RoutingServiceable { func calculateRouteWith(depaturePosition: CLLocationCoordinate2D, destinationPosition: CLLocationCoordinate2D, - travelModes: [LocationClientTypes.TravelMode], + travelModes: [GeoRoutesClientTypes.RouteTravelMode], avoidFerries: Bool, - avoidTolls: Bool) async throws -> [LocationClientTypes.TravelMode: Result] + avoidTolls: Bool) async throws -> [GeoRoutesClientTypes.RouteTravelMode: Result] +} + +enum RouteError: Error { + case noRouteFound(String) } struct RoutingAPIService: AWSRoutingServiceProtocol, RoutingServiceable { - func calculateRouteWith(depaturePosition: CLLocationCoordinate2D, destinationPosition: CLLocationCoordinate2D, - travelModes: [LocationClientTypes.TravelMode], + travelModes: [GeoRoutesClientTypes.RouteTravelMode], avoidFerries: Bool, - avoidTolls: Bool) async throws -> [LocationClientTypes.TravelMode: Result] { + avoidTolls: Bool) async throws -> [GeoRoutesClientTypes.RouteTravelMode: Result] { - var presentationObject: [LocationClientTypes.TravelMode: Result] = [:] + var presentationObject: [GeoRoutesClientTypes.RouteTravelMode: Result] = [:] for travelMode in travelModes { do { @@ -34,8 +37,13 @@ struct RoutingAPIService: AWSRoutingServiceProtocol, RoutingServiceable { travelMode: travelMode, avoidFerries: avoidFerries, avoidTolls: avoidTolls)! - let model = DirectionPresentation(model: response, travelMode: travelMode) - presentationObject[travelMode] = .success(model) + if let route = response.routes?[safe: 0] { + let model = DirectionPresentation(model: route, travelMode: travelMode) + presentationObject[travelMode] = .success(model) + } + else { + presentationObject[travelMode] = .failure(RouteError.noRouteFound("No routes found")) + } } catch { presentationObject[travelMode] = .failure(error) } diff --git a/LocationServices/LocationServices/Networking/Services/TrackingServiceable.swift b/LocationServices/LocationServices/Networking/Services/TrackingServiceable.swift index e6da15a8..c4b6256b 100644 --- a/LocationServices/LocationServices/Networking/Services/TrackingServiceable.swift +++ b/LocationServices/LocationServices/Networking/Services/TrackingServiceable.swift @@ -30,7 +30,7 @@ struct TrackingAPIService: AWSTrackingServiceProtocol, TrackingServiceable { return timestamp1 > timestamp2 } let presentation = sortedPositions.map { TrackingHistoryPresentation(model: $0, - stepType: sortedPositions.last?.sampleTime == $0.sampleTime ? .last : .first) } + stepState: sortedPositions.last?.sampleTime == $0.sampleTime ? .last : .first) } return presentation } return [] diff --git a/LocationServices/LocationServices/Scenes/About/Sub-Scenes/Attribution/Controller/AttributionVC.swift b/LocationServices/LocationServices/Scenes/About/Sub-Scenes/Attribution/Controller/AttributionVC.swift index fc6b2483..daf27609 100644 --- a/LocationServices/LocationServices/Scenes/About/Sub-Scenes/Attribution/Controller/AttributionVC.swift +++ b/LocationServices/LocationServices/Scenes/About/Sub-Scenes/Attribution/Controller/AttributionVC.swift @@ -35,15 +35,7 @@ final class AttributionVC: UIViewController { label.font = .amazonFont(type: .regular, size: 13) label.textColor = .lsGrey label.numberOfLines = 0 - - let localData = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - switch localData?.type { - case .esri, .none: - label.text = StringConstant.partnerAttributionESRIDescription - case .here: - label.text = StringConstant.partnerAttributionHEREDescription - } - + label.text = StringConstant.partnerAttributionHEREDescription return label }() @@ -224,14 +216,7 @@ final class AttributionVC: UIViewController { // NARK: - Actions @objc func partnerLearnButtonTapped() { let providerURL: String - let localData = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - switch localData?.type { - case .esri, .none: - providerURL = StringConstant.esriDataProviderLearnMoreURL - case .here: - providerURL = StringConstant.hereDataProviderLearnMoreURL - } - + providerURL = StringConstant.hereDataProviderLearnMoreURL let url = URL(string: providerURL) openSafariBrowser(with: url) } diff --git a/LocationServices/LocationServices/Scenes/Explore/Contracts/ExploreContracts.swift b/LocationServices/LocationServices/Scenes/Explore/Contracts/ExploreContracts.swift index 68af09b5..d4e68bc1 100755 --- a/LocationServices/LocationServices/Scenes/Explore/Contracts/ExploreContracts.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Contracts/ExploreContracts.swift @@ -38,13 +38,13 @@ protocol ExploreVCProtocol: AnyObject { protocol ExploreNavigationDelegate: AnyObject { func showMapStyles() func showDirections(isRouteOptionEnabled: Bool?, - firstDestionation: MapModel?, - secondDestionation: MapModel?, + firstDestination: MapModel?, + secondDestination: MapModel?, lat: Double?, long: Double?) func showSearchSceneWith(lat: Double?, long: Double?) func showPoiCardScene(cardData: [MapModel], lat: Double?, long: Double?) - func showNavigationview(steps: [NavigationSteps], summaryData: (totalDistance: Double, totalDuration: Double), firstDestionation: MapModel?, secondDestionation: MapModel?) + func showNavigationview(routeLegDetails: [RouteLegDetails], summaryData: (totalDistance: Double, totalDuration: Double), firstDestination: MapModel?, secondDestination: MapModel?) func showLoginFlow() func showLoginSuccess() func showAttribution() diff --git a/LocationServices/LocationServices/Scenes/Explore/Controller/ExploreVC.swift b/LocationServices/LocationServices/Scenes/Explore/Controller/ExploreVC.swift index f9158882..b2417a0d 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Controller/ExploreVC.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Controller/ExploreVC.swift @@ -47,7 +47,6 @@ final class ExploreVC: UIViewController { override func viewDidLoad() { super.viewDidLoad() - //self.hideKeyboardWhenTappedAround() setupHandlers() setupNotifications() @@ -61,7 +60,6 @@ final class ExploreVC: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) exploreView.shouldBottomStackViewPositionUpdate() - blurStatusBar() setupKeyboardNotifications() } @@ -135,15 +133,15 @@ extension ExploreVC: ExploreViewOutputDelegate { delegate?.showMapStyles() } - func showNavigationView(steps: [NavigationSteps]) { + func showNavigationView(steps: [RouteNavigationStep]) { } func showDirectionView(userLocation: CLLocationCoordinate2D?) { delegate?.showDirections(isRouteOptionEnabled: false, - firstDestionation: nil, - secondDestionation: nil, + firstDestination: nil, + secondDestination: nil, lat: userLocation?.latitude, long: userLocation?.longitude) } @@ -370,7 +368,7 @@ extension ExploreVC { } @objc private func drawDirectionRoute(_ notification: Notification) { - guard let data = notification.userInfo?["LineString"] as? Data, + guard let data = notification.userInfo?["LineString"] as? [Data], let departureLocation = notification.userInfo?["DepartureLocation"] as? CLLocationCoordinate2D, let destinationLocation = notification.userInfo?["DestinationLocation"] as? CLLocationCoordinate2D, let routeType = notification.userInfo?["routeType"] as? RouteTypes else { @@ -397,7 +395,7 @@ extension ExploreVC { } @objc private func showNavigationScene(_ notification: Notification) { - if let datas = notification.userInfo?["steps"] as? (steps: [NavigationSteps], sumData: (totalDistance: Double, totalDuration: Double)), + if let datas = notification.userInfo?["routeLegdetails"] as? (routeLegdetails: [RouteLegDetails]?, sumData: (totalDistance: Double, totalDuration: Double)), let routeModel = notification.userInfo?["routeModel"] as? RouteModel { viewModel.activateRoute(route: routeModel) if !routeModel.isPreview { @@ -411,10 +409,10 @@ extension ExploreVC { let firstDestination = MapModel(placeName: routeModel.departurePlaceName, placeAddress: routeModel.departurePlaceAddress, placeLat: routeModel.departurePosition.latitude, placeLong: routeModel.departurePosition.longitude) let secondDestination = MapModel(placeName: routeModel.destinationPlaceName, placeAddress: routeModel.destinationPlaceAddress, placeLat: routeModel.destinationPosition.latitude, placeLong: routeModel.destinationPosition.longitude) - self.delegate?.showNavigationview(steps: datas.steps, + self.delegate?.showNavigationview(routeLegDetails: datas.routeLegdetails!, summaryData: datas.sumData, - firstDestionation: firstDestination, - secondDestionation: secondDestination) + firstDestination: firstDestination, + secondDestination: secondDestination) } } @@ -504,14 +502,19 @@ extension ExploreVC: CLLocationManagerDelegate { } func routeReCalculated(route: DirectionPresentation, departureLocation: CLLocationCoordinate2D, destinationLocation: CLLocationCoordinate2D, routeType: RouteTypes) { - let steps = route.navigationSteps - let sumData = (route.distance, route.duration) - - let userInfo = ["steps": (steps: steps, sumData: sumData)] - NotificationCenter.default.post(name: Notification.Name("NavigationStepsUpdated"), object: nil, userInfo: userInfo) - - let data = (try? JSONEncoder().encode(route.lineString)) ?? Data() - self.exploreView.drawCalculatedRouteWith(data, departureLocation: departureLocation, destinationLocation: destinationLocation, isRecalculation: true, routeType: routeType) + if let routeLegDetails = route.routeLegDetails { + + let sumData = (route.distance, route.duration) + let userInfo = ["routeLegDetails": (routeLegDetails: routeLegDetails, sumData: sumData)] + NotificationCenter.default.post(name: Notification.Name("NavigationStepsUpdated"), object: nil, userInfo: userInfo) + + var datas: [Data] = [] + for legDetails in routeLegDetails { + let data = (try? JSONEncoder().encode(legDetails.lineString)) ?? Data() + datas.append(data) + } + self.exploreView.drawCalculatedRouteWith(datas, departureLocation: departureLocation, destinationLocation: destinationLocation, isRecalculation: true, routeType: routeType) + } } func userReachedDestination(_ destination: MapModel) { diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Controller/DirectionVC+Tableview.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Controller/DirectionVC+Tableview.swift index c69321f9..f6beb224 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Controller/DirectionVC+Tableview.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Controller/DirectionVC+Tableview.swift @@ -45,9 +45,9 @@ extension DirectionVC: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let destination: DirectionTextFieldModel? if isDestination { - destination = secondDestionation + destination = secondDestination } else { - destination = firstDestionation + destination = firstDestination } let isMyLocationSelected = destination?.placeName == "My Location" @@ -89,9 +89,9 @@ extension DirectionVC: UITableViewDataSource { let searchTextModel = DirectionTextFieldModel(placeName: currentModel.locationName ?? "", placeAddress: currentModel.label, lat: currentModel.lat, long: currentModel.long) if self.isDestination { - secondDestionation = searchTextModel + secondDestination = searchTextModel } else { - firstDestionation = searchTextModel + firstDestination = searchTextModel } if currentModel.searchType == .mylocation { @@ -110,7 +110,7 @@ extension DirectionVC: UITableViewDataSource { Task { let state = try await viewModel.searchSelectedPlaceWith(currentModel, lat: userLocation?.lat, long: userLocation?.long) - let canSearch = firstDestionation != nil && firstDestionation?.lat != nil && secondDestionation != nil && secondDestionation?.lat != nil + let canSearch = firstDestination != nil && firstDestination?.lat != nil && secondDestination != nil && secondDestination?.lat != nil if state && canSearch { self.sheetPresentationController?.selectedDetentIdentifier = .medium @@ -119,7 +119,7 @@ extension DirectionVC: UITableViewDataSource { } } - func sendDirectionsToExploreVC(data: Data, + func sendDirectionsToExploreVC(data: [Data], departureLocation: CLLocationCoordinate2D, destinationLocation: CLLocationCoordinate2D, routeType: RouteTypes) { @@ -128,6 +128,5 @@ extension DirectionVC: UITableViewDataSource { "DestinationLocation": destinationLocation, "routeType": routeType] NotificationCenter.default.post(name: Notification.Name("DirectionLineString"), object: nil, userInfo: datas) - //NotificationCenter.default.post(name: Notification.Name("updateMapViewButtons"), object: nil, userInfo: nil) } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Controller/DirectionVC.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Controller/DirectionVC.swift index 30123126..93f6b104 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Controller/DirectionVC.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Controller/DirectionVC.swift @@ -24,8 +24,8 @@ final class DirectionVC: UIViewController { var isInSplitViewController: Bool = false var dismissHandler: VoidHandler? var isRoutingOptionsEnabled: Bool = false - var firstDestionation: DirectionTextFieldModel? - var secondDestionation: DirectionTextFieldModel? + var firstDestination: DirectionTextFieldModel? + var secondDestination: DirectionTextFieldModel? var isInitalState: Bool = true var userLocation: (lat: Double?, long: Double?)? { didSet { @@ -53,6 +53,12 @@ final class DirectionVC: UIViewController { } } + private let activityIndicator: UIActivityIndicatorView = { + let indicator = UIActivityIndicatorView(style: .medium) + indicator.hidesWhenStopped = true + return indicator + }() + lazy var directionSearchView: DirectionSearchView = { let titleTopOffset: CGFloat = isInSplitViewController ? Constants.titleOffsetiPad : Constants.titleOffsetiPhone return DirectionSearchView(titleTopOffset: titleTopOffset, isCloseButtonHidden: isInSplitViewController) @@ -80,7 +86,7 @@ final class DirectionVC: UIViewController { applyStyles() viewModel.loadLocalOptions() - if firstDestionation?.placeName == "My Location" { + if firstDestination?.placeName == "My Location" { directionSearchView.setMyLocationText() } locationManagerSetup() @@ -93,7 +99,7 @@ final class DirectionVC: UIViewController { if isRoutingOptionsEnabled { sheetPresentationController?.selectedDetentIdentifier = Constants.mediumId } else { - let isDestination = firstDestionation?.placeName != nil + let isDestination = firstDestination?.placeName != nil directionSearchView.becomeFirstResponder(isDestination: isDestination) } } @@ -116,6 +122,14 @@ final class DirectionVC: UIViewController { removeNotifications() } + func showLoadingIndicator() { + activityIndicator.startAnimating() + } + + func hideLoadingIndicator() { + activityIndicator.stopAnimating() + } + private func applyStyles() { tableView.backgroundColor = directionScreenStyle.backgroundColor view.backgroundColor = directionScreenStyle.backgroundColor @@ -143,16 +157,16 @@ final class DirectionVC: UIViewController { // Delete my location value and restore My location text if model.searchText == "" { if model.isDestination { - self?.secondDestionation = DirectionTextFieldModel(placeName: "", + self?.secondDestination = DirectionTextFieldModel(placeName: "", placeAddress: nil, lat: nil, long: nil) } else { - self?.firstDestionation = DirectionTextFieldModel(placeName: "", + self?.firstDestination = DirectionTextFieldModel(placeName: "", placeAddress: nil, lat: nil, long: nil) } - if self?.firstDestionation?.placeName != "My Location" && self?.secondDestionation?.placeName != "My Location" { + if self?.firstDestination?.placeName != "My Location" && self?.secondDestination?.placeName != "My Location" { self?.viewModel.addMyLocationItem() } } @@ -216,10 +230,10 @@ final class DirectionVC: UIViewController { let currentLocation = DirectionTextFieldModel(placeName: "My Location", placeAddress: nil, lat: self.userLocation?.lat, long: self.userLocation?.long) - if firstDestionation?.placeName == "My Location" { - firstDestionation = currentLocation - } else if secondDestionation?.placeName == "My Location" { - secondDestionation = currentLocation + if firstDestination?.placeName == "My Location" { + firstDestination = currentLocation + } else if secondDestination?.placeName == "My Location" { + secondDestination = currentLocation } Task { try await calculateRoute() @@ -227,20 +241,14 @@ final class DirectionVC: UIViewController { } private func setupViews() { - directionSearchView.changeSearchRouteName(with: firstDestionation?.placeName, isDestination: false) - directionSearchView.changeSearchRouteName(with: secondDestionation?.placeName, isDestination: true) - //let scrollView = UIScrollView() + directionSearchView.changeSearchRouteName(with: firstDestination?.placeName, isDestination: false) + directionSearchView.changeSearchRouteName(with: secondDestination?.placeName, isDestination: true) self.view.addSubview(directionSearchView) - //self.view.addSubview(scrollView) self.view.addSubview(directionView) + self.view.addSubview(activityIndicator) self.view.addSubview(tableView) -// scrollView.snp.makeConstraints { -// $0.top.equalToSuperview().offset(10) -// $0.trailing.leading.bottom.equalToSuperview() -// } - directionSearchView.snp.makeConstraints { $0.leading.trailing.equalToSuperview().offset(14) $0.top.equalTo(view.safeAreaLayoutGuide) @@ -256,8 +264,13 @@ final class DirectionVC: UIViewController { $0.width.equalToSuperview() } - tableView.snp.makeConstraints { + activityIndicator.snp.makeConstraints { $0.top.equalTo(directionSearchView.snp.bottom).offset(16) + $0.centerX.equalToSuperview() + } + + tableView.snp.makeConstraints { + $0.top.equalTo(activityIndicator.snp.bottom).offset(16) $0.leading.trailing.bottom.equalToSuperview() $0.width.equalToSuperview() } @@ -275,13 +288,13 @@ final class DirectionVC: UIViewController { let currentModel = SearchCellViewModel(searchType: .location, placeId: nil, - locationName: secondDestionation?.placeName, + locationName: secondDestination?.placeName, locationDistance: nil, locationCountry: nil, locationCity: nil, label: nil, - long: secondDestionation?.long, - lat: secondDestionation?.lat) + long: secondDestination?.long, + lat: secondDestination?.lat) try await calculateGenericRoute(currentModel: currentModel, routeType: routeType, avoidFerries: avoidFerries, @@ -289,78 +302,79 @@ final class DirectionVC: UIViewController { } func setupSearchTitleDestinations() { - self.directionSearchView.changeSearchRouteName(with: firstDestionation?.placeName ?? "", isDestination: false) - self.directionSearchView.changeSearchRouteName(with: secondDestionation?.placeName ?? "", isDestination: true) + self.directionSearchView.changeSearchRouteName(with: firstDestination?.placeName ?? "", isDestination: false) + self.directionSearchView.changeSearchRouteName(with: secondDestination?.placeName ?? "", isDestination: true) } func calculateGenericRoute(currentModel: SearchCellViewModel, routeType: RouteTypes = .car, avoidFerries: Bool = false, avoidTolls: Bool = false) async throws { - guard let (departureLocation, destinationLocation) = getRouteLocations(currentModel: currentModel) else { return } - + showLoadingIndicator() guard isDistanceValid(departureLoc: departureLocation, destinationLoc: destinationLocation) else { return } if let (data, directionVM) = try await viewModel.calculateRouteWith(destinationPosition: destinationLocation, departurePosition: departureLocation, travelMode: routeType, avoidFerries: avoidFerries, avoidTolls: avoidTolls) { - - self.tableView.isHidden = true - self.directionView.isHidden = false - - let isPreview = self.firstDestionation?.placeName != "My Location" - self.directionView.setup(model: directionVM, isPreview: isPreview) DispatchQueue.main.async { + self.tableView.isHidden = true + self.directionView.isHidden = false + + let isPreview = self.firstDestination?.placeName != "My Location" + self.directionView.setup(model: directionVM, isPreview: isPreview) + self.directionView.showOptionsStackView() + + + self.setupSearchTitleDestinations() + self.view.endEditing(true) + self.sendDirectionsToExploreVC(data: data, + departureLocation: departureLocation, + destinationLocation: destinationLocation, + routeType: routeType) + self.hideLoadingIndicator() } - - self.setupSearchTitleDestinations() - self.view.endEditing(true) - self.sendDirectionsToExploreVC(data: data, - departureLocation: departureLocation, - destinationLocation: destinationLocation, - routeType: routeType) } } private func updateMyLocationDestination() { - if firstDestionation?.placeName == "My Location" { - firstDestionation?.lat = userLocation?.lat - firstDestionation?.long = userLocation?.long - } else if secondDestionation?.placeName == "My Location" { - secondDestionation?.lat = userLocation?.lat - secondDestionation?.long = userLocation?.long + if firstDestination?.placeName == "My Location" { + firstDestination?.lat = userLocation?.lat + firstDestination?.long = userLocation?.long + } else if secondDestination?.placeName == "My Location" { + secondDestination?.lat = userLocation?.lat + secondDestination?.long = userLocation?.long } } private func getRouteLocations(currentModel: SearchCellViewModel) -> (departure: CLLocationCoordinate2D, destination: CLLocationCoordinate2D)? { updateMyLocationDestination() - if let departureLat = firstDestionation?.lat, - let departureLong = firstDestionation?.long, - let destionationLat = secondDestionation?.lat, - let destinationLong = secondDestionation?.long { + if let departureLat = firstDestination?.lat, + let departureLong = firstDestination?.long, + let destinationLat = secondDestination?.lat, + let destinationLong = secondDestination?.long { let departureLoc = CLLocationCoordinate2D(latitude: departureLat, longitude: departureLong) - let destionationLoc = CLLocationCoordinate2D(latitude: destionationLat, longitude: destinationLong) + let destinationLoc = CLLocationCoordinate2D(latitude: destinationLat, longitude: destinationLong) - return (departureLoc, destionationLoc) + return (departureLoc, destinationLoc) } else { guard let userLat = userLocation?.long, let userlong = userLocation?.lat, - let destionationLat = currentModel.lat, + let destinationLat = currentModel.lat, let destinationLong = currentModel.long else { return nil } let userLoc = CLLocationCoordinate2D(latitude: userLat, longitude: userlong) - let secondDestination = CLLocationCoordinate2D(latitude: destionationLat, longitude: destinationLong) + let secondDestinationCoordinates = CLLocationCoordinate2D(latitude: destinationLat, longitude: destinationLong) - let isStartFromCurrentLocation = firstDestionation?.placeName == "My Location" - let isEndWithCurrentLocation = secondDestionation?.placeName == "My Location" + let isStartFromCurrentLocation = firstDestination?.placeName == "My Location" + let isEndWithCurrentLocation = secondDestination?.placeName == "My Location" if isStartFromCurrentLocation { - return (userLoc, secondDestination) + return (userLoc, secondDestinationCoordinates) } else if isEndWithCurrentLocation { - return (secondDestination, userLoc) + return (secondDestinationCoordinates, userLoc) } else { return nil } @@ -370,13 +384,13 @@ final class DirectionVC: UIViewController { func calculateRoute() async throws { updateMyLocationDestination() - if let departureLat = firstDestionation?.lat, let departureLong = firstDestionation?.long, let destionationLat = secondDestionation?.lat, let destinationLong = secondDestionation?.long { + if let departureLat = firstDestination?.lat, let departureLong = firstDestination?.long, let destinationLat = secondDestination?.lat, let destinationLong = secondDestination?.long { let departureLoc = CLLocationCoordinate2D(latitude: departureLat, longitude: departureLong) - let destionationLoc = CLLocationCoordinate2D(latitude: destionationLat, longitude: destinationLong) + let destinationLoc = CLLocationCoordinate2D(latitude: destinationLat, longitude: destinationLong) - guard isDistanceValid(departureLoc: departureLoc, destinationLoc: destionationLoc) else { return } - if let (data, directionVM) = try await viewModel.calculateRouteWith(destinationPosition: destionationLoc, departurePosition: departureLoc, avoidFerries: viewModel.avoidFerries, avoidTolls: viewModel.avoidTolls) { + guard isDistanceValid(departureLoc: departureLoc, destinationLoc: destinationLoc) else { return } + if let (data, directionVM) = try await viewModel.calculateRouteWith(destinationPosition: destinationLoc, departurePosition: departureLoc, avoidFerries: viewModel.avoidFerries, avoidTolls: viewModel.avoidTolls) { self.tableView.isHidden = true self.directionView.isHidden = false @@ -385,12 +399,12 @@ final class DirectionVC: UIViewController { self.directionView.showOptionsStackView() } - let isPreview = self.firstDestionation?.placeName != "My Location" + let isPreview = self.firstDestination?.placeName != "My Location" self.directionView.setup(model: directionVM, isPreview: isPreview) self.sendDirectionsToExploreVC(data: data, departureLocation: departureLoc, - destinationLocation: destionationLoc, routeType: .car) + destinationLocation: destinationLoc, routeType: .car) } } } @@ -398,15 +412,15 @@ final class DirectionVC: UIViewController { private func getRouteModel(for type: RouteTypes) -> RouteModel? { updateMyLocationDestination() - let departureLat = firstDestionation?.lat - let departureLong = firstDestionation?.long - let departurePlaceName = firstDestionation?.placeName - let departurePlaceAddress = firstDestionation?.placeAddress + let departureLat = firstDestination?.lat + let departureLong = firstDestination?.long + let departurePlaceName = firstDestination?.placeName + let departurePlaceAddress = firstDestination?.placeAddress - let destinationLat = secondDestionation?.lat - let destinationLong = secondDestionation?.long - let destinationPlaceName = secondDestionation?.placeName - let destinationPlaceAddress = secondDestionation?.placeAddress + let destinationLat = secondDestination?.lat + let destinationLong = secondDestination?.long + let destinationPlaceName = secondDestination?.placeName + let destinationPlaceAddress = secondDestination?.placeAddress guard let departureLat, let departureLong, @@ -416,37 +430,20 @@ final class DirectionVC: UIViewController { } let departureLoc = CLLocationCoordinate2D(latitude: departureLat, longitude: departureLong) - let destionationLoc = CLLocationCoordinate2D(latitude: destinationLat, longitude: destinationLong) + let destinationLoc = CLLocationCoordinate2D(latitude: destinationLat, longitude: destinationLong) let avoidFerries = viewModel.avoidFerries let avoidToll = viewModel.avoidTolls let isPreview = departurePlaceName != "My Location" - let routeModel = RouteModel(departurePosition: departureLoc, destinationPosition: destionationLoc, travelMode: type, avoidFerries: avoidFerries, avoidTolls: avoidToll, isPreview: isPreview, departurePlaceName: departurePlaceName, departurePlaceAddress: departurePlaceAddress, destinationPlaceName: destinationPlaceName, destinationPlaceAddress: destinationPlaceAddress) + let routeModel = RouteModel(departurePosition: departureLoc, destinationPosition: destinationLoc, travelMode: type, avoidFerries: avoidFerries, avoidTolls: avoidToll, isPreview: isPreview, departurePlaceName: departurePlaceName, departurePlaceAddress: departurePlaceAddress, destinationPlaceName: destinationPlaceName, destinationPlaceAddress: destinationPlaceAddress) return routeModel } private func isDistanceValid(departureLoc: CLLocationCoordinate2D, destinationLoc: CLLocationCoordinate2D) -> Bool { - let currentMapStyle = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - switch currentMapStyle?.type { - case .esri, .none: - let userLocation = CLLocation(location: departureLoc) - let placeLocation = CLLocation(location: destinationLoc) - - let distance = userLocation.distance(from: placeLocation) - guard distance < NumberConstants.fourHundredKMInMeters else { - DispatchQueue.main.async { - self.directionView.showErrorStackView() - self.tableView.isHidden = true - self.directionView.isHidden = false - } - return false - } - case .here: - break - } + //May implement this later if there is limit on distance return true } } @@ -460,11 +457,11 @@ extension DirectionVC: DirectionViewModelOutputDelegate { if let model = mapModel[safe: 0] { let currentModel = SearchCellViewModel(searchType: .location, - placeId: nil, + placeId: model.placeId, locationName: model.placeName, - locationDistance: nil, - locationCountry: nil, - locationCity: nil, + locationDistance: model.distance, + locationCountry: model.placeCountry, + locationCity: model.placeCity, label: model.placeName, long: model.placeLong, lat: model.placeLat) @@ -472,9 +469,9 @@ extension DirectionVC: DirectionViewModelOutputDelegate { let searchTextModel = DirectionTextFieldModel(placeName: currentModel.locationName ?? "", placeAddress: model.placeAddress, lat: currentModel.lat, long: currentModel.long) if self.isDestination { - secondDestionation = searchTextModel + secondDestination = searchTextModel } else { - firstDestionation = searchTextModel + firstDestination = searchTextModel } try await calculateGenericRoute(currentModel: currentModel, avoidFerries: viewModel.avoidFerries, avoidTolls: viewModel.avoidTolls) DispatchQueue.main.async { @@ -501,7 +498,7 @@ extension DirectionVC: DirectionViewModelOutputDelegate { } func isMyLocationAlreadySelected() -> Bool { - return [firstDestionation, secondDestionation].contains(where: { $0?.placeName == "My Location" }) + return [firstDestination, secondDestination].contains(where: { $0?.placeName == "My Location" }) } } @@ -510,7 +507,7 @@ extension DirectionVC: DirectionViewOutputDelegate { let navigationLegs = self.viewModel.getCurrentNavigationLegsWith(type) switch navigationLegs { - case .success(let steps): + case .success(let routeLegdetails): let routeModel = self.getRouteModel(for: type) let sumData = self.viewModel.getSumData(type) @@ -520,7 +517,7 @@ extension DirectionVC: DirectionViewOutputDelegate { } } - let userInfo = ["steps" : (steps: steps, sumData: sumData), "routeModel": routeModel as Any] as [String : Any] + let userInfo = ["routeLegdetails" : (routeLegdetails: routeLegdetails, sumData: sumData), "routeModel": routeModel as Any] as [String : Any] NotificationCenter.default.post(name: Notification.Name("NavigationSteps"), object: nil, userInfo: userInfo) case .failure(let error): let alertModel = AlertModel(title: StringConstant.error, message: error.localizedDescription, cancelButton: nil) @@ -540,7 +537,7 @@ extension DirectionVC: DirectionSearchViewOutputDelegate { } func swapLocations() async throws { - (firstDestionation, secondDestionation) = (secondDestionation, firstDestionation) + (firstDestination, secondDestination) = (secondDestination, firstDestination) try await calculateRoute() } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Presentation/DirectionPresentation.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Presentation/DirectionPresentation.swift index d2a829bb..7df8f510 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Presentation/DirectionPresentation.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Presentation/DirectionPresentation.swift @@ -6,47 +6,220 @@ // SPDX-License-Identifier: MIT-0 import Foundation -import AWSLocation +import AWSGeoRoutes +import UIKit struct DirectionPresentation { - let travelMode: LocationClientTypes.TravelMode + let travelMode: GeoRoutesClientTypes.RouteTravelMode let distance: Double let duration: Double - let lineString: GeoData - let navigationSteps: [NavigationSteps] + var routeLegDetails: [RouteLegDetails]? = nil - - init(model: CalculateRouteOutput, travelMode: LocationClientTypes.TravelMode) { - self.distance = Double(model.legs?[0].distance ?? 0) - self.duration = Double(model.legs?[0].durationSeconds ?? 0) + init(model: GeoRoutesClientTypes.Route, travelMode: GeoRoutesClientTypes.RouteTravelMode) { + self.distance = Double(model.summary?.distance ?? 0) + self.duration = Double(model.summary?.duration ?? 0) + - let geometry = Geometry(type: "LineString", coordinates: model.legs?[0].geometry?.lineString as? [[Double]]) - let properties = Properties(name: "Crema to Council Cres") - let feature = [Feature(type: "Feature", properties: properties, geometry: geometry)] - self.lineString = GeoData(type: "FeatureCollection", features: feature) self.travelMode = travelMode - if let steps = model.legs?[safe: 0]?.steps { - self.navigationSteps = steps.map(NavigationSteps.init) - } else { - self.navigationSteps = [] + if let legs = model.legs { + self.routeLegDetails = [] + for leg in legs { + if let legDetails = leg.pedestrianLegDetails { + var legDetails = RouteLegDetails(model: legDetails) + legDetails.setLineString(leg: leg) + routeLegDetails?.append(legDetails) + } + if let legDetails = leg.vehicleLegDetails { + var legDetails = RouteLegDetails(model: legDetails) + legDetails.setLineString(leg: leg) + routeLegDetails?.append(legDetails) + } + if let legDetails = leg.ferryLegDetails { + var legDetails = RouteLegDetails(model: legDetails) + legDetails.setLineString(leg: leg) + routeLegDetails?.append(legDetails) + } + } } } } +struct RouteLegDetails { + let distance: Double + let duration: Double + let navigationSteps: [RouteNavigationStep] + var lineString: GeoData? = nil + + init(model: GeoRoutesClientTypes.RouteVehicleLegDetails) { + self.distance = Double(model.summary?.overview?.distance ?? 0) + self.duration = Double(model.summary?.overview?.duration ?? 0) + self.navigationSteps = model.travelSteps?.map({ step in + return RouteNavigationStep(distance: Double(step.distance), duration: Double(step.duration), instruction: step.instruction ?? "", type: NavigationStepType(from: step.type ?? .sdkUnknown(""))) + }) ?? [] + } + + init(model: GeoRoutesClientTypes.RoutePedestrianLegDetails) { + self.distance = Double(model.summary?.overview?.distance ?? 0) + self.duration = Double(model.summary?.overview?.duration ?? 0) + self.navigationSteps = model.travelSteps?.map({ step in + return RouteNavigationStep(distance: Double(step.distance), duration: Double(step.duration), instruction: step.instruction ?? "", type: NavigationStepType(from: step.type ?? .sdkUnknown(""))) + }) ?? [] + } + + init(model: GeoRoutesClientTypes.RouteFerryLegDetails) { + self.distance = Double(model.summary?.overview?.distance ?? 0) + self.duration = Double(model.summary?.overview?.duration ?? 0) + self.navigationSteps = model.travelSteps?.map({ step in + return RouteNavigationStep(distance: Double(step.distance), duration: Double(step.duration), instruction: step.instruction ?? "", type: NavigationStepType(from: step.type ?? .sdkUnknown(""))) + }) ?? [] + } + + mutating func setLineString(leg: GeoRoutesClientTypes.RouteLeg) { + let geometry = Geometry(type: "LineString", coordinates: leg.geometry?.lineString as? [[Double]]) + let properties = Properties(name: "Crema to Council Cres") + let feature = [Feature(type: "Feature", properties: properties, geometry: geometry)] + self.lineString = GeoData(type: "FeatureCollection", features: feature) + } +} -struct NavigationSteps { +struct RouteNavigationStep { + let startPosition: [Double]? = nil + let endPosition: [Double]? = nil let distance: Double let duration: Double - let startPosition: [Double] - let endPosition: [Double] + let instruction: String + let type: NavigationStepType +} + +enum NavigationStepType { + case arrive + case `continue` + case depart + case exit + case keep + case ramp + case roundaboutEnter + case roundaboutExit + case roundaboutPass + case turn + case uTurn + case sdkUnkown + case continueHighway + case enterHighway + + // TO DO: replace image names with correct names in future + var image: UIImage? { + var imageName = "step-icon" + switch self { + case .arrive: + imageName = "step-icon" + case .continue: + imageName = "step-icon" + case .depart: + imageName = "step-icon" + case .exit: + imageName = "step-icon" + case .keep: + imageName = "step-icon" + case .ramp: + imageName = "step-icon" + case .roundaboutEnter: + imageName = "step-icon" + case .roundaboutExit: + imageName = "step-icon" + case .roundaboutPass: + imageName = "step-icon" + case .turn: + imageName = "step-icon" + case .uTurn: + imageName = "step-icon" + case .continueHighway: + imageName = "step-icon" + case .sdkUnkown: + imageName = "step-icon" + case .enterHighway: + imageName = "step-icon" + } + return UIImage(named: imageName) + } - init(model: LocationClientTypes.Step) { - self.distance = Double(model.distance ?? 0) - self.duration = Double(model.durationSeconds ?? 0) - self.startPosition = model.startPosition ?? [] - self.endPosition = model.endPosition ?? [] + init(from type: GeoRoutesClientTypes.RoutePedestrianTravelStepType) { + switch type { + case .arrive: + self = .arrive + case .continue: + self = .continue + case .depart: + self = .depart + case .exit: + self = .exit + case .keep: + self = .keep + case .ramp: + self = .ramp + case .roundaboutEnter: + self = .roundaboutEnter + case .roundaboutExit: + self = .roundaboutExit + case .roundaboutPass: + self = .roundaboutPass + case .turn: + self = .turn + case .uTurn: + self = .uTurn + + case .sdkUnknown(_): + self = .sdkUnkown + } } + + init(from type: GeoRoutesClientTypes.RouteVehicleTravelStepType) { + switch type { + case .arrive: + self = .arrive + case .continue: + self = .continue + case .depart: + self = .depart + case .exit: + self = .exit + case .keep: + self = .keep + case .ramp: + self = .ramp + case .roundaboutEnter: + self = .roundaboutEnter + case .roundaboutExit: + self = .roundaboutExit + case .roundaboutPass: + self = .roundaboutPass + case .turn: + self = .turn + case .uTurn: + self = .uTurn + case .sdkUnknown(_): + self = .sdkUnkown + case .continueHighway: + self = .continueHighway + case .enterHighway: + self = .enterHighway + } + } + + init(from type: GeoRoutesClientTypes.RouteFerryTravelStepType) { + switch type { + case .arrive: + self = .arrive + case .continue: + self = .continue + case .depart: + self = .depart + case .sdkUnknown(_): + self = .sdkUnkown + } + } + } // MARK: - GeoData diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/ViewModel/DirectionViewModel.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/ViewModel/DirectionViewModel.swift index e1c1424a..e0b6acfe 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/ViewModel/DirectionViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/ViewModel/DirectionViewModel.swift @@ -7,7 +7,7 @@ import Foundation import CoreLocation -import AWSLocation +import AWSGeoRoutes final class DirectionViewModel: DirectionViewModelProtocol { @@ -17,7 +17,7 @@ final class DirectionViewModel: DirectionViewModelProtocol { // to use in case of second call for routing private var cachedMapModel: MapModel? - var defaultTravelMode: [LocationClientTypes.TravelMode: Result] = [:] + var defaultTravelMode: [GeoRoutesClientTypes.RouteTravelMode: Result] = [:] var userLocation: (lat: Double?, long: Double?)? @@ -67,7 +67,7 @@ final class DirectionViewModel: DirectionViewModelProtocol { if text.isCoordinate() { let requestValue = text.convertTextToCoordinate() - let response = await service.searchWithPosition(position: requestValue, userLat: userLat, userLong: userLong) + let response = await service.reverseGeocode(position: requestValue, userLat: userLat, userLong: userLong) switch response { case .success(let results): self.presentation = results @@ -75,10 +75,12 @@ final class DirectionViewModel: DirectionViewModelProtocol { self.delegate?.searchResult(mapModel: model) case .failure(let error): let model = AlertModel(title: StringConstant.error, message: error.localizedDescription, cancelButton: nil) - self.delegate?.showAlert(model) + DispatchQueue.main.async { + self.delegate?.showAlert(model) + } } } else { - let response = await service.searchTextWithSuggestion(text: text, userLat: userLat, userLong: userLong) + let response = await service.searchWithSuggest(text: text, userLat: userLat, userLong: userLong) switch response { case .success(let results): self.presentation = results @@ -87,7 +89,9 @@ final class DirectionViewModel: DirectionViewModelProtocol { self.delegate?.searchResult(mapModel: model) case .failure(let error): let model = AlertModel(title: StringConstant.error, message: error.localizedDescription, cancelButton: nil) - self.delegate?.showAlert(model) + DispatchQueue.main.async { + self.delegate?.showAlert(model) + } } } @@ -105,7 +109,7 @@ final class DirectionViewModel: DirectionViewModelProtocol { if text.isCoordinate() { let requestValue = text.convertTextToCoordinate() - let response = await service.searchWithPosition(position: requestValue, userLat: userLat, userLong: userLong) + let response = await service.reverseGeocode(position: requestValue, userLat: userLat, userLong: userLong) switch response { case .success(let results): self.presentation = results @@ -113,7 +117,9 @@ final class DirectionViewModel: DirectionViewModelProtocol { self.delegate?.searchResult(mapModel: model) case .failure(let error): let model = AlertModel(title: StringConstant.error, message: error.localizedDescription, cancelButton: nil) - self.delegate?.showAlert(model) + DispatchQueue.main.async { + self.delegate?.showAlert(model) + } } } else { let result = await service.searchText(text: text, userLat: userLat, userLong: userLong) @@ -163,10 +169,8 @@ final class DirectionViewModel: DirectionViewModelProtocol { return true } - if let id = selectedItem.placeId { - let result = try await service.getPlace(with: id) - guard let result else { return false } - let mapModel = MapModel(model: result) + if selectedItem.placeId != nil { + let mapModel = MapModel(placeId: selectedItem.placeId, placeName: selectedItem.locationName, placeAddress: selectedItem.label, placeCity: selectedItem.locationCity, placeCountry: selectedItem.locationCountry, placeLat: selectedItem.lat, placeLong: selectedItem.long, distance: selectedItem.locationDistance) // cache the latest result for future usage self.cachedMapModel = mapModel try await self.delegate?.selectedPlaceResult(mapModel: [mapModel]) @@ -189,11 +193,11 @@ final class DirectionViewModel: DirectionViewModelProtocol { } } - func getCurrentNavigationLegsWith(_ type: RouteTypes) -> Result<[NavigationSteps], Error> { + func getCurrentNavigationLegsWith(_ type: RouteTypes) -> Result<[RouteLegDetails]?, Error> { let currentModel = getModel(for: type) switch currentModel { case .success(let presentation): - return .success(presentation.navigationSteps) + return .success(presentation.routeLegDetails) case .failure(let error): return .failure(error) case .none: @@ -215,14 +219,14 @@ final class DirectionViewModel: DirectionViewModelProtocol { departurePosition: CLLocationCoordinate2D, travelMode: RouteTypes = .car, avoidFerries: Bool = false, - avoidTolls: Bool = false) async throws -> (Data, DirectionVM)? { + avoidTolls: Bool = false) async throws -> ([Data], DirectionVM)? { defaultTravelMode = [:] selectedTravelMode = travelMode self.avoidFerries = avoidFerries self.avoidTolls = avoidTolls let result = try await routingService.calculateRouteWith(depaturePosition: departurePosition, destinationPosition: destinationPosition, - travelModes: [LocationClientTypes.TravelMode.car, LocationClientTypes.TravelMode.walking, LocationClientTypes.TravelMode.truck], + travelModes: [GeoRoutesClientTypes.RouteTravelMode.car, GeoRoutesClientTypes.RouteTravelMode.pedestrian, GeoRoutesClientTypes.RouteTravelMode.truck], avoidFerries: avoidFerries, avoidTolls: avoidTolls) self.defaultTravelMode = result @@ -233,13 +237,13 @@ final class DirectionViewModel: DirectionViewModelProtocol { case .success(let model): switch model.travelMode { case .car: - directionVM.carTypeDistane = model.distance.convertFormattedKMString() + directionVM.carTypeDistane = model.distance.formatToKmString() directionVM.carTypeDuration = model.duration.convertSecondsToMinString() - case .walking: - directionVM.walkingTypeDistance = model.distance.convertFormattedKMString() + case .pedestrian: + directionVM.walkingTypeDistance = model.distance.formatToKmString() directionVM.walkingTypeDuration = model.duration.convertSecondsToMinString() case .truck: - directionVM.truckTypeDistance = model.distance.convertFormattedKMString() + directionVM.truckTypeDistance = model.distance.formatToKmString() directionVM.truckTypeDuration = model.duration.convertSecondsToMinString() default: break } @@ -253,17 +257,27 @@ final class DirectionViewModel: DirectionViewModelProtocol { case .success(let travelMode): let encoder = JSONEncoder() do { - let jsonData = try encoder.encode(travelMode.lineString) - return (jsonData, directionVM) + var jsonDatas: [Data] = [] + if let legDetails = travelMode.routeLegDetails { + for leg in legDetails { + let jsonData = try encoder.encode(leg.lineString) + jsonDatas.append(jsonData) + } + } + return (jsonDatas, directionVM) } catch { print(String.errorJSONDecoder) } case .failure(let error): let alertModel = AlertModel(title: StringConstant.error, message: error.localizedDescription, cancelButton: nil) - self.delegate?.showAlert(alertModel) + DispatchQueue.main.async { + self.delegate?.showAlert(alertModel) + } case .none: let alertModel = AlertModel(title: StringConstant.error, message: StringConstant.failedToCalculateRoute, cancelButton: nil) - self.delegate?.showAlert(alertModel) + DispatchQueue.main.async { + self.delegate?.showAlert(alertModel) + } } return nil } @@ -274,7 +288,7 @@ final class DirectionViewModel: DirectionViewModelProtocol { return model } - private func convertToLocationTravelMode(type: RouteTypes) -> LocationClientTypes.TravelMode { - return LocationClientTypes.TravelMode(rawValue: type.title) ?? .walking + private func convertToLocationTravelMode(type: RouteTypes) -> GeoRoutesClientTypes.RouteTravelMode { + return GeoRoutesClientTypes.RouteTravelMode(rawValue: type.title) ?? .pedestrian } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Views/Common Views/RouteTypeView.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Views/Common Views/RouteTypeView.swift index 40b0f4a7..f2313945 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Views/Common Views/RouteTypeView.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Views/Common Views/RouteTypeView.swift @@ -9,12 +9,12 @@ import UIKit import SnapKit enum RouteTypes { - case walking, car, truck + case pedestrian, car, truck var title: String { switch self { - case .walking: - return "Walking" + case .pedestrian: + return "Pedestrian" case .car: return "Car" case .truck: @@ -24,7 +24,7 @@ enum RouteTypes { var image: UIImage { switch self { - case .walking: + case .pedestrian: return .navigationWalkingIcon.withRenderingMode(.alwaysTemplate) case .car: return .navigationCarIcon.withRenderingMode(.alwaysTemplate) diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Views/DirectionView.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Views/DirectionView.swift index 02011ecb..dd656c8d 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Views/DirectionView.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Direction/Views/DirectionView.swift @@ -24,7 +24,7 @@ final class DirectionView: UIView { private var model: DirectionVM! { didSet { carRouteTypeView.setDatas(distance: model.carTypeDistane, duration: model.carTypeDuration, isPreview: isPreview) - walkRouteTypeView.setDatas(distance: model.walkingTypeDistance, duration: model.walkingTypeDuration, isPreview: isPreview) + pedestrianRouteTypeView.setDatas(distance: model.walkingTypeDistance, duration: model.walkingTypeDuration, isPreview: isPreview) truckRouteTypeView.setDatas(distance: model.truckTypeDistance, duration: model.truckTypeDuration, isPreview: isPreview) } } @@ -52,8 +52,8 @@ final class DirectionView: UIView { view.backgroundColor = .searchBarBackgroundColor return view }() - private var walkRouteTypeView: RouteTypeView = RouteTypeView(viewType: .walking) - private var walkingSeperatorView: UIView = { + private var pedestrianRouteTypeView: RouteTypeView = RouteTypeView(viewType: .pedestrian) + private var pedestrianSeperatorView: UIView = { let view = UIView() view.backgroundColor = .searchBarBackgroundColor return view @@ -149,15 +149,15 @@ final class DirectionView: UIView { } - walkRouteTypeView.isSelectedHandle = { [weak self] state in + pedestrianRouteTypeView.isSelectedHandle = { [weak self] state in self?.changeSelectedTextFor(walkingType: state) Task { - try await self?.delegate?.changeRoute(type: .walking) + try await self?.delegate?.changeRoute(type: .pedestrian) } } - walkRouteTypeView.goButtonHandler = { [weak self] in - self?.delegate?.startNavigation(type: .walking) + pedestrianRouteTypeView.goButtonHandler = { [weak self] in + self?.delegate?.startNavigation(type: .pedestrian) } @@ -201,7 +201,7 @@ final class DirectionView: UIView { walkingType: Bool = false, truckType: Bool = false) { carRouteTypeView.isDotViewVisible(carType) - walkRouteTypeView.isDotViewVisible(walkingType) + pedestrianRouteTypeView.isDotViewVisible(walkingType) truckRouteTypeView.isDotViewVisible(truckType) } @@ -217,14 +217,14 @@ final class DirectionView: UIView { private func setupViews() { carRouteTypeView.accessibilityIdentifier = ViewsIdentifiers.Routing.carContainer - walkRouteTypeView.accessibilityIdentifier = ViewsIdentifiers.Routing.walkContainer + pedestrianRouteTypeView.accessibilityIdentifier = ViewsIdentifiers.Routing.pedestrianContainer truckRouteTypeView.accessibilityIdentifier = ViewsIdentifiers.Routing.truckContainer routeTypeStackView.removeArrangedSubViews() routeTypeStackView.addArrangedSubview(carRouteTypeView) routeTypeStackView.addArrangedSubview(carSeperatorView) - routeTypeStackView.addArrangedSubview(walkRouteTypeView) - routeTypeStackView.addArrangedSubview(walkingSeperatorView) + routeTypeStackView.addArrangedSubview(pedestrianRouteTypeView) + routeTypeStackView.addArrangedSubview(pedestrianSeperatorView) routeTypeStackView.addArrangedSubview(truckRouteTypeView) self.addSubview(routeOptions) @@ -252,11 +252,11 @@ final class DirectionView: UIView { $0.height.equalTo(1) } - walkRouteTypeView.snp.makeConstraints { + pedestrianRouteTypeView.snp.makeConstraints { $0.height.equalTo(72) } - walkingSeperatorView.snp.makeConstraints { + pedestrianSeperatorView.snp.makeConstraints { $0.height.equalTo(1) } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Contracts/ExploreMapStyleContract.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Contracts/ExploreMapStyleContract.swift index d442f32c..b5702247 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Contracts/ExploreMapStyleContract.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Contracts/ExploreMapStyleContract.swift @@ -10,7 +10,6 @@ import UIKit protocol ExploreMapStyleViewModelProtocol: AnyObject { var delegate: ExploreMapStyleViewModelOutputDelegate? { get set } func getItemsCount() -> Int - func getItem(with index: Int) -> MapStyleSourceType func loadData() func updateDataProviderWithMap(index: Int) } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Controller/ExploreMapStyleVC+TableView.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Controller/ExploreMapStyleVC+TableView.swift index 440d2d76..d60250a2 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Controller/ExploreMapStyleVC+TableView.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Controller/ExploreMapStyleVC+TableView.swift @@ -35,10 +35,9 @@ extension ExploreMapStyleVC: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: ExploreMapStyleCell.reuseId, for: indexPath) as? ExploreMapStyleCell else { - fatalError("Can't dequeu ExploreMapSytleCell") + fatalError("Can't deque ExploreMapStyleCell") } - let cellModel = viewModel.getItem(with: indexPath.row) - cell.updateDatas(source: cellModel, isSelected: indexPath.row == selectedIndex) + cell.updateDatas(isSelected: indexPath.row == selectedIndex) cell.collectionView.reloadData() return cell } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Controller/ExploreMapStyleVC.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Controller/ExploreMapStyleVC.swift index a08e13f7..9fef7715 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Controller/ExploreMapStyleVC.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Controller/ExploreMapStyleVC.swift @@ -7,12 +7,12 @@ import UIKit final class ExploreMapStyleVC: UIViewController { - let datas: [MapStyleSourceType] = [.esri, .here] var dismissHandler: VoidHandler? var selectedIndex: Int = 0 var headerView: ExploreMapStyleHeaderView = ExploreMapStyleHeaderView() - + var colorSegment: ColorSegmentControl? = nil + var politicalView = PoliticalView() var viewModel: ExploreMapStyleViewModelProtocol! { didSet { @@ -34,6 +34,10 @@ final class ExploreMapStyleVC: UIViewController { viewModel.loadData() } + override func viewDidAppear(_ animated: Bool) { + politicalView.setPoliticalView() + } + private func setupHandlers() { self.headerView.dismissHandler = { [weak self] in self?.dismissHandler?() @@ -41,9 +45,14 @@ final class ExploreMapStyleVC: UIViewController { } private func setupViews() { + let colorNames = [MapStyleColorType.light.colorName, MapStyleColorType.dark.colorName] + colorSegment = ColorSegmentControl(items: colorNames) + view.backgroundColor = .searchBarBackgroundColor self.view.addSubview(headerView) self.view.addSubview(tableView) + self.view.addSubview(colorSegment!) + self.view.addSubview(politicalView) headerView.snp.makeConstraints { $0.top.equalTo(self.view.safeAreaLayoutGuide) @@ -53,11 +62,31 @@ final class ExploreMapStyleVC: UIViewController { } tableView.snp.makeConstraints { - $0.top.equalTo(headerView.snp.bottom).offset(10) + $0.top.equalTo(headerView.snp.bottom) $0.leading.equalToSuperview().offset(5) $0.trailing.equalToSuperview().offset(-5) - $0.bottom.equalToSuperview() + $0.height.equalTo(260) } + + colorSegment?.snp.makeConstraints { + $0.top.equalTo(tableView.snp.bottom).offset(10) + $0.centerX.equalToSuperview() + if UIDevice.current.userInterfaceIdiom == .pad { + $0.width.equalTo(400) + } + else { + $0.width.equalToSuperview().offset(-50) + } + $0.height.equalTo(40) + } + + politicalView.snp.makeConstraints { + $0.top.equalTo(colorSegment!.snp.bottom).offset(50) + $0.centerX.equalToSuperview() + $0.width.equalToSuperview() + } + + politicalView.viewController = self } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/View Model/ExploreMapStyleCellViewModel.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/View Model/ExploreMapStyleCellViewModel.swift index 85920eda..33146aa6 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/View Model/ExploreMapStyleCellViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/View Model/ExploreMapStyleCellViewModel.swift @@ -10,8 +10,8 @@ import Foundation final class ExploreMapStyleCellViewModel: ExploreMapStyleCellViewModelProtocol { private let mapStyles: [MapStyleModel] - init(mapStyleSourceType: MapStyleSourceType) { - self.mapStyles = DefaultMapStyles.mapStyles.filter { $0.type == mapStyleSourceType } + init() { + self.mapStyles = DefaultMapStyles.mapStyles } var delegate: ExploreMapStyleCellViewModelOutputDelegate? @@ -32,7 +32,7 @@ final class ExploreMapStyleCellViewModel: ExploreMapStyleCellViewModelProtocol { func saveSelectedState(_ indexPath: IndexPath) { let item = mapStyles[indexPath.row] - saveUnitSettingsData(mapSource: item) + saveUnitSettingsData(mapStyle: item) delegate?.loadData(selectedIndex: indexPath.row) } } @@ -43,8 +43,9 @@ private extension ExploreMapStyleCellViewModel { return mapStyles.firstIndex(where: { $0.title == localData?.title }) } - func saveUnitSettingsData(mapSource: MapStyleModel) { - UserDefaultsHelper.saveObject(value: mapSource, key: .mapStyle) + func saveUnitSettingsData(mapStyle: MapStyleModel) { + UserDefaultsHelper.saveObject(value: mapStyle, key: .mapStyle) NotificationCenter.default.post(name: Notification.refreshMapView, object: nil, userInfo: nil) + NotificationCenter.default.post(name: Notification.validateMapColor, object: nil, userInfo: nil) } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/View Model/ExploreMapStyleViewModel.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/View Model/ExploreMapStyleViewModel.swift index 42db39af..793ff00e 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/View Model/ExploreMapStyleViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/View Model/ExploreMapStyleViewModel.swift @@ -10,46 +10,29 @@ import UIKit final class ExploreMapStyleViewModel: ExploreMapStyleViewModelProtocol { var delegate: ExploreMapStyleViewModelOutputDelegate? - private var datas: [MapStyleSourceType] = [.esri, .here] - + func loadData() { let index = getDataFromLocal() delegate?.updateTableView(item: index) } func updateDataProviderWithMap(index: Int) { - let mapStyle: MapStyleModel - switch getItem(with: index) { - case .esri: - mapStyle = DefaultUserSettings.mapStyle - case .here: - mapStyle = DefaultUserSettings.mapHereStyle - } + let mapStyle = DefaultUserSettings.mapStyle + let mapStyleColorType = DefaultUserSettings.mapStyleColorType UserDefaultsHelper.saveObject(value: mapStyle, key: .mapStyle) + UserDefaultsHelper.saveObject(value: mapStyleColorType, key: .mapStyleColorType) NotificationCenter.default.post(name: Notification.refreshMapView, object: nil, userInfo: nil) delegate?.updateTableView(item: index) } func getItemsCount() -> Int { - datas.count + 1 } - - func getItem(with index: Int) -> MapStyleSourceType { - datas[index] - } - } private extension ExploreMapStyleViewModel { func getDataFromLocal() -> Int { - let localData = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - - var currentIndex = 0 - if let type = localData?.type, - let index = datas.firstIndex(of: type) { - currentIndex = index - } - + let currentIndex = 0 return currentIndex } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/Cell/ExploreMapStyleCell.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/Cell/ExploreMapStyleCell.swift index e54e96b4..680c8a09 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/Cell/ExploreMapStyleCell.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/Cell/ExploreMapStyleCell.swift @@ -9,9 +9,9 @@ import UIKit import SnapKit private enum Constants { - static let cellSize = CGSize(width: 80, height: 106) + static let cellSize = CGSize(width: 160, height: 106) static let minimumLineSpacing: CGFloat = 36 - static let itemsCountPerRaw = 3 + static let itemsCountPerRow = 2 } final class ExploreMapStyleCell: UITableViewCell { @@ -27,20 +27,9 @@ final class ExploreMapStyleCell: UITableViewCell { return view }() - private var seperatorView: UIView = { - let view = UIView() - view.backgroundColor = .searchBarBackgroundColor - return view - }() - - private var mainView = AmazonCustomSelectableView() - - func updateDatas(source: MapStyleSourceType, isSelected: Bool) { - mapStyleViewModel = ExploreMapStyleCellViewModel(mapStyleSourceType: source) + func updateDatas(isSelected: Bool) { + mapStyleViewModel = ExploreMapStyleCellViewModel() mapStyleViewModel?.delegate = self - mainView.setValues(title: source.title, isSelected: isSelected) - updateMainViewConstraits(state: isSelected) - seperatorView.isHidden = !isSelected if isSelected { mapStyleViewModel?.loadLocalMapData() } @@ -66,7 +55,6 @@ final class ExploreMapStyleCell: UITableViewCell { self.selectionStyle = .none self.backgroundColor = .clear self.backgroundColor = .clear - mainView.isUserInteractionEnabled = false setupCollectionView() setupViews() } @@ -75,23 +63,6 @@ final class ExploreMapStyleCell: UITableViewCell { fatalError(.errorInitWithCoder) } - private func updateMainViewConstraits(state: Bool) { - mainView.snp.removeConstraints() - - if !state { - mainView.snp.makeConstraints { - $0.leading.trailing.equalToSuperview() - $0.centerY.equalToSuperview() - $0.height.equalTo(48) - } - } else { - mainView.snp.makeConstraints { - $0.top.leading.trailing.equalToSuperview() - $0.height.equalTo(48) - } - } - } - private func setupCollectionView() { collectionView.isUserInteractionEnabled = true collectionView.delegate = self @@ -101,8 +72,6 @@ final class ExploreMapStyleCell: UITableViewCell { private func setupViews() { self.contentView.addSubview(containerView) - containerView.addSubview(mainView) - containerView.addSubview(seperatorView) containerView.addSubview(collectionViewWrapperView) containerView.snp.makeConstraints { @@ -111,20 +80,8 @@ final class ExploreMapStyleCell: UITableViewCell { $0.trailing.equalToSuperview().offset(-8) $0.bottom.equalToSuperview().offset(-8) } - - mainView.snp.makeConstraints { - $0.top.leading.trailing.equalToSuperview() - $0.height.equalTo(48) - } - - seperatorView.snp.makeConstraints { - $0.top.equalTo(mainView.snp.bottom) - $0.leading.trailing.equalToSuperview() - $0.height.equalTo(1) - } - collectionViewWrapperView.snp.makeConstraints { - $0.top.equalTo(seperatorView.snp.bottom).offset(24) + $0.top.leading.trailing.equalToSuperview() $0.leading.trailing.equalToSuperview() $0.bottom.equalToSuperview().offset(-24) } @@ -163,9 +120,9 @@ extension ExploreMapStyleCell: UICollectionViewDelegate, UICollectionViewDataSou } func calculateMinimumInteritemSpacing() -> CGFloat { - let itemsCountPerRaw = CGFloat(Constants.itemsCountPerRaw) - let freeSpace = collectionView.frame.width - (Constants.cellSize.width * itemsCountPerRaw) - return (freeSpace / itemsCountPerRaw).rounded(.down) + let itemsCountPerRow = CGFloat(Constants.itemsCountPerRow) + let freeSpace = collectionView.frame.width - (Constants.cellSize.width * itemsCountPerRow) + return (freeSpace / itemsCountPerRow).rounded(.down) } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/Cell/PoliticalViewCell.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/Cell/PoliticalViewCell.swift new file mode 100644 index 00000000..fa3a0210 --- /dev/null +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/Cell/PoliticalViewCell.swift @@ -0,0 +1,91 @@ +import UIKit + +class PoliticalViewCell: UITableViewCell { + private let containerView: UIView = UIView() + private let contentCellView: UIView = UIView() + private var itemIcon = UILabel() + + private var itemTitle: UILabel = { + var label = UILabel() + label.font = .amazonFont(type: .regular, size: 16) + label.textColor = .mapDarkBlackColor + label.textAlignment = .left + label.text = "Political view" + return label + }() + + private var itemSubtitle: UILabel = { + var label = UILabel() + label.font = .amazonFont(type: .regular, size: 13) + label.textColor = .gray + label.textAlignment = .left + label.text = "Map representation for different countries" + label.numberOfLines = 0 + label.lineBreakMode = .byWordWrapping + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { + self.addSubview(containerView) + containerView.addSubview(contentCellView) + containerView.addSubview(itemIcon) + contentCellView.addSubview(itemTitle) + contentCellView.addSubview(itemSubtitle) + + containerView.snp.makeConstraints { + $0.top.leading.trailing.bottom.equalToSuperview() + } + + contentCellView.snp.makeConstraints { + $0.top.equalToSuperview().offset(10) + $0.leading.equalTo(itemIcon.snp.trailing).offset(2) + $0.trailing.equalToSuperview().offset(-20) + $0.bottom.equalToSuperview().offset(-10) + } + + itemIcon.snp.makeConstraints { + $0.width.height.equalTo(24) + $0.leading.equalToSuperview().offset(5) + $0.top.equalToSuperview().offset(10) + } + + itemTitle.snp.makeConstraints { + $0.leading.equalToSuperview().offset(5) + $0.top.equalToSuperview() + $0.trailing.lessThanOrEqualToSuperview().offset(-12) + } + + itemSubtitle.snp.makeConstraints { + $0.top.equalTo(itemTitle.snp.bottom).offset(5) + $0.leading.equalTo(itemIcon.snp.leading) + $0.trailing.equalToSuperview().offset(-20) + $0.bottom.equalTo(contentCellView.snp.bottom) + } + } + + func configure(with politicalView: PoliticalViewType) { + let countryFlag = flag(country: politicalView.flagCode) + itemTitle.text = politicalView.fullName + itemIcon.text = countryFlag + itemIcon.font = UIFont.systemFont(ofSize: 24) // Adjust flag size + itemSubtitle.text = politicalView.politicalDescription + } + + func flag(country: String) -> String { + let base: UInt32 = 127397 + var s = "" + for v in country.unicodeScalars { + s.unicodeScalars.append(UnicodeScalar(base + v.value)!) + } + return String(s) + } +} diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/ColorSegmentControl.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/ColorSegmentControl.swift new file mode 100644 index 00000000..60cf36ed --- /dev/null +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/ColorSegmentControl.swift @@ -0,0 +1,62 @@ +// +// ColorSegmentControl.swift +// LocationServices +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +import Foundation +import UIKit + +final class ColorSegmentControl: UISegmentedControl { + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + override init(items: [Any]?) { + super.init(items: items) + setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup() { + let colorNames = [MapStyleColorType.light.colorName, MapStyleColorType.dark.colorName] + + let lightImage = GeneralHelper.getImageAndText(image: UIImage(systemName: "sun.max")!, string: colorNames[0], isImageBeforeText: true) + let darkImage = GeneralHelper.getImageAndText(image: UIImage(systemName: "moon")!, string: colorNames[1], isImageBeforeText: true) + + self.setImage(lightImage, forSegmentAt: 0) + self.setImage(darkImage, forSegmentAt: 1) + self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.mapStyleTintColor], for: .selected) + self.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.black], for: .normal) + + let colorType = UserDefaultsHelper.getObject(value: MapStyleColorType.self, key: .mapStyleColorType) + self.selectedSegmentIndex = (colorType != nil && colorType! == .dark) ? 1 : 0 + + self.addTarget(self, action: #selector(mapColorChanged(_:)), for: .valueChanged) + + NotificationCenter.default.addObserver(self, selector: #selector(validateMapColor(_:)), name: Notification.validateMapColor, object: nil) + } + + @objc private func validateMapColor(_ notification: Notification) { + let mapStyle = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) + if mapStyle?.imageType == .hybrid || mapStyle?.imageType == .satellite { + self.isEnabled = false + self.selectedSegmentIndex = 0 + UserDefaultsHelper.saveObject(value: MapStyleColorType.light, key: .mapStyleColorType) + } + else { + self.isEnabled = true + } + } + + @objc func mapColorChanged(_ sender: UISegmentedControl) { + let colorType: MapStyleColorType = sender.selectedSegmentIndex == 1 ? .dark : .light + UserDefaultsHelper.saveObject(value: colorType, key: .mapStyleColorType) + NotificationCenter.default.post(name: Notification.refreshMapView, object: nil, userInfo: nil) + } +} diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/ExploreMapStyleHeaderView.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/ExploreMapStyleHeaderView.swift index 32d1cad7..3389a8a2 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/ExploreMapStyleHeaderView.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/ExploreMapStyleHeaderView.swift @@ -16,17 +16,7 @@ final class ExploreMapStyleHeaderView: UIView { var label = LargeTitleLabel(labelText: StringConstant.mapStyle) return label }() - - private var subtitle: UILabel = { - var label = UILabel() - label.font = .amazonFont(type: .regular, size: 13) - label.textColor = .searchBarTintColor - label.textAlignment = .left - label.text = "Changing data provider also affects Places & Routes API" - return label - }() - - + private lazy var closeButton: UIButton = { let button = UIButton(type: .system) button.accessibilityIdentifier = ViewsIdentifiers.General.closeButton @@ -43,7 +33,6 @@ final class ExploreMapStyleHeaderView: UIView { dismissHandler?() } - override init(frame: CGRect) { super.init(frame: frame) setupViews() @@ -56,7 +45,6 @@ final class ExploreMapStyleHeaderView: UIView { private func setupViews() { self.addSubview(containerView) containerView.addSubview(title) - containerView.addSubview(subtitle) self.addSubview(closeButton) containerView.snp.makeConstraints { @@ -68,14 +56,7 @@ final class ExploreMapStyleHeaderView: UIView { $0.top.equalToSuperview().offset(20) $0.height.equalTo(28) } - - subtitle.snp.makeConstraints { - $0.top.equalTo(title.snp.bottom).offset(2) - $0.leading.equalToSuperview().offset(16) - $0.trailing.equalToSuperview().offset(-16) - $0.height.equalTo(18) - } - + closeButton.snp.makeConstraints { $0.top.equalToSuperview().offset(14) $0.trailing.equalToSuperview().offset(-16) diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/PoliticalView.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/PoliticalView.swift new file mode 100644 index 00000000..1a9fac1d --- /dev/null +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/PoliticalView.swift @@ -0,0 +1,126 @@ +// +// PoliticalView.swift +// LocationServices +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +import UIKit + +final class PoliticalView: UIButton { + private var itemIcon: UIImageView = { + let image = UIImage(systemName: "globe.europe.africa.fill") + let iv = UIImageView(image: image) + iv.contentMode = .scaleAspectFit + iv.tintColor = .mapStyleTintColor + return iv + }() + + private var itemTitle: UILabel = { + var label = UILabel() + label.font = .amazonFont(type: .medium, size: 18) + label.textColor = .mapDarkBlackColor + label.textAlignment = .left + label.text = "Political view" + return label + }() + + private var itemSubtitle: UILabel = { + var label = UILabel() + label.font = .amazonFont(type: .regular, size: 13) + label.textColor = .gray + label.textAlignment = .left + label.text = "" + label.accessibilityIdentifier = ViewsIdentifiers.General.politicalViewSubtitle + return label + }() + + private var arrowIcon: UIImageView = { + let image = UIImage(systemName: "chevron.right") + let iv = UIImageView(image: image) + iv.contentMode = .scaleAspectFill + iv.tintColor = .searchBarTintColor + return iv + }() + + private var textStackView: UIStackView = { + let stackView = UIStackView() + stackView.alignment = .leading + stackView.axis = .vertical + stackView.distribution = .equalSpacing + return stackView + }() + + private var containerView: UIView = UIView() + public var viewController: UIViewController? + + override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { + self.accessibilityIdentifier = ViewsIdentifiers.General.politicalViewButton + textStackView.removeArrangedSubViews() + textStackView.addArrangedSubview(itemTitle) + textStackView.addArrangedSubview(itemSubtitle) + + self.addSubview(containerView) + containerView.addSubview(itemIcon) + containerView.addSubview(arrowIcon) + containerView.addSubview(textStackView) + + containerView.snp.makeConstraints { + $0.top.leading.trailing.bottom.equalToSuperview() + } + + itemIcon.snp.makeConstraints { + $0.height.width.equalTo(32) + $0.leading.equalToSuperview().offset(18) + $0.centerY.equalToSuperview() + } + + arrowIcon.snp.makeConstraints { + $0.height.width.equalTo(14) + $0.trailing.equalToSuperview().offset(-25) + $0.centerY.equalToSuperview() + } + + textStackView.snp.makeConstraints { + $0.height.equalTo(46) + $0.leading.equalTo(itemIcon.snp.trailing).offset(24) + $0.trailing.equalTo(arrowIcon.snp.leading) + $0.centerY.equalToSuperview() + } + + self.isUserInteractionEnabled = true + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture)) + self.addGestureRecognizer(tapGestureRecognizer) + + setPoliticalView() + } + + @objc private func handleTapGesture() { + let politicalVC = PoliticalViewController() + politicalVC.modalPresentationStyle = .formSheet + politicalVC.onDismiss = { + self.setPoliticalView() + } + viewController?.present(politicalVC, animated: true) + } + + public func setPoliticalView() { + if let politicalViewType = UserDefaultsHelper.getObject(value: PoliticalViewType.self, key: .politicalView) { + itemSubtitle.text = "\(politicalViewType.countryCode). \(politicalViewType.politicalDescription)" + itemSubtitle.textColor = .mapStyleTintColor + } + else { + itemSubtitle.text = "Map representation for different countries" + itemSubtitle.textColor = .gray + } + } +} diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/PoliticalViewController.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/PoliticalViewController.swift new file mode 100644 index 00000000..d732ca69 --- /dev/null +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Explore Map Style/Views/PoliticalViewController.swift @@ -0,0 +1,216 @@ +// +// PoliticalViewController.swift +// LocationServices +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +import UIKit + +class PoliticalViewController: UIViewController, UISearchBarDelegate, UITableViewDelegate, UITableViewDataSource { + + var filteredPoliticalViews: [PoliticalViewType] = [] + var selectedIndexPath: IndexPath? + var tableView: UITableView! + private var isSearching: Bool = false + + private var headerView: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + private var titleLabel: UILabel = { + let label = UILabel() + label.text = "Political View" + label.font = .boldSystemFont(ofSize: 18) + label.textAlignment = .center + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private lazy var closeButton: UIButton = { + let button = UIButton(type: .detailDisclosure) + button.accessibilityIdentifier = ViewsIdentifiers.General.politicalViewCloseButton + button.setImage(.closeIcon, for: .normal) + button.tintColor = .closeButtonTintColor + button.backgroundColor = .closeButtonBackgroundColor + button.isUserInteractionEnabled = true + button.layer.cornerRadius = 15 + button.addTarget(self, action: #selector(politicalViewDismiss), for: .touchUpInside) + return button + }() + + private lazy var clearButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Clear Selection", for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + button.addTarget(self, action: #selector(clearPoliticalView), for: .touchUpInside) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + private var searchBar: UISearchBar = { + let searchBar = UISearchBar() + searchBar.placeholder = "Search" + searchBar.image(for: .search, state: .normal) + searchBar.backgroundColor = .clear + searchBar.searchTextField.backgroundColor = .white + searchBar.searchTextField.borderStyle = .none + searchBar.layer.borderWidth = 0 + return searchBar + }() + + var onDismiss: (() -> Void)? + + @objc private func politicalViewDismiss() { + self.dismiss(animated: true) + onDismiss?() + } + + @objc private func clearPoliticalView() { + selectedIndexPath = nil + UserDefaultsHelper.removeObject(for: .politicalView) + tableView.reloadData() + NotificationCenter.default.post(name: Notification.refreshMapView, object: nil, userInfo: nil) + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .politicalListViewBackgroundColor + + headerView.addSubview(titleLabel) + headerView.addSubview(closeButton) + view.addSubview(headerView) + + searchBar.delegate = self + view.addSubview(searchBar) + + view.addSubview(clearButton) + + tableView = UITableView(frame: view.bounds) + tableView.accessibilityIdentifier = ViewsIdentifiers.General.politicalViewTable + tableView.dataSource = self + tableView.delegate = self + tableView.register(PoliticalViewCell.self, forCellReuseIdentifier: ViewsIdentifiers.General.politicalViewCell) + view.addSubview(tableView) + + headerView.snp.makeConstraints { + $0.top.leading.equalToSuperview().offset(10) + $0.trailing.equalToSuperview().offset(-10) + $0.height.equalTo(50) + } + + titleLabel.snp.makeConstraints { + $0.top.leading.equalToSuperview() + $0.centerY.equalToSuperview() + } + + closeButton.snp.makeConstraints { + $0.top.equalToSuperview().offset(14) + $0.trailing.equalToSuperview().offset(-11) + $0.height.width.equalTo(30) + } + + searchBar.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom).offset(10) + $0.leading.equalToSuperview().offset(10) + $0.trailing.equalToSuperview().offset(-10) + $0.height.equalTo(60) + } + + clearButton.snp.makeConstraints { + $0.top.equalTo(searchBar.snp.bottom) + $0.trailing.equalToSuperview().offset(-10) + } + + tableView.snp.makeConstraints { + $0.top.equalTo(clearButton.snp.bottom).offset(10) + $0.leading.equalToSuperview().offset(10) + $0.trailing.equalToSuperview().offset(-10) + $0.bottom.equalToSuperview().offset(-40) + } + + searchBar.backgroundColor = .clear + searchBar.layer.cornerRadius = 10 + searchBar.layer.masksToBounds = true + + tableView.layer.cornerRadius = 10 + tableView.layer.masksToBounds = true + + filteredPoliticalViews = PoliticalViewTypes + + let politicalViewType = UserDefaultsHelper.getObject(value: PoliticalViewType.self, key: .politicalView) + let selectedIndex = filteredPoliticalViews.firstIndex(where: { type in + if type.countryCode == politicalViewType?.countryCode { + return true + } + return false + }) + selectedIndexPath = selectedIndex != nil ? IndexPath(row: selectedIndex!, section: 0) : nil + tableView.reloadData() + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableView.automaticDimension + } + + func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + return 70 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return filteredPoliticalViews.count + } + + private var checkedIcon: UIImageView = { + let image = UIImage(systemName: "checkmark") + let iv = UIImageView(image: image) + iv.contentMode = .scaleAspectFill + iv.tintColor = .mapStyleTintColor + return iv + }() + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: ViewsIdentifiers.General.politicalViewCell, for: indexPath) as? PoliticalViewCell else { + return UITableViewCell() + } + let politicalView = filteredPoliticalViews[indexPath.row] + cell.configure(with: politicalView) + + if let selectedIndexPath = selectedIndexPath, selectedIndexPath == indexPath { + cell.accessoryView = checkedIcon + } else { + cell.accessoryView = nil + } + + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + selectedIndexPath = indexPath + UserDefaultsHelper.saveObject(value: filteredPoliticalViews[indexPath.row], key: .politicalView) + tableView.reloadData() + NotificationCenter.default.post(name: Notification.refreshMapView, object: nil, userInfo: nil) + } + + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + filterPoliticalViews(for: searchText) + } + + private func filterPoliticalViews(for query: String) { + if query.isEmpty { + isSearching = false + filteredPoliticalViews = PoliticalViewTypes + } else { + isSearching = true + filteredPoliticalViews = PoliticalViewTypes.filter { + $0.fullName.lowercased().contains(query.lowercased()) || + $0.politicalDescription.lowercased().contains(query.lowercased()) + } + } + tableView.reloadData() + } +} diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Builder/NavigationBuilder.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Builder/NavigationBuilder.swift index 29290c24..cf9f1e14 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Builder/NavigationBuilder.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Builder/NavigationBuilder.swift @@ -8,10 +8,10 @@ import UIKit final class NavigationBuilder { - static func create(steps: [NavigationSteps], summaryData: (totalDistance: Double, totalDuration: Double), firstDestionation: MapModel?, secondDestionation: MapModel?) -> NavigationVC { + static func create(routeLegDetails: [RouteLegDetails], summaryData: (totalDistance: Double, totalDuration: Double), firstDestination: MapModel?, secondDestination: MapModel?) -> NavigationVC { let vc = NavigationVC() let serivce = LocationService() - let vm = NavigationVCViewModel(service: serivce, steps: steps, summaryData: summaryData, firstDestionation: firstDestionation, secondDestionation: secondDestionation) + let vm = NavigationVCViewModel(service: serivce, routeLegDetails: routeLegDetails, summaryData: summaryData, firstDestination: firstDestination, secondDestination: secondDestination) vc.viewModel = vm return vc } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Controller/NavigationVC.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Controller/NavigationVC.swift index 3d8c32e0..ef05e961 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Controller/NavigationVC.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Controller/NavigationVC.swift @@ -83,15 +83,15 @@ final class NavigationVC: UIViewController { @objc private func closeScreen() { var lat: Double? = nil var long: Double? = nil - if viewModel.firstDestionation?.placeName == StringConstant.myLocation { - lat = viewModel.firstDestionation?.placeLat - long = viewModel.firstDestionation?.placeLong - } else if viewModel.secondDestionation?.placeName == StringConstant.myLocation { - lat = viewModel.secondDestionation?.placeLat - long = viewModel.secondDestionation?.placeLong + if viewModel.firstDestination?.placeName == StringConstant.myLocation { + lat = viewModel.firstDestination?.placeLat + long = viewModel.firstDestination?.placeLong + } else if viewModel.secondDestination?.placeName == StringConstant.myLocation { + lat = viewModel.secondDestination?.placeLat + long = viewModel.secondDestination?.placeLong } - delegate?.showDirections(isRouteOptionEnabled: true, firstDestionation: viewModel.firstDestionation, secondDestionation: viewModel.secondDestionation, lat: lat, long: long) + delegate?.showDirections(isRouteOptionEnabled: true, firstDestination: viewModel.firstDestination, secondDestination: viewModel.secondDestination, lat: lat, long: long) delegate?.closeNavigationScene() } @@ -151,7 +151,7 @@ private extension NavigationVC { func sendMapViewData() { let datas = viewModel.getData() if let mapData = datas[safe: 0] { - let mapHeaderData = (distance: mapData.distance, street: mapData.streetAddress) + let mapHeaderData = (distance: mapData.distance, street: mapData.instruction) let summaryData = viewModel.getSummaryData() let data: [String: Any] = ["MapViewValues" : mapHeaderData, "SummaryData": summaryData] NotificationCenter.default.post(name: Notification.Name("UpdateMapViewValues"), object: nil, userInfo: data) @@ -163,7 +163,7 @@ private extension NavigationVC { } @objc private func updateNavigationSteps(_ notification: Notification) { - guard let datas = notification.userInfo?["steps"] as? (steps: [NavigationSteps], sumData: (totalDistance: Double, totalDuration: Double)) else { return } - viewModel.update(steps: datas.steps, summaryData: datas.sumData) + guard let datas = notification.userInfo?["routeLegDetails"] as? (routeLegDetails: [RouteLegDetails], sumData: (totalDistance: Double, totalDuration: Double)) else { return } + viewModel.update(routeLegDetails: datas.routeLegDetails, summaryData: datas.sumData) } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Presentation/NavigationPresentation.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Presentation/NavigationPresentation.swift index 7075f851..8a8ae9a5 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Presentation/NavigationPresentation.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/Presentation/NavigationPresentation.swift @@ -11,7 +11,8 @@ struct NavigationPresentation { var id: Int var duration: String var distance: String - var streetAddress: String + var instruction: String + var stepType: NavigationStepType } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/View/Cell/NavigationVCCell.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/View/Cell/NavigationVCCell.swift index ab3d4f20..14f9fd8c 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/View/Cell/NavigationVCCell.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/View/Cell/NavigationVCCell.swift @@ -8,19 +8,21 @@ import UIKit import SnapKit -enum StepType { +enum StepState { case first, last } struct NavigationCellModel { - var stepType: StepType - var streetAddress: String + var stepState: StepState + var instruction: String var distance: String + var stepType: NavigationStepType - init(model: NavigationPresentation, stepType: StepType? = .first) { + init(model: NavigationPresentation, stepState: StepState? = .first) { self.distance = model.distance - self.streetAddress = model.streetAddress - self.stepType = stepType ?? .first + self.instruction = model.instruction + self.stepState = stepState ?? .first + self.stepType = model.stepType } } @@ -35,13 +37,12 @@ final class NavigationVCCell: UITableViewCell { var model: NavigationCellModel! { didSet { - self.streetLabel.text = model.streetAddress + self.streetLabel.text = model.instruction self.distanceLabel.text = model.distance - self.stepImage.image = model.stepType == .first ? .stepIcon : .selectedPlace - - switch model.stepType { + + switch model.stepState { case .first: - self.stepImage.image = .stepIcon + self.stepImage.image = model.stepType.image stepLine.isHidden = false case .last: self.stepImage.image = .selectedPlace diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/ViewModel/NavigationViewModel.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/ViewModel/NavigationViewModel.swift index 8496b24a..5fd36b38 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/ViewModel/NavigationViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Navigation/ViewModel/NavigationViewModel.swift @@ -10,22 +10,22 @@ import Foundation final class NavigationVCViewModel { var delegate: NavigationViewModelOutputDelegate? var service: LocationService - var steps: [NavigationSteps] + var routeLegDetails: [RouteLegDetails] var presentation: [NavigationPresentation] = [] private var summaryData: (totalDistance: Double, totalDuration: Double) let dispatchGroup = DispatchGroup() - private(set) var firstDestionation: MapModel? - private(set) var secondDestionation: MapModel? + private(set) var firstDestination: MapModel? + private(set) var secondDestination: MapModel? - init(service: LocationService, steps: [NavigationSteps], summaryData: (totalDistance: Double, totalDuration: Double), firstDestionation: MapModel?, secondDestionation: MapModel?) { + init(service: LocationService, routeLegDetails: [RouteLegDetails], summaryData: (totalDistance: Double, totalDuration: Double), firstDestination: MapModel?, secondDestination: MapModel?) { self.service = service - self.steps = steps + self.routeLegDetails = routeLegDetails self.summaryData = summaryData - self.firstDestionation = firstDestionation - self.secondDestionation = secondDestionation + self.firstDestination = firstDestination + self.secondDestination = secondDestination Task { - await fetchStreetNames() + await populateNavigationSteps() } } @@ -42,42 +42,29 @@ final class NavigationVCViewModel { } } - private func fetchStreetNames() async { + private func populateNavigationSteps() async { let manager = PresentationManager() - - await withTaskGroup(of: Void.self) { group in - for (id, step) in steps.enumerated() { - group.addTask { - let position = step.startPosition - let response = await self.service.searchWithPosition(position: position, userLat: nil, userLong: nil) - - switch response { - case .success(let results): - guard let result = results.first else { return } - let model = NavigationPresentation(id: id, duration: step.duration.convertSecondsToMinString(), distance: step.distance.convertFormattedKMString(), streetAddress: result.placeLabel ?? "") - await manager.addPresentation(model) - case .failure: - break - } - } + for legDetails in routeLegDetails { + for (id, step) in legDetails.navigationSteps.enumerated() { + let model = NavigationPresentation(id: id, duration: step.duration.convertSecondsToMinString(), distance: step.distance.formatToKmString(), instruction: step.instruction, stepType: step.type) + await manager.addPresentation(model) } } - let sortedPresentation = await manager.getSortedPresentation() self.presentation = sortedPresentation self.delegate?.updateResults() } - func update(steps: [NavigationSteps], summaryData: (totalDistance: Double, totalDuration: Double)) { - self.steps = steps + func update(routeLegDetails: [RouteLegDetails], summaryData: (totalDistance: Double, totalDuration: Double)) { + self.routeLegDetails = routeLegDetails self.summaryData = summaryData Task { - await fetchStreetNames() + await populateNavigationSteps() } } func getSummaryData() -> (totalDistance: String, totalDuration: String) { - return (summaryData.totalDistance.convertFormattedKMString(), + return (summaryData.totalDistance.formatToKmString(), summaryData.totalDuration.convertSecondsToMinString()) } @@ -87,9 +74,9 @@ final class NavigationVCViewModel { for i in 0...presentation.count - 1 { let item = presentation[i] if i == presentation.count - 1 { - model.append(NavigationCellModel(model: item, stepType: .last)) + model.append(NavigationCellModel(model: item, stepState: .last)) } else { - model.append(NavigationCellModel(model: item, stepType: .first)) + model.append(NavigationCellModel(model: item, stepState: .first)) } } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/POICard/Controller/POICardVC.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/POICard/Controller/POICardVC.swift index 5e7fd193..eff06223 100755 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/POICard/Controller/POICardVC.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/POICard/Controller/POICardVC.swift @@ -146,8 +146,8 @@ extension POICardVC: POICardViewModelOutputDelegate { func showDirections(secondDestination: MapModel) { DispatchQueue.main.async { [self] in delegate?.showDirections(isRouteOptionEnabled: true, - firstDestionation: nil, - secondDestionation: secondDestination, + firstDestination: nil, + secondDestination: secondDestination, lat: userLocation?.lat, long: userLocation?.long) } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/POICard/ViewModel/POICardViewModel.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/POICard/ViewModel/POICardViewModel.swift index 0fa0a369..396a22e8 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/POICard/ViewModel/POICardViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/POICard/ViewModel/POICardViewModel.swift @@ -47,22 +47,6 @@ final class POICardViewModel: POICardViewModelProcotol { guard let placeLat = cardData.placeLat, let placeLong = cardData.placeLong else { return } let destinationPosition = CLLocationCoordinate2D(latitude: placeLat, longitude: placeLong) - - let currentMapStyle = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - switch currentMapStyle?.type { - case .esri, .none: - let userLocation = CLLocation(location: userLocation) - let placeLocation = CLLocation(location: destinationPosition) - - let distance = userLocation.distance(from: placeLocation) - guard distance < NumberConstants.fourHundredKMInMeters else { - delegate?.populateDatas(cardData: cardData, isLoadingData: false, errorMessage: StringConstant.esriDistanceError, errorInfoMessage: nil) - return - } - case .here: - break - } - delegate?.populateDatas(cardData: cardData, isLoadingData: isLoading, errorMessage: nil, errorInfoMessage: nil) let result = try await routingService.calculateRouteWith(depaturePosition: userLocation, destinationPosition: destinationPosition, travelModes: [.car], avoidFerries: true, avoidTolls: true) @@ -71,7 +55,7 @@ final class POICardViewModel: POICardViewModelProcotol { case .success(let direction): guard !(self.datas.isEmpty) else { break } - self.datas[0].distance = direction.distance.convertKMToMeters() + self.datas[0].distance = direction.distance self.datas[0].duration = direction.duration.convertSecondsToMinString() case .failure(let error): responseError = error diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/Presentation/SearchPresentation.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/Presentation/SearchPresentation.swift index 19634880..e6240928 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/Presentation/SearchPresentation.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/Presentation/SearchPresentation.swift @@ -6,7 +6,7 @@ // SPDX-License-Identifier: MIT-0 import Foundation -import AWSLocation +import AWSGeoPlaces import CoreLocation @@ -43,8 +43,8 @@ struct SearchPresentation { init(placeId: String, model: GetPlaceOutput) { self.placeId = placeId - self.countryName = model.place?.country - if let fullAddress = model.place?.label?.formatAddressField() { + self.countryName = model.address?.country?.name + if let fullAddress = model.address?.label?.formatAddressField() { self.name = fullAddress[safe: 0] ?? "" self.fullLocationAddress = fullAddress[safe: 1] ?? "" } else { @@ -52,31 +52,31 @@ struct SearchPresentation { self.fullLocationAddress = nil } self.distance = 0 //No user location is determined by the app this constructor will be called - if let point = model.place?.geometry?.point { + if let point = model.position { self.placeLong = point[0] self.placeLat = point[1] } else { self.placeLong = nil self.placeLat = nil } - self.cityName = model.place?.municipality - self.placeLabel = model.place?.label + self.cityName = model.address?.district + self.placeLabel = model.address?.label } - init(model: LocationClientTypes.SearchForSuggestionsResult, placeLat: Double? = nil, placeLong: Double? = nil, userLocation: CLLocation? = nil) { + init(model: GeoPlacesClientTypes.SearchTextResultItem, placeLat: Double? = nil, placeLong: Double? = nil, userLocation: CLLocation? = nil) { self.placeId = model.placeId - self.countryName = nil + self.countryName = model.address?.country?.name self.placeLong = placeLong self.placeLat = placeLat - self.cityName = nil + self.cityName = model.address?.district - if let fullAddress = model.text?.formatAddressField(), + if let fullAddress = model.address?.label?.formatAddressField(), !fullAddress.isEmpty { self.name = fullAddress[safe: 0] ?? "" self.fullLocationAddress = fullAddress[safe: 1] ?? "" } else { - self.name = model.text - self.fullLocationAddress = model.text + self.name = model.title + self.fullLocationAddress = model.address?.label } if let placeLat, let placeLong, let userLocation { @@ -85,100 +85,118 @@ struct SearchPresentation { } else { self.distance = nil } - self.placeLabel = model.text + self.placeLabel = model.title } - init(model: GetPlaceOutput) { - self.placeId = nil - if let fullAddress = model.place?.label?.formatAddressField() { + init(model: GeoPlacesClientTypes.SearchNearbyResultItem, placeLat: Double? = nil, placeLong: Double? = nil, userLocation: CLLocation? = nil) { + self.placeId = model.placeId + self.countryName = model.address?.country?.name + self.placeLong = placeLong + self.placeLat = placeLat + self.cityName = model.address?.district + + if let fullAddress = model.address?.label?.formatAddressField(), + !fullAddress.isEmpty { self.name = fullAddress[safe: 0] ?? "" self.fullLocationAddress = fullAddress[safe: 1] ?? "" } else { - self.name = nil - self.fullLocationAddress = nil + self.name = model.title + self.fullLocationAddress = model.address?.label } - self.countryName = model.place?.country - if let point = model.place?.geometry?.point { - self.placeLong = point[0] - self.placeLat = point[1] + + if let placeLat, let placeLong, let userLocation { + let placeLocation = CLLocation(latitude: placeLat, longitude: placeLong) + self.distance = placeLocation.distance(from: userLocation) } else { - self.placeLong = nil - self.placeLat = nil + self.distance = nil } - self.distance = nil - self.cityName = model.place?.municipality - self.placeLabel = model.place?.label + self.placeLabel = model.title } - init(model: LocationClientTypes.SearchForTextResult, userLocation: CLLocation?) { - + init(model: GeoPlacesClientTypes.ReverseGeocodeResultItem, userLocation: CLLocation? = nil) { self.placeId = model.placeId - self.countryName = model.place?.country - - if let fullAddress = model.place?.label?.formatAddressField() { + self.countryName = model.address?.country?.name + self.placeLong = model.position?.first + self.placeLat = model.position?.last + self.cityName = model.address?.district + + if let fullAddress = model.address?.label?.formatAddressField(), + !fullAddress.isEmpty { self.name = fullAddress[safe: 0] ?? "" self.fullLocationAddress = fullAddress[safe: 1] ?? "" } else { - self.name = nil - self.fullLocationAddress = nil - } - - if let point = model.place?.geometry?.point { - self.placeLong = point[0] - self.placeLat = point[1] - } else { - self.placeLong = nil - self.placeLat = nil + self.name = model.title + self.fullLocationAddress = model.address?.label } - - //In getPlace API there is no ability to send user location in request, - //so no destination is provided in response and needed to be recalculated - // with user location and place location - if let placeLat, let placeLong, let userLocation { + if let placeLat = self.placeLat, let placeLong = self.placeLong, let userLocation { let placeLocation = CLLocation(latitude: placeLat, longitude: placeLong) self.distance = placeLocation.distance(from: userLocation) } else { - self.distance = model.distance + self.distance = nil } - - self.cityName = model.place?.municipality - self.placeLabel = model.place?.label + self.placeLabel = model.title } - init(model: LocationClientTypes.SearchForPositionResult, userLocation: CLLocation?) { - + init(model: GetPlaceOutput) { self.placeId = model.placeId - self.countryName = model.place?.country - - if let fullAddress = model.place?.label?.formatAddressField() { + if let fullAddress = model.address?.label?.formatAddressField() { self.name = fullAddress[safe: 0] ?? "" self.fullLocationAddress = fullAddress[safe: 1] ?? "" } else { self.name = nil self.fullLocationAddress = nil } - - if let point = model.place?.geometry?.point { - //class LocationService -> func searchWithPosition -> func searchWithPositionRequest -> AWSLocationSearchPlaceIndexForPositionRequest - // AWSLocationSearchPlaceIndexForPositionRequest - geometry contains [longitude, latitude] + self.countryName = model.address?.country?.name + if let point = model.position { self.placeLong = point[0] self.placeLat = point[1] } else { self.placeLong = nil self.placeLat = nil } - - //there is no ability to send user location in request, - //so destination is incorrect in response - //and needed to be recalculated - if let placeLat, let placeLong, let userLocation { - let placeLocation = CLLocation(latitude: placeLat, longitude: placeLong) - self.distance = placeLocation.distance(from: userLocation) + self.distance = nil + self.cityName = model.address?.district + self.placeLabel = model.title + } + + init(model: GeoPlacesClientTypes.SuggestResultItem, userLocation: CLLocation? = nil) { + self.placeId = model.place?.placeId + if let fullAddress = model.place?.address?.label?.formatAddressField() { + self.name = fullAddress[safe: 0] ?? "" + self.fullLocationAddress = fullAddress[safe: 1] ?? "" } else { - self.distance = model.distance + self.name = model.title + self.fullLocationAddress = nil } - - self.cityName = model.place?.municipality - self.placeLabel = model.place?.label + self.countryName = model.place?.address?.country?.name + self.placeLong = model.place?.position?.first + self.placeLat = model.place?.position?.last + if let distance = model.place?.distance { + self.distance = Double(distance) + } + else { + self.distance = 0 + } + self.cityName = model.place?.address?.district + self.placeLabel = model.title } + + init(model: GeoPlacesClientTypes.SearchTextResultItem, userLocation: CLLocation?) { + self.placeId = model.placeId + self.countryName = model.address?.country?.name + + if let fullAddress = model.address?.label?.formatAddressField() { + self.name = fullAddress[safe: 0] ?? "" + self.fullLocationAddress = fullAddress[safe: 1] ?? "" + } else { + self.name = nil + self.fullLocationAddress = nil + } + self.placeLong = model.position?.first + self.placeLat = model.position?.last + self.distance = Double(model.distance) + + self.cityName = model.address?.district + self.placeLabel = model.title + } } diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/ViewModel/SearchViewModel.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/ViewModel/SearchViewModel.swift index 293a80ad..f25b5786 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/ViewModel/SearchViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/ViewModel/SearchViewModel.swift @@ -31,7 +31,7 @@ final class SearchViewModel: SearchViewModelProcotol { if text.isCoordinate() { let requestValue = text.convertTextToCoordinate() - let response = await service.searchWithPosition(position: requestValue, userLat: userLat, userLong: userLong) + let response = await service.reverseGeocode(position: requestValue, userLat: userLat, userLong: userLong) switch response { case .success(let results): self.presentation = results @@ -44,7 +44,7 @@ final class SearchViewModel: SearchViewModelProcotol { } } } else { - let response = await service.searchTextWithSuggestion(text: text, userLat: userLat, userLong: userLong) + let response = await service.searchWithSuggest(text: text, userLat: userLat, userLong: userLong) switch response { case .success(let results): self.presentation = results @@ -68,7 +68,7 @@ final class SearchViewModel: SearchViewModelProcotol { if text.isCoordinate() { let requestValue = text.convertTextToCoordinate() - let response = await service.searchWithPosition(position: requestValue, userLat: userLat, userLong: userLong) + let response = await service.reverseGeocode(position: requestValue, userLat: userLat, userLong: userLong) switch response { case .success(let results): self.presentation = results diff --git a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/Views/Cell/SearchCell.swift b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/Views/Cell/SearchCell.swift index 27700e02..577f307d 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/Views/Cell/SearchCell.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Sub-Scenes/Search/Views/Cell/SearchCell.swift @@ -55,7 +55,7 @@ final class SearchCell: UITableViewCell { locationAddress.snp.remakeConstraints { $0.top.equalTo(locationTitle.snp.bottom).offset(5) $0.leading.equalTo(locationTitle.snp.leading) - $0.trailing.equalToSuperview().offset(-20) + $0.trailing.equalToSuperview().offset(-30) $0.bottom.equalTo(contentCellView.snp.bottom) } } else { diff --git a/LocationServices/LocationServices/Scenes/Explore/ViewModel/ExploreViewModel.swift b/LocationServices/LocationServices/Scenes/Explore/ViewModel/ExploreViewModel.swift index d9375c0e..b1af9218 100644 --- a/LocationServices/LocationServices/Scenes/Explore/ViewModel/ExploreViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Explore/ViewModel/ExploreViewModel.swift @@ -7,7 +7,7 @@ import Foundation import CoreLocation -import AWSLocation +import AWSGeoRoutes import UIKit final class ExploreViewModel: ExploreViewModelProtocol { @@ -66,13 +66,13 @@ final class ExploreViewModel: ExploreViewModelProtocol { guard let selectedRoute else { return } self.selectedRoute?.departurePosition = userLocation - let travelMode = LocationClientTypes.TravelMode(routeType: selectedRoute.travelMode) ?? .walking + let travelMode = GeoRoutesClientTypes.RouteTravelMode(routeType: selectedRoute.travelMode) ?? .pedestrian let travelModes = [travelMode] let result = try await routingService.calculateRouteWith(depaturePosition: userLocation, destinationPosition: selectedRoute.destinationPosition, travelModes: travelModes, avoidFerries: selectedRoute.avoidFerries, - avoidTolls: selectedRoute.avoidTolls) //{ [weak self] response in + avoidTolls: selectedRoute.avoidTolls) for route in result { self.delegate?.routeReCalculated(route: try route.value.get(), departureLocation: userLocation, destinationLocation: selectedRoute.destinationPosition, routeType: selectedRoute.travelMode) } @@ -89,7 +89,7 @@ final class ExploreViewModel: ExploreViewModelProtocol { } func loadPlace(for coordinates: CLLocationCoordinate2D, userLocation: CLLocationCoordinate2D?) async { - let result = await locationService.searchWithPosition(position: [coordinates.longitude, coordinates.latitude], userLat: userLocation?.latitude, userLong: userLocation?.longitude) + let result = await locationService.reverseGeocode(position: [coordinates.longitude, coordinates.latitude], userLat: userLocation?.latitude, userLong: userLocation?.longitude) DispatchQueue.main.async { do { if let model = try result.get().first { diff --git a/LocationServices/LocationServices/Scenes/Explore/Views/ExploreView.swift b/LocationServices/LocationServices/Scenes/Explore/Views/ExploreView.swift index d2193287..fcda442f 100644 --- a/LocationServices/LocationServices/Scenes/Explore/Views/ExploreView.swift +++ b/LocationServices/LocationServices/Scenes/Explore/Views/ExploreView.swift @@ -60,7 +60,6 @@ final class ExploreView: UIView, NavigationMapProtocol { private var wasCenteredByUserLocation = false private var searchDatas: [MapModel] = [] - private var signingDelegate: MLNOfflineStorageDelegate? private var containerView: UIView = UIView() let searchBarView: SearchBarView = SearchBarView(becomeFirstResponder: true) @@ -315,27 +314,26 @@ final class ExploreView: UIView, NavigationMapProtocol { delegate?.showDirectionView(userLocation: (mapView.locationManager.authorizationStatus == .authorizedAlways || mapView.locationManager.authorizationStatus == .authorizedWhenInUse) ? mapView.userLocation?.coordinate : nil) } - func drawCalculatedRouteWith(_ data: Data, departureLocation: CLLocationCoordinate2D, destinationLocation: CLLocationCoordinate2D, isRecalculation: Bool, routeType: RouteTypes) { + func drawCalculatedRouteWith(_ geoDatas: [Data], departureLocation: CLLocationCoordinate2D, destinationLocation: CLLocationCoordinate2D, isRecalculation: Bool, routeType: RouteTypes) { DispatchQueue.main.async { let isDashedLine: Bool switch routeType { - case .walking: + case .pedestrian: isDashedLine = true case .car, .truck: isDashedLine = false } + self.createAnnotationsForDirection(departureCoordinates: departureLocation, destinationCoordinates: destinationLocation) - self.drawPolyline(self.mapView, geoJson: data, isDashedLine: isDashedLine) - - let dashedData = self.mapMode == .search ? data : Data() - self.drawDashedLine(self.mapView, geoJson: dashedData, departureCoordinates: departureLocation, destinationCoordinates: destinationLocation) + self.drawPolyline(self.mapView, geoDatas: geoDatas, isDashedLine: isDashedLine) guard !isRecalculation else { return } - self.createAnnotationsForDirection(departureCoordinates: departureLocation, destinationCoordinates: destinationLocation) - - let routeCoordinates = self.getCoordinates(from: data) + var routeCoordinates: [CLLocationCoordinate2D] = [] + for data in geoDatas { + routeCoordinates.append(contentsOf: self.getCoordinates(from: data)) + } let boundsCoordinates = routeCoordinates.isEmpty ? [departureLocation, destinationLocation] : routeCoordinates let coordinateBounds = MLNCoordinateBounds.create(from: boundsCoordinates) let edgePadding = self.configureMapEdgePadding() @@ -435,31 +433,17 @@ final class ExploreView: UIView, NavigationMapProtocol { } } + var mapLoaded = false func setupMapView() { - let identityPoolId: String - if let customModel = UserDefaultsHelper.getObject(value: CustomConnectionModel.self, key: .awsConnect) { - identityPoolId = customModel.identityPoolId - } else { - identityPoolId = Bundle.main.object(forInfoDictionaryKey: Constants.dictinaryKeyIdentityPoolId) as! String - } - - let regionName = identityPoolId.toRegionString() - let mapName = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - DispatchQueue.main.async { [self] in - signingDelegate = AWSSignatureV4Delegate(region: regionName) - MLNOfflineStorage.shared.delegate = signingDelegate - mapView.styleURL = URL(string: "https://maps.geo.\(regionName).amazonaws.com/maps/v0/maps/\(mapName?.imageType.mapName ?? "EsriLight")/style-descriptor") + if !mapLoaded { + mapView.styleURL = DefaultMapStyles.getMapStyleUrl() + locateMeAction(force: true) + mapView.showsUserLocation = true + mapView.accessibilityIdentifier = ViewsIdentifiers.General.mapRendering + amazonMapLogo.tintColor = GeneralHelper.getAmazonMapLogo() + } } - - mapView.accessibilityIdentifier = ViewsIdentifiers.General.mapRendering - - - // it is just to force to redraw the mapView - mapView.zoomLevel = mapView.zoomLevel + 0.01 - amazonMapLogo.tintColor = GeneralHelper.getAmazonMapLogo(mapImageType: mapName?.imageType) - mapView.showsUserLocation = true - locateMeAction(force: true) } func setupTapGesture() { @@ -504,21 +488,6 @@ final class ExploreView: UIView, NavigationMapProtocol { setupBottomStack(bottomStackOffset: mapLayerBottomOffset) } -// func setupAmazonLogo(leadingOffset: CGFloat?, bottomOffset: CGFloat?) { -// let leadingOffset = leadingOffset ?? Constant.defaultHorizontalOffset -// let bottomOffset = bottomOffset ?? Constant.amazonLogoBottomOffset -// amazonMapLogo.snp.remakeConstraints { -// $0.leading.equalToSuperview().offset(leadingOffset) -// if isiPad { -// $0.bottom.equalTo(safeAreaLayoutGuide).offset(bottomOffset) -// } else { -// $0.bottom.equalTo(searchBarView.snp.top).offset(bottomOffset) -// } -// $0.height.equalTo(Constant.amazonLogoHeight) -// $0.width.equalTo(Constant.amazonLogoWidth) -// } -// } - func setupAmazonLogo(bottomOffset: CGFloat?) { let bottomOffset = bottomOffset ?? (isiPad ? 0 : Constants.searchBarHeight) + Constants.bottomStackViewOffset @@ -594,45 +563,38 @@ private extension ExploreView { self.mapView.addAnnotations(pointsToAdd) } - func drawPolyline(_ mapView: MLNMapView, geoJson: Data, isDashedLine: Bool) { - // Add our GeoJSON data to the map as an MLNGeoJSONSource. - // We can then reference this data from an MLNStyleLayer. - - // MLNMapView.style is optional, so you must guard against it not being set. + func drawPolyline(_ mapView: MLNMapView, geoDatas: [Data], isDashedLine: Bool) { + // Ensure the style is loaded guard let style = mapView.style else { return } - - if let layer = style.layer(withIdentifier: "polyline") { - style.removeLayer(layer) - } - - if let layer2 = style.layer(withIdentifier: "polyline-case") { - style.removeLayer(layer2) + + // Remove existing layers if any + ["polyline", "polyline-case", "trails-path"].forEach { layerID in + if let layer = style.layer(withIdentifier: layerID) { + style.removeLayer(layer) + } } - - if let layer3 = style.layer(withIdentifier: "trails-path") { - style.removeLayer(layer3) + + // Convert each GeoJSON Data into an MLNShape and add to the array of shapes + let shapes: [MLNShape] = geoDatas.compactMap { data in + return try? MLNShape(data: data, encoding: String.Encoding.utf8.rawValue) } - guard let shapeFromGeoJSON = try? MLNShape(data: geoJson, - encoding: String.Encoding.utf8.rawValue) else { - fatalError("Could not generate MLNShape") + // Create source from the combined shape and add it to the style + let source = MLNShapeSource(identifier: "polyline-source", shapes: shapes, options: nil) + if let existingSource = style.source(withIdentifier: "polyline-source") { + style.removeSource(existingSource) } - - // create source and add it to the style - let source = self.createSource(style, fromShape: shapeFromGeoJSON) - - // prepare layer parameters - // Set the line join and cap to a rounded end. + style.addSource(source) + + // Prepare layer parameters let lineJoinCap = NSExpression(forConstantValue: "round") - // Use `NSExpression` to smoothly adjust the line width from 2pt to 20pt between zoom levels 14 and 18. - // The `interpolationBase` parameter allows the values to interpolate along an exponential curve. let lineWidth = NSExpression( format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", - [16: 2, 20: 20]) - + [16: 2, 20: 20] + ) let lineColor = UIColor(hex: "#008296") - - // create and add layers + + // Create layers based on the dashed line option if isDashedLine { let dashedLayer = createDashLayer(source, withLineJoinCap: lineJoinCap, withLineWidth: lineWidth, color: lineColor) @@ -654,39 +616,56 @@ private extension ExploreView { } } + func drawDashedLine(_ mapView: MLNMapView, geoJsonArray: [Data], departureCoordinates: CLLocationCoordinate2D, destinationCoordinates: CLLocationCoordinate2D) { + guard let style = mapView.style else { return } + + // Convert each GeoJSON Data into an MLNShape and combine into one shape collection + let shapes = geoJsonArray.compactMap { geoJson -> MLNShape? in + return try? MLNShape(data: geoJson, encoding: String.Encoding.utf8.rawValue) + } + + // Create a collection feature from all shapes + let combinedShape = MLNShapeCollectionFeature(shapes: shapes) + + // Draw the dashed line for the combined shape + drawDashedLine(style, combinedShape: combinedShape, point: departureCoordinates, isFirst: true) + drawDashedLine(style, combinedShape: combinedShape, point: destinationCoordinates, isFirst: false) + } + func drawDashedLine(_ mapView: MLNMapView, geoJson: Data, departureCoordinates: CLLocationCoordinate2D, destinationCoordinates: CLLocationCoordinate2D) { guard let style = mapView.style else { return } let shapeFromGeoJSON = (try? MLNShape(data: geoJson, encoding: String.Encoding.utf8.rawValue)) ?? MLNShape() - drawDashedLine(style, shape: shapeFromGeoJSON, point: departureCoordinates, isFirst: true) - drawDashedLine(style, shape: shapeFromGeoJSON, point: destinationCoordinates, isFirst: false) + drawDashedLine(style, combinedShape: shapeFromGeoJSON, point: departureCoordinates, isFirst: true) + drawDashedLine(style, combinedShape: shapeFromGeoJSON, point: destinationCoordinates, isFirst: false) } - func drawDashedLine(_ style: MLNStyle, shape: MLNShape, point: CLLocationCoordinate2D, isFirst: Bool) { - let layerName = isFirst ? "dashed-layer-start-point" : "dashed-layer-end-point" - let sourceName = isFirst ? "dashed-source-start-point" : "dashed-source-end-point" - if let layer = style.layer(withIdentifier: layerName) { - style.removeLayer(layer) + func drawDashedLine(_ style: MLNStyle, combinedShape: MLNShape, point: CLLocationCoordinate2D, isFirst: Bool) { + // Remove any existing layers or sources + let sourceName = "dashed-source-combined" + let layerName = "dashed-layer-combined" + + if let existingLayer = style.layer(withIdentifier: layerName) { + style.removeLayer(existingLayer) } - if let source = style.source(withIdentifier: sourceName) { - style.removeSource(source) + if let existingSource = style.source(withIdentifier: sourceName) { + style.removeSource(existingSource) } - guard let source = createDashedSource(shape: shape, point: point, isFirst: isFirst, identifier: sourceName) else { return } + // Create and add the source + guard let source = createDashedSource(combinedShape: combinedShape, point: point, isFirst: true, identifier: sourceName) else { return } style.addSource(source) - // prepare layer parameters - // Set the line join and cap to a rounded end. + // Prepare and add the dashed layer with required styling let lineJoinCap = NSExpression(forConstantValue: "round") - // Use `NSExpression` to smoothly adjust the line width from 2pt to 20pt between zoom levels 14 and 18. - // The `interpolationBase` parameter allows the values to interpolate along an exponential curve. let lineWidth = NSExpression( format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", [16: 2, 20: 20]) let dashedLayer = createDashLayer(source, withLineJoinCap: lineJoinCap, withLineWidth: lineWidth, color: .gray, identifier: layerName) + if let symbolLayer = style.layers.last { style.insertLayer(dashedLayer, below: symbolLayer) } else { @@ -694,8 +673,8 @@ private extension ExploreView { } } - func createDashedSource(shape: MLNShape, point firstPoint: CLLocationCoordinate2D, isFirst: Bool, identifier: String) -> MLNSource? { - guard let secondPoint = findCoordinate(in: shape, isFirst: isFirst) else { return nil } + func createDashedSource(combinedShape: MLNShape, point firstPoint: CLLocationCoordinate2D, isFirst: Bool, identifier: String) -> MLNSource? { + guard let secondPoint = findCoordinate(in: combinedShape, isFirst: isFirst) else { return nil } let firstLocation = CLLocation(location: firstPoint) let secondLocation = CLLocation(location: secondPoint) @@ -986,6 +965,7 @@ extension ExploreView: MLNMapViewDelegate { debounceForMapRendering.debounce { [weak self] in self?.updateMapHelperConstraints() self?.mapView.accessibilityIdentifier = ViewsIdentifiers.General.mapRendered + self?.mapLoaded = true } } else { debounceForMapRendering.debounce {} diff --git a/LocationServices/LocationServices/Scenes/Geofence/Controller/GeofenceVC.swift b/LocationServices/LocationServices/Scenes/Geofence/Controller/GeofenceVC.swift index b4c1d36a..a3996e45 100644 --- a/LocationServices/LocationServices/Scenes/Geofence/Controller/GeofenceVC.swift +++ b/LocationServices/LocationServices/Scenes/Geofence/Controller/GeofenceVC.swift @@ -130,7 +130,6 @@ final class GeofenceVC: UIViewController { Task { await viewModel.fetchListOfGeofences() } - blurStatusBar() setupKeyboardNotifications() } diff --git a/LocationServices/LocationServices/Scenes/Geofence/SubViews/AddGeofence/ViewModel/AddGeofenceViewModel.swift b/LocationServices/LocationServices/Scenes/Geofence/SubViews/AddGeofence/ViewModel/AddGeofenceViewModel.swift index 50f5b221..377c5a9b 100644 --- a/LocationServices/LocationServices/Scenes/Geofence/SubViews/AddGeofence/ViewModel/AddGeofenceViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Geofence/SubViews/AddGeofence/ViewModel/AddGeofenceViewModel.swift @@ -86,7 +86,7 @@ final class AddGeofenceViewModel: AddGeofenceViewModelProcotol { if text.isCoordinate() { let requestValue = text.convertTextToCoordinate() - let response = await searchService.searchWithPosition(position: requestValue, userLat: userLat, userLong: userLong) + let response = await searchService.reverseGeocode(position: requestValue, userLat: userLat, userLong: userLong) switch response { case .success(let results): self.presentation = results @@ -97,7 +97,7 @@ final class AddGeofenceViewModel: AddGeofenceViewModelProcotol { self.delegate?.showAlert(model) } } else { - let result = await searchService.searchTextWithSuggestion(text: text, userLat: userLat, userLong: userLong) + let result = await searchService.searchWithSuggest(text: text, userLat: userLat, userLong: userLong) let resultValue = try result.get() self.presentation = resultValue let model = resultValue.map(MapModel.init) @@ -113,7 +113,7 @@ final class AddGeofenceViewModel: AddGeofenceViewModelProcotol { if text.isCoordinate() { let requestValue = text.convertTextToCoordinate() - let response = await searchService.searchWithPosition(position: requestValue, userLat: userLat, userLong: userLong) + let response = await searchService.reverseGeocode(position: requestValue, userLat: userLat, userLong: userLong) switch response { case .success(let results): self.presentation = results diff --git a/LocationServices/LocationServices/Scenes/Geofence/Views/GeofenceView.swift b/LocationServices/LocationServices/Scenes/Geofence/Views/GeofenceView.swift index 1259a269..691f38c9 100644 --- a/LocationServices/LocationServices/Scenes/Geofence/Views/GeofenceView.swift +++ b/LocationServices/LocationServices/Scenes/Geofence/Views/GeofenceView.swift @@ -161,8 +161,7 @@ final class GeofenceMapView: UIView { func reloadMap() { mapView.setupMapView() deselectAnnotation() - let mapName = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - amazonMapLogo.tintColor = GeneralHelper.getAmazonMapLogo(mapImageType: mapName?.imageType) + amazonMapLogo.tintColor = GeneralHelper.getAmazonMapLogo() } func deselectAnnotation() { diff --git a/LocationServices/LocationServices/Scenes/Login/Contracts/LoginContracts.swift b/LocationServices/LocationServices/Scenes/Login/Contracts/LoginContracts.swift index 324664be..70690862 100644 --- a/LocationServices/LocationServices/Scenes/Login/Contracts/LoginContracts.swift +++ b/LocationServices/LocationServices/Scenes/Login/Contracts/LoginContracts.swift @@ -23,6 +23,7 @@ protocol LoginViewModelOutputDelegate: AnyObject, AlertPresentable { func cloudConnectionDisconnected() func loginCompleted() + func loginCancelled() func logoutCompleted() func identityPoolIdValidationSucceed() } diff --git a/LocationServices/LocationServices/Scenes/Login/Controller/LoginVC.swift b/LocationServices/LocationServices/Scenes/Login/Controller/LoginVC.swift index 9475d48c..5fc5039e 100644 --- a/LocationServices/LocationServices/Scenes/Login/Controller/LoginVC.swift +++ b/LocationServices/LocationServices/Scenes/Login/Controller/LoginVC.swift @@ -87,6 +87,14 @@ final class LoginVC: UIViewController { return stackView }() + let signInSpinner: UIActivityIndicatorView = { + let signInSpinner = UIActivityIndicatorView(style: .large) + signInSpinner.translatesAutoresizingMaskIntoConstraints = false + signInSpinner.color = .white + signInSpinner.hidesWhenStopped = true + return signInSpinner + }() + private lazy var signInButton: UIButton = { let button = UIButton(type: .system) button.accessibilityIdentifier = ViewsIdentifiers.AWSConnect.signInButton @@ -245,10 +253,22 @@ final class LoginVC: UIViewController { if let navigationController { (UIApplication.shared.delegate as? AppDelegate)?.navigationController = navigationController } - + showLoadingOnSignIn() viewModel.login() } + private func showLoadingOnSignIn() { + signInButton.setTitle("", for: .normal) + signInSpinner.startAnimating() + signInButton.isUserInteractionEnabled = false + } + + private func hideLoadingOnSignIn() { + signInSpinner.stopAnimating() + signInButton.setTitle("Sign In", for: .normal) + signInButton.isUserInteractionEnabled = true + } + @objc private func signOutAction() { if let navigationController { (UIApplication.shared.delegate as? AppDelegate)?.navigationController = navigationController @@ -353,6 +373,13 @@ final class LoginVC: UIViewController { view.addSubview(bottomGradientView) view.addSubview(bottomButtonStackView) + + signInButton.addSubview(signInSpinner) + signInSpinner.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.centerY.equalToSuperview() + } + bottomButtonStackView.addArrangedSubview(signInButton) bottomButtonStackView.addArrangedSubview(signOutButton) @@ -426,24 +453,29 @@ extension LoginVC: LoginViewModelOutputDelegate { } func loginCompleted() { - // TODO: investigate crash cause - //NotificationCenter.default.post(name: Notification.refreshMapView, object: nil, userInfo: nil) - DispatchQueue.main.async{ + self.dismissHandler?() self.setup() self.updateAccordingToAppState() + self.hideLoadingOnSignIn() + } + } + + func loginCancelled() { + DispatchQueue.main.async{ + self.hideLoadingOnSignIn() } } func logoutCompleted() { NotificationCenter.default.post(name: Notification.refreshMapView, object: nil, userInfo: nil) - DispatchQueue.main.async{ self.setup() self.updateAccordingToAppState() } } + func identityPoolIdValidationSucceed() { UserDefaultsHelper.save(value: isFromSettingScene, key: .awsCustomConnectFromSettings) UserDefaultsHelper.setAppState(state: .customAWSConnected) diff --git a/LocationServices/LocationServices/Scenes/Login/ViewModel/LoginViewModel.swift b/LocationServices/LocationServices/Scenes/Login/ViewModel/LoginViewModel.swift index 6508c8cc..adf37ac7 100644 --- a/LocationServices/LocationServices/Scenes/Login/ViewModel/LoginViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Login/ViewModel/LoginViewModel.swift @@ -61,6 +61,7 @@ final class LoginViewModel: LoginViewModelProtocol { let isValid = try await awsLoginService.validate(identityPoolId: identityPoolId) if isValid { + let configurationModel = awsLoginService.getAWSConfigurationModel() DispatchQueue.main.async { var userDomainValid = userDomain var webSocketUrlValid = webSocketUrl @@ -71,7 +72,8 @@ final class LoginViewModel: LoginViewModelProtocol { ["https://", "http://"].forEach { webSocketUrlValid = webSocketUrlValid.replacingOccurrences(of: $0, with: "") } - self.saveAWS(identityPoolId: identityPoolId, userPoolId: userPoolId, userPoolClientId: userPoolClientId, userDomain: userDomainValid, webSocketUrl: webSocketUrlValid, region: "", apiKey: "") + + self.saveAWS(identityPoolId: identityPoolId, userPoolId: userPoolId, userPoolClientId: userPoolClientId, userDomain: userDomainValid, webSocketUrl: webSocketUrlValid, region: configurationModel?.region ?? "", apiKey: configurationModel?.apiKey ?? "") } } else { @@ -138,7 +140,14 @@ final class LoginViewModel: LoginViewModelProtocol { extension LoginViewModel: AWSLoginServiceOutputProtocol { func loginResult(_ result: Result) { - delegate?.loginCompleted() + switch result { + case .success(): + print("Logged in") + delegate?.loginCompleted() + case .failure(let error): + print("Logged in failure \(error)") + delegate?.loginCancelled() + } } func logoutResult(_ error: Error?) { diff --git a/LocationServices/LocationServices/Scenes/Settings/Controller/SettingsVC.swift b/LocationServices/LocationServices/Scenes/Settings/Controller/SettingsVC.swift index 0fb5eb3f..f6079907 100644 --- a/LocationServices/LocationServices/Scenes/Settings/Controller/SettingsVC.swift +++ b/LocationServices/LocationServices/Scenes/Settings/Controller/SettingsVC.swift @@ -60,7 +60,6 @@ final class SettingsVC: UIViewController { super.viewWillAppear(animated) viewModel.loadData() updateLogoutButtonVisibility() - self.navigationController?.navigationBar.isHidden = true } private func setupNavigationItems() { diff --git a/LocationServices/LocationServices/Scenes/Settings/Subviews/Data Provider/ViewModel/DataProviderViewModel.swift b/LocationServices/LocationServices/Scenes/Settings/Subviews/Data Provider/ViewModel/DataProviderViewModel.swift index 0e06c12c..82a77cb4 100644 --- a/LocationServices/LocationServices/Scenes/Settings/Subviews/Data Provider/ViewModel/DataProviderViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Settings/Subviews/Data Provider/ViewModel/DataProviderViewModel.swift @@ -8,48 +8,24 @@ import Foundation enum DataProviderName { - case here, esri + case here var title: String { switch self { - case .esri: - return "Esri" case .here: return "HERE" } } - - var placeIndexesName: String { - switch self { - case .esri: - return "location.aws.com.demo.places.Esri.PlaceIndex" - case .here: - return "location.aws.com.demo.places.HERE.PlaceIndex" - } - } - - var routeCalculator: String { - switch self { - case .esri: - return "location.aws.com.demo.routes.Esri.RouteCalculator" - case .here: - return "location.aws.com.demo.routes.HERE.RouteCalculator" - } - } } final class DataProviderViewModel: DataProviderViewModelProtocol { private var initialDatas: [CommonSelectableCellModel] = [ - CommonSelectableCellModel(title: DataProviderName.esri.title, - subTitle: nil, - isSelected: false, - identifier: MapStyleSourceType.esri.title), CommonSelectableCellModel(title: DataProviderName.here.title, subTitle: nil, isSelected: true, - identifier: MapStyleSourceType.here.title) + identifier: ViewsIdentifiers.General.mapStyleRow) ] var delegate: DataProviderViewModelOutputDelegate? @@ -64,7 +40,7 @@ final class DataProviderViewModel: DataProviderViewModelProtocol { func loadData() { - let index = getDataFromLocal() + let index = 0 delegate?.updateTableView(index: index) } @@ -76,26 +52,8 @@ final class DataProviderViewModel: DataProviderViewModelProtocol { } private extension DataProviderViewModel { - func getDataFromLocal() -> Int { - var currentDataIndex = 0 - let localData = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - - for index in initialDatas.indices { - let isSelected = initialDatas[index].title == localData?.type.title - initialDatas[index].isSelected = isSelected - if isSelected { - currentDataIndex = index - } - } - return currentDataIndex - } - func saveUnitSettingsData(title: String) { - if title == DataProviderName.here.title { - UserDefaultsHelper.saveObject(value: DefaultUserSettings.mapHereStyle, key: .mapStyle) - } else { - UserDefaultsHelper.saveObject(value: DefaultUserSettings.mapStyle, key: .mapStyle) - } + UserDefaultsHelper.saveObject(value: DefaultUserSettings.mapStyle, key: .mapStyle) NotificationCenter.default.post(name: Notification.refreshMapView, object: nil, userInfo: nil) } } diff --git a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Controller/MapStyleVC+CollectionView.swift b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Controller/MapStyleVC+CollectionView.swift index 0c508f6d..70450f46 100644 --- a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Controller/MapStyleVC+CollectionView.swift +++ b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Controller/MapStyleVC+CollectionView.swift @@ -42,7 +42,6 @@ extension MapStyleVC: UICollectionViewDataSource { switch kind { case UICollectionView.elementKindSectionHeader: view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: MapStyleSectionHeaderView.reuseId, for: indexPath) - (view as? MapStyleSectionHeaderView)?.title = viewModel.getSectionTitle(at: indexPath.section) case UICollectionView.elementKindSectionFooter: view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: MapStyleSectionFooterView.reuseId, for: indexPath) default: @@ -55,11 +54,7 @@ extension MapStyleVC: UICollectionViewDataSource { extension MapStyleVC: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let totalWidth = collectionView.frame.size.width - let interitemSpacingSum = minimumInteritemSpacing * (numberOfItemsInRow - 1) - let itemWidth = (totalWidth - horizontalItemPadding - interitemSpacingSum)/numberOfItemsInRow - return CGSize(width: itemWidth, - height: itemHeight) + return Constants.cellSize } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { @@ -86,6 +81,12 @@ extension MapStyleVC: UICollectionViewDelegate, UICollectionViewDelegateFlowLayo func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { - return minimumInteritemSpacing + return calculateMinimumInteritemSpacing() + } + + func calculateMinimumInteritemSpacing() -> CGFloat { + let itemsCountPerRow = CGFloat(Constants.itemsCountPerRow) + let freeSpace = collectionView.frame.width - (Constants.cellSize.width * itemsCountPerRow) + return (freeSpace / itemsCountPerRow).rounded(.down) } } diff --git a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Controller/MapStyleVC.swift b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Controller/MapStyleVC.swift index 2794fa5c..13820810 100644 --- a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Controller/MapStyleVC.swift +++ b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Controller/MapStyleVC.swift @@ -12,6 +12,9 @@ final class MapStyleVC: UIViewController { enum Constants { static let horizontalOffset: CGFloat = 16 + static let cellSize = CGSize(width: 160, height: 106) + static let minimumLineSpacing: CGFloat = 36 + static let itemsCountPerRow = 2 } var selectedCell: IndexPath = IndexPath(row: 0, section: 0) @@ -31,6 +34,8 @@ final class MapStyleVC: UIViewController { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) return collectionView }() + var colorSegment: ColorSegmentControl? = nil + var politicalView = PoliticalView() var isLargerPad: Bool { max(UIScreen.main.bounds.size.width, UIScreen.main.bounds.size.height) > largerPadSideSizeThreshold @@ -109,6 +114,9 @@ final class MapStyleVC: UIViewController { } private func setupViews() { + let colorNames = [MapStyleColorType.light.colorName, MapStyleColorType.dark.colorName] + colorSegment = ColorSegmentControl(items: colorNames) + navigationController?.navigationBar.tintColor = .mapDarkBlackColor navigationItem.title = UIDevice.current.isPad ? "" : StringConstant.mapStyle view.backgroundColor = .white @@ -123,15 +131,38 @@ final class MapStyleVC: UIViewController { } self.view.addSubview(collectionView) + self.view.addSubview(colorSegment!) + self.view.addSubview(politicalView) + collectionView.snp.makeConstraints { if isPad { $0.top.equalTo(screenTitleLabel.snp.bottom) } else { $0.top.equalTo(self.view.safeAreaLayoutGuide) } - $0.bottom.equalToSuperview() $0.horizontalEdges.equalToSuperview().inset(16) + $0.height.equalTo(280) } + + colorSegment!.snp.makeConstraints { + $0.top.equalTo(collectionView.snp.bottom).offset(10) + $0.centerX.equalToSuperview() + if UIDevice.current.userInterfaceIdiom == .pad { + $0.width.equalToSuperview().offset(-20) + } + else { + $0.width.equalToSuperview().offset(-50) + } + $0.height.equalTo(40) + } + + politicalView.snp.makeConstraints { + $0.top.equalTo(colorSegment!.snp.bottom).offset(50) + $0.centerX.equalToSuperview() + $0.width.equalToSuperview() + } + + politicalView.viewController = self } } diff --git a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleModel.swift b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleModel.swift index d25b3087..f5f95183 100644 --- a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleModel.swift +++ b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleModel.swift @@ -10,6 +10,5 @@ import UIKit struct MapStyleModel: Codable { var title: String var imageType: MapStyleImages - var type: MapStyleSourceType var isSelected: Bool } diff --git a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleTypes+Image.swift b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleTypes+Image.swift index 5e15ce33..1a6f4538 100644 --- a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleTypes+Image.swift +++ b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleTypes+Image.swift @@ -10,28 +10,14 @@ import UIKit extension MapStyleImages { var image: UIImage { switch self { - case .light: - return .lightMapLayer - case .street: + case .standard: return .streetMapLayer - case .navigation: - return .navigationMapLayer - case .explore: - return .exploreMapLayer - case .contrast: - return .contrastMapLayer - case .exploreTruck: - return .explore_truck_map_layer - case .darkGray: - return .dark_gray_map_layer - case .lightGray: + case .monochrome: return .light_gray_map_layer - case .Imagery: - return .esri_imagerey - case .hereImagery: - return .here_imagerey_map_layer case .hybrid: return .hybird_map_layer + case .satellite: + return .here_imagerey_map_layer } } } diff --git a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleTypes.swift b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleTypes.swift index b1b835d1..d2ba1bb7 100644 --- a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleTypes.swift +++ b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Models/MapStyleTypes.swift @@ -8,54 +8,56 @@ import Foundation enum MapStyleImages: Codable { - case light, street, navigation, explore,contrast,exploreTruck, darkGray, lightGray, Imagery, hereImagery, hybrid + case standard, monochrome, hybrid, satellite var mapName: String { switch self { - case .light: - return "location.aws.com.demo.maps.Esri.Light" - case .street: - return "location.aws.com.demo.maps.Esri.Streets" - case .navigation: - return "location.aws.com.demo.maps.Esri.Navigation" - case .explore: - return "location.aws.com.demo.maps.HERE.Explore" - case .contrast: - return"location.aws.com.demo.maps.HERE.Contrast" - case .exploreTruck: - return "location.aws.com.demo.maps.HERE.ExploreTruck" - case .darkGray: - return "location.aws.com.demo.maps.Esri.DarkGrayCanvas" - case .lightGray: - return "location.aws.com.demo.maps.Esri.LightGrayCanvas" - case .Imagery: - return "location.aws.com.demo.maps.Esri.Imagery" - case .hereImagery: - return "location.aws.com.demo.maps.HERE.Imagery" + case .standard: + return "Standard" + case .monochrome: + return "Monochrome" case .hybrid: - return "location.aws.com.demo.maps.HERE.Hybrid" - } - } - - var sourceType: MapStyleSourceType { - switch self { - case .light, .street, .navigation, .darkGray, .lightGray, .Imagery: - return .esri - case .explore, .contrast, .exploreTruck, .hereImagery, .hybrid: - return .here + return "Hybrid" + case .satellite: + return "Satellite" } } } -enum MapStyleSourceType: String, Codable { - case esri, here +enum MapStyleColorType: String, Codable { + case light, dark - var title: String { + var colorName: String { switch self { - case .esri: - return "Esri" - case .here: - return "HERE" + case .light: + return "Light" + case .dark: + return "Dark" } } } + +struct PoliticalViewType: Codable { + let countryCode: String + let flagCode: String + let fullName: String + let politicalDescription: String +} + + +let PoliticalViewTypes: [PoliticalViewType] = [ + PoliticalViewType(countryCode: "ARG", flagCode: "AR", fullName: "Argentina", politicalDescription: "Argentina's view on the Southern Patagonian Ice Field and Tierra Del Fuego, including the Falkland Islands, South Georgia, and South Sandwich Islands"), + PoliticalViewType(countryCode: "EGY", flagCode: "EG", fullName: "Egypt", politicalDescription: "Egypt's view on Bir Tawil"), + PoliticalViewType(countryCode: "IND", flagCode: "IN", fullName: "India", politicalDescription: "India's view on Gilgit-Baltistan"), + PoliticalViewType(countryCode: "KEN", flagCode: "KE", fullName: "Kenya", politicalDescription: "Kenya's view on the Ilemi Triangle"), + PoliticalViewType(countryCode: "MAR", flagCode: "MA", fullName: "Morocco", politicalDescription: "Morocco's view on Western Sahara"), + PoliticalViewType(countryCode: "RUS", flagCode: "RU", fullName: "Russia", politicalDescription: "Russia's view on Crimea"), + PoliticalViewType(countryCode: "SDN", flagCode: "SD", fullName: "Sudan", politicalDescription: "Sudan's view on the Halaib Triangle"), + PoliticalViewType(countryCode: "SRB", flagCode: "RS", fullName: "Serbia", politicalDescription: "Serbia's view on Kosovo, Vukovar, and Sarengrad Islands"), + PoliticalViewType(countryCode: "SUR", flagCode: "SR", fullName: "Suriname", politicalDescription: "Suriname's view on the Courantyne Headwaters and Lawa Headwaters"), + PoliticalViewType(countryCode: "SYR", flagCode: "SY", fullName: "Syria", politicalDescription: "Syria's view on the Golan Heights"), + PoliticalViewType(countryCode: "TUR", flagCode: "TR", fullName: "Turkey", politicalDescription: "Turkey's view on Cyprus and Northern Cyprus"), + PoliticalViewType(countryCode: "TZA", flagCode: "TZ", fullName: "Tanzania", politicalDescription: "Tanzania's view on Lake Malawi"), + PoliticalViewType(countryCode: "URY", flagCode: "UY", fullName: "Uruguay", politicalDescription: "Uruguay's view on Rincon de Artigas"), + PoliticalViewType(countryCode: "VNM", flagCode: "VN", fullName: "Vietnam", politicalDescription: "Vietnam's view on the Paracel Islands and Spratly Islands") +] diff --git a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/ViewModel/MapStyleViewModel.swift b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/ViewModel/MapStyleViewModel.swift index 308cc6b6..344092a3 100644 --- a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/ViewModel/MapStyleViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/ViewModel/MapStyleViewModel.swift @@ -9,37 +9,34 @@ import Foundation final class MapStyleViewModel: MapStyleViewModelProtocol { - private let mapStyles: [MapStyleSourceType: [MapStyleModel]] - private let sortedKeys: [MapStyleSourceType] - + private let mapStyles: [MapStyleModel] var delegate: MapStyleViewModelOutputDelegate? init() { - mapStyles = Dictionary(grouping: DefaultMapStyles.mapStyles) { $0.type } - sortedKeys = Array(mapStyles.keys).sorted(by: { first, _ in first == .esri }) + mapStyles = DefaultMapStyles.mapStyles } func getSectionsCount() -> Int { - return sortedKeys.count + return 1 } func getSectionTitle(at section: Int) -> String { - return sortedKeys[section].title + return "Map Style" } func getItemCount(at section: Int) -> Int { - return mapStyles[sortedKeys[section]]?.count ?? 0 + return mapStyles.count } func getCellItem(_ indexPath: IndexPath) -> MapStyleCellModel? { - guard let style = mapStyles[sortedKeys[indexPath.section]]?[indexPath.row] else { return nil } + let style = mapStyles[indexPath.row] return MapStyleCellModel(model: style) } func saveSelectedState(_ indexPath: IndexPath) { - guard let item = mapStyles[sortedKeys[indexPath.section]]?[indexPath.row] else { return } + let item = mapStyles[indexPath.row] - saveUnitSettingsData(mapSource: item) + saveUnitSettingsData(mapStyle: item) delegate?.loadData(selectedIndexPath: indexPath) } @@ -54,16 +51,14 @@ private extension MapStyleViewModel { func loadCurentSourceMap() -> IndexPath? { guard let localData = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) else { return nil } - guard let section = sortedKeys.firstIndex(of: localData.type) else { return nil } - - let mapStyles = self.mapStyles[localData.type] - guard let row = mapStyles?.firstIndex(where: { $0.title == localData.title }) else { return nil } - - return IndexPath(row: row, section: section) + let section = 0 + let row = mapStyles.firstIndex(where: { $0.title == localData.title }) + return IndexPath(row: row!, section: section) } - func saveUnitSettingsData(mapSource: MapStyleModel) { - UserDefaultsHelper.saveObject(value: mapSource, key: .mapStyle) + func saveUnitSettingsData(mapStyle: MapStyleModel) { + UserDefaultsHelper.saveObject(value: mapStyle, key: .mapStyle) NotificationCenter.default.post(name: Notification.refreshMapView, object: nil, userInfo: nil) + NotificationCenter.default.post(name: Notification.validateMapColor, object: nil, userInfo: nil) } } diff --git a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Views/MapStyleSectionHeaderView.swift b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Views/MapStyleSectionHeaderView.swift index 3a58cb39..2c083624 100644 --- a/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Views/MapStyleSectionHeaderView.swift +++ b/LocationServices/LocationServices/Scenes/Settings/Subviews/MapStyle/Views/MapStyleSectionHeaderView.swift @@ -44,16 +44,9 @@ final class MapStyleSectionHeaderView: UICollectionReusableView { private func setupViews() { self.addSubview(containerView) - containerView.addSubview(titleLabel) containerView.snp.makeConstraints { $0.top.bottom.leading.trailing.equalToSuperview() } - - titleLabel.snp.makeConstraints { - $0.top.equalToSuperview().offset(15) - $0.leading.trailing.equalToSuperview() - $0.bottom.equalToSuperview().offset(-8) - } } } diff --git a/LocationServices/LocationServices/Scenes/Settings/ViewModel/SettingsViewModel.swift b/LocationServices/LocationServices/Scenes/Settings/ViewModel/SettingsViewModel.swift index 32605583..3e1e423c 100644 --- a/LocationServices/LocationServices/Scenes/Settings/ViewModel/SettingsViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Settings/ViewModel/SettingsViewModel.swift @@ -61,7 +61,6 @@ final class SettingsViewModel: SettingsViewModelProtocol { let unitType = UserDefaultsHelper.get(for: String.self, key: .unitType) datas = [ - SettingsCellModel(type: .dataProvider, subTitle: mapStyle?.type.title ?? ""), SettingsCellModel(type: .mapStyle, subTitle: mapStyle?.title ?? ""), SettingsCellModel(type: .routeOption), SettingsCellModel(type: .awsCloud) @@ -76,6 +75,11 @@ extension SettingsViewModel: AWSLoginServiceOutputProtocol { } func loginResult(_ result: Result) { - print("Logged in") + switch result { + case .success(): + print("Logged in") + case .failure(let error): + print("Logged in failure") + } } } diff --git a/LocationServices/LocationServices/Scenes/Splash/ViewModel/SplashViewModel.swift b/LocationServices/LocationServices/Scenes/Splash/ViewModel/SplashViewModel.swift index 90a3378c..e12f2d63 100644 --- a/LocationServices/LocationServices/Scenes/Splash/ViewModel/SplashViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Splash/ViewModel/SplashViewModel.swift @@ -71,6 +71,7 @@ final class SplashViewModel: SplashViewModelProtocol, AWSLoginServiceOutputProto private func initializeMobileClient(configurationModel: CustomConnectionModel) async throws { try await CognitoAuthHelper.initialise(identityPoolId: configurationModel.identityPoolId) + try await ApiAuthHelper.initialise(apiKey: configurationModel.apiKey, region: configurationModel.region) self.setupCompleted() } diff --git a/LocationServices/LocationServices/Scenes/Tracking/Controller/TrackingVC.swift b/LocationServices/LocationServices/Scenes/Tracking/Controller/TrackingVC.swift index 6954eb1e..83299bda 100644 --- a/LocationServices/LocationServices/Scenes/Tracking/Controller/TrackingVC.swift +++ b/LocationServices/LocationServices/Scenes/Tracking/Controller/TrackingVC.swift @@ -246,7 +246,6 @@ final class TrackingVC: UIViewController { super.viewWillAppear(animated) openLoginFlow(skipDashboard: viewModel.hasHistory) showGeofenceAnnotations() - blurStatusBar() setupKeyboardNotifications() } @@ -320,7 +319,7 @@ extension TrackingVC: TrackingMapViewOutputDelegate { Task { guard let lat = userLocation?.coordinate.latitude, let long = userLocation?.coordinate.longitude else { return } - try await GeofenceAPIService().evaluateGeofence(lat: lat, long: long) + try await GeofenceAPIService().evaluateGeofence(lat: lat, long: long) self.geofenceHandler?() } } diff --git a/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/View/TrackHistoryCell.swift b/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/View/TrackHistoryCell.swift index 3410b546..2bbfcc0b 100644 --- a/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/View/TrackHistoryCell.swift +++ b/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/View/TrackHistoryCell.swift @@ -9,7 +9,7 @@ import UIKit import SnapKit struct TrackHistoryCellModel { - var stepType: StepType + var stepState: StepState var coordinates: String var time: String var date: String @@ -18,7 +18,7 @@ struct TrackHistoryCellModel { self.coordinates = model.cooordinates self.time = model.time self.date = model.date - self.stepType = model.stepType + self.stepState = model.stepState } } @@ -38,7 +38,7 @@ final class TrackHistoryCell: UITableViewCell { self.timeLabel.text = model.time stepImage.image = .stepIcon - switch model.stepType { + switch model.stepState{ case .first: stepLineStackView.isHidden = false case .last: diff --git a/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/View/TrackHistoryHeaderView.swift b/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/View/TrackHistoryHeaderView.swift index bbdd9ad1..64f07cb3 100644 --- a/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/View/TrackHistoryHeaderView.swift +++ b/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/View/TrackHistoryHeaderView.swift @@ -50,11 +50,7 @@ final class TrackingHistoryHeaderView: UIView { } let mapStyle = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - if mapStyle?.type != .here && UserDefaultsHelper.getAppState() == .loggedIn && !isTrackingStarted { - showChangeStyleAlert() - } else { - toggleTrackingStatus() - } + toggleTrackingStatus() } private func showChangeStyleAlert() { diff --git a/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/ViewModel/TrackingHistoryViewModel.swift b/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/ViewModel/TrackingHistoryViewModel.swift index f6b87f5d..aec27ab7 100644 --- a/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/ViewModel/TrackingHistoryViewModel.swift +++ b/LocationServices/LocationServices/Scenes/Tracking/Subviews/Tracking History/ViewModel/TrackingHistoryViewModel.swift @@ -12,10 +12,10 @@ struct TrackingHistoryPresentation { let time: String let date: String let cooordinates: String - let stepType: StepType + let stepState: StepState let receivedTime: Date - init(model: LocationClientTypes.DevicePosition, stepType: StepType) { + init(model: LocationClientTypes.DevicePosition, stepState: StepState) { if let time = model.receivedTime { self.receivedTime = time self.time = time.convertTimeString() @@ -31,7 +31,7 @@ struct TrackingHistoryPresentation { } else { cooordinates = "" } - self.stepType = stepType + self.stepState = stepState } } diff --git a/LocationServices/LocationServices/Scenes/Tracking/Views/TrackingViews.swift b/LocationServices/LocationServices/Scenes/Tracking/Views/TrackingViews.swift index ba5613bd..5619759e 100644 --- a/LocationServices/LocationServices/Scenes/Tracking/Views/TrackingViews.swift +++ b/LocationServices/LocationServices/Scenes/Tracking/Views/TrackingViews.swift @@ -140,8 +140,7 @@ final class TrackingMapView: UIView { func reloadMap() { mapView.setupMapView() - let mapName = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - amazonMapLogo.tintColor = GeneralHelper.getAmazonMapLogo(mapImageType: mapName?.imageType) + amazonMapLogo.tintColor = GeneralHelper.getAmazonMapLogo() } func removeGeofencesFromMap() { diff --git a/LocationServices/LocationServices/Services/AWSLoginService.swift b/LocationServices/LocationServices/Services/AWSLoginService.swift index 11e66396..490854c1 100644 --- a/LocationServices/LocationServices/Services/AWSLoginService.swift +++ b/LocationServices/LocationServices/Services/AWSLoginService.swift @@ -22,6 +22,7 @@ protocol AWSLoginServiceProtocol { func logout(skipPolicy: Bool) func validate(identityPoolId: String) async throws -> Bool func disconnectAWS() + func getAWSConfigurationModel() -> CustomConnectionModel? } protocol AWSLoginServiceOutputProtocol { @@ -184,8 +185,6 @@ final class AWSLoginService: NSObject, AWSLoginServiceProtocol, ASWebAuthenticat let cognitoToken = CognitoToken(accessToken: json["access_token"] as! String, expiresIn: json["expires_in"] as! Int, idToken: json["id_token"] as! String, refreshToken: json["refresh_token"] as! String, tokenType: json["token_type"] as! String, issueDate: Date()) try await self.updateAWSServicesToken(cognitoToken: cognitoToken) - self.delegate?.loginResult(.success(())) - NotificationCenter.default.post(name: Notification.authorizationStatusChanged, object: self, userInfo: nil) } } catch { print("Error fetching tokens: \(error.localizedDescription)") @@ -259,7 +258,7 @@ final class AWSLoginService: NSObject, AWSLoginServiceProtocol, ASWebAuthenticat // remove custom configuration UserDefaultsHelper.removeObject(for: .awsConnect) - UserDefaultsHelper.setAppState(state: .defaultAWSConnected) + UserDefaultsHelper.setAppState(state: .initial) } diff --git a/LocationServices/LocationServices/Services/AWSSignatureV4Delegate.swift b/LocationServices/LocationServices/Services/AWSSignatureV4Delegate.swift deleted file mode 100644 index 1b7df979..00000000 --- a/LocationServices/LocationServices/Services/AWSSignatureV4Delegate.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// AWSSignitureDelegate.swift -// LocationServices -// -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 - -import MapLibre -import AwsCommonRuntimeKit - -class AWSSignatureV4Delegate : NSObject, MLNOfflineStorageDelegate { - private var region: String? = nil - private var apiKey: String? = nil - - init(region: String) { - super.init() - self.region = region - } - - init(apiKey: String, region: String) { - super.init() - self.apiKey = apiKey - self.region = region - } - - func offlineStorage(_ storage: MLNOfflineStorage, urlForResourceOf kind: MLNResourceKind, with url: URL) -> URL { - if url.host?.contains("amazonaws.com") != true || url.absoluteString.contains("?key=") { - return url - } - - if apiKey != nil && region != nil { - return URL(string: "\(url)?key=\(apiKey!)") ?? url - } - else if let cognitoProvider = CognitoAuthHelper.default().locationCredentialsProvider?.getCognitoProvider(), region != nil { - var signedURL: URL = url - let cognitoCredentials: CognitoCredentials? = cognitoProvider.getCognitoCredentials() - let awsSigner = AWSSignerV4(credentials: cognitoCredentials!, serviceName: "geo", region: self.region!) - signedURL = awsSigner.signURL(url: url, expires: .hours(1)) - return signedURL - } - return url - } -} diff --git a/LocationServices/LocationServices/Services/GeofenceService.swift b/LocationServices/LocationServices/Services/GeofenceService.swift index 879f6c8d..b2cebaf9 100644 --- a/LocationServices/LocationServices/Services/GeofenceService.swift +++ b/LocationServices/LocationServices/Services/GeofenceService.swift @@ -31,7 +31,7 @@ extension AWSGeofenceServiceProtocol { let circle = LocationClientTypes.Circle(center: center, radius: radius) let geometry = LocationClientTypes.GeofenceGeometry(circle: circle) let input = PutGeofenceInput(collectionName: GeofenceServiceConstant.collectionName, geofenceId: id, geometry: geometry) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { + if let client = try await AmazonLocationClient.getCognitoLocationClient() { let result = try await client.putGeofence(input: input) return result } else { @@ -47,7 +47,7 @@ extension AWSGeofenceServiceProtocol { do { try await AWSLoginService.default().refreshLoginIfExpired() let input = BatchDeleteGeofenceInput(collectionName: GeofenceServiceConstant.collectionName, geofenceIds: ids) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { + if let client = try await AmazonLocationClient.getCognitoLocationClient() { let result = try await client.batchDeleteGeofence(input: input) return result } else { @@ -63,7 +63,7 @@ extension AWSGeofenceServiceProtocol { do { try await AWSLoginService.default().refreshLoginIfExpired() let input = ListGeofencesInput(collectionName: GeofenceServiceConstant.collectionName) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { + if let client = try await AmazonLocationClient.getCognitoLocationClient() { let result = try await client.listGeofences(input: input) return result } else { @@ -85,7 +85,7 @@ extension AWSGeofenceServiceProtocol { devicePositionUpdate.positionProperties = ["region": identityId.toRegionString(), "id": identityId.toId()] } let input = BatchEvaluateGeofencesInput(collectionName: GeofenceServiceConstant.collectionName, devicePositionUpdates: [devicePositionUpdate]) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { + if let client = try await AmazonLocationClient.getCognitoLocationClient() { let result = try await client.batchEvaluateGeofences(input: input) return result } else { diff --git a/LocationServices/LocationServices/Services/LocationSearchService.swift b/LocationServices/LocationServices/Services/LocationSearchService.swift index aeafacb2..060ecf5e 100755 --- a/LocationServices/LocationServices/Services/LocationSearchService.swift +++ b/LocationServices/LocationServices/Services/LocationSearchService.swift @@ -6,19 +6,19 @@ // SPDX-License-Identifier: MIT-0 import Foundation -import AWSLocation +import AWSGeoPlaces enum LocationServiceConstant { static let maxResult: NSNumber = 5 } protocol AWSLocationSearchService { - func searchTextRequest(text: String, userLat: Double?, userLong: Double?) async throws -> SearchPlaceIndexForTextOutput? - func searchTextWithSuggestionRequest(text: String, + func searchTextRequest(text: String, userLat: Double?, userLong: Double?) async throws -> SearchTextOutput? + func searchWithSuggestRequest(text: String, userLat: Double?, - userLong: Double?) async throws -> SearchPlaceIndexForSuggestionsOutput? + userLong: Double?) async throws -> SuggestOutput? func getPlaceRequest(with placeId: String) async throws -> GetPlaceOutput? - func searchWithPositionRequest(position: [Double]) async throws -> SearchPlaceIndexForPositionOutput? + func reverseGeocodeRequest(position: [Double]) async throws -> ReverseGeocodeOutput? } extension AWSLocationSearchService { @@ -26,32 +26,39 @@ extension AWSLocationSearchService { func searchTextRequest(text: String, userLat: Double?, - userLong: Double?) async throws -> SearchPlaceIndexForTextOutput? { + userLong: Double?) async throws -> SearchTextOutput? { var biasPosition: [Double]? = nil if let lat = userLat, let long = userLong { biasPosition = [long, lat] } - let input = SearchPlaceIndexForTextInput(biasPosition: biasPosition, indexName: getIndexName(), language: Locale.currentLanguageIdentifier(), text: text) + else { + biasPosition = [AppConstants.amazonHqMapPosition.longitude, AppConstants.amazonHqMapPosition.latitude] + } + let politicalView = UserDefaultsHelper.getObject(value: PoliticalViewType.self, key: .politicalView) + let input = SearchTextInput(biasPosition: biasPosition, language: Locale.currentLanguageIdentifier(), politicalView: politicalView?.countryCode, queryText: text) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { - let result = try await client.searchPlaceIndexForText(input: input) + if let client = AmazonLocationClient.getPlacesClient() { + let result = try await client.searchText(input: input) return result } else { return nil } } - func searchTextWithSuggestionRequest(text: String, + func searchWithSuggestRequest(text: String, userLat: Double?, - userLong: Double?) async throws -> SearchPlaceIndexForSuggestionsOutput? { + userLong: Double?) async throws -> SuggestOutput? { var biasPosition: [Double]? = nil if let lat = userLat, let long = userLong { biasPosition = [long, lat] } - - let input = SearchPlaceIndexForSuggestionsInput(biasPosition: biasPosition, indexName: getIndexName(), language: Locale.currentLanguageIdentifier(), maxResults: LocationServiceConstant.maxResult as? Int, text: text) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { - let result = try await client.searchPlaceIndexForSuggestions(input: input) + else { + biasPosition = [AppConstants.amazonHqMapPosition.longitude, AppConstants.amazonHqMapPosition.latitude] + } + let politicalView = UserDefaultsHelper.getObject(value: PoliticalViewType.self, key: .politicalView) + let input = SuggestInput(additionalFeatures: [.sdkUnknown("Core")], biasPosition: biasPosition, language: Locale.currentLanguageIdentifier(), politicalView: politicalView?.countryCode, queryText: text) + if let client = AmazonLocationClient.getPlacesClient() { + let result = try await client.suggest(input: input) return result } else { return nil @@ -59,8 +66,9 @@ extension AWSLocationSearchService { } func getPlaceRequest(with placeId: String) async throws -> GetPlaceOutput? { - let input = GetPlaceInput(indexName: getIndexName(), language: Locale.currentLanguageIdentifier(), placeId: placeId) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { + let politicalView = UserDefaultsHelper.getObject(value: PoliticalViewType.self, key: .politicalView) + let input = GetPlaceInput(language: Locale.currentLanguageIdentifier(), placeId: placeId, politicalView: politicalView?.countryCode) + if let client = AmazonLocationClient.getPlacesClient() { let result = try await client.getPlace(input: input) return result } else { @@ -68,25 +76,14 @@ extension AWSLocationSearchService { } } - func searchWithPositionRequest(position: [Double]) async throws -> SearchPlaceIndexForPositionOutput? { - let input = SearchPlaceIndexForPositionInput(indexName: getIndexName(), language: Locale.currentLanguageIdentifier(), position: position) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { - let result = try await client.searchPlaceIndexForPosition(input: input) + func reverseGeocodeRequest(position: [Double]) async throws -> ReverseGeocodeOutput? { + let politicalView = UserDefaultsHelper.getObject(value: PoliticalViewType.self, key: .politicalView) + let input = ReverseGeocodeInput(language: Locale.currentLanguageIdentifier(), politicalView: politicalView?.countryCode, queryPosition: position, queryRadius: 50) + if let client = AmazonLocationClient.getPlacesClient() { + let result = try await client.reverseGeocode(input: input) return result } else { return nil } } } - -extension AWSLocationSearchService { - private func getIndexName() -> String { - let localData = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - switch localData?.type { - case .esri, .none: - return DataProviderName.esri.placeIndexesName - case .here: - return DataProviderName.here.placeIndexesName - } - } -} diff --git a/LocationServices/LocationServices/Services/RoutingService.swift b/LocationServices/LocationServices/Services/RoutingService.swift index 9098bef2..6843cd2a 100644 --- a/LocationServices/LocationServices/Services/RoutingService.swift +++ b/LocationServices/LocationServices/Services/RoutingService.swift @@ -6,46 +6,38 @@ // SPDX-License-Identifier: MIT-0 import Foundation -import AWSLocation import CoreLocation +import AWSGeoRoutes protocol AWSRoutingServiceProtocol { func calculateRoute(depaturePosition: CLLocationCoordinate2D, destinationPosition: CLLocationCoordinate2D, - travelMode: LocationClientTypes.TravelMode, + travelMode: GeoRoutesClientTypes.RouteTravelMode, avoidFerries: Bool, - avoidTolls: Bool) async throws -> CalculateRouteOutput? + avoidTolls: Bool) async throws -> CalculateRoutesOutput? } extension AWSRoutingServiceProtocol { func calculateRoute(depaturePosition: CLLocationCoordinate2D, destinationPosition: CLLocationCoordinate2D, - travelMode: LocationClientTypes.TravelMode, + travelMode: GeoRoutesClientTypes.RouteTravelMode, avoidFerries: Bool, - avoidTolls: Bool) async throws -> CalculateRouteOutput? { - var carModeOptions: LocationClientTypes.CalculateRouteCarModeOptions? = nil + avoidTolls: Bool) async throws -> CalculateRoutesOutput? { + var routeAvoidanceOptions: GeoRoutesClientTypes.RouteAvoidanceOptions? = nil if travelMode == .car { - carModeOptions = LocationClientTypes.CalculateRouteCarModeOptions(avoidFerries: avoidFerries, avoidTolls: avoidTolls) + routeAvoidanceOptions = GeoRoutesClientTypes.RouteAvoidanceOptions(ferries: avoidFerries, tollRoads: avoidTolls) } - let input = CalculateRouteInput(calculatorName: getCalculatorName(), carModeOptions: carModeOptions, departNow: true, departurePosition: [depaturePosition.longitude, depaturePosition.latitude], destinationPosition: [destinationPosition.longitude, destinationPosition.latitude], includeLegGeometry: true, travelMode: travelMode) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { - let result = try await client.calculateRoute(input: input) - + let origin = [depaturePosition.longitude, depaturePosition.latitude] + let destination = [destinationPosition.longitude, destinationPosition.latitude] + let legAdditionalFeatures: [GeoRoutesClientTypes.RouteLegAdditionalFeature] = [.travelStepInstructions, .summary] + + let input = CalculateRoutesInput(avoid: routeAvoidanceOptions, departNow: true, destination: destination, instructionsMeasurementSystem: .metric, legAdditionalFeatures: legAdditionalFeatures, legGeometryFormat: GeoRoutesClientTypes.GeometryFormat.simple, origin: origin, travelStepType: .default) + + if let client = AmazonLocationClient.getRoutesClient() { + let result = try await client.calculateRoutes(input: input) return result } else { return nil } } } - -extension AWSRoutingServiceProtocol { - private func getCalculatorName() -> String { - let localData = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - switch localData?.type { - case .esri, .none: - return DataProviderName.esri.routeCalculator - case .here: - return DataProviderName.here.routeCalculator - } - } -} diff --git a/LocationServices/LocationServices/Services/TrackingService.swift b/LocationServices/LocationServices/Services/TrackingService.swift index 6a572a52..a1b308a2 100644 --- a/LocationServices/LocationServices/Services/TrackingService.swift +++ b/LocationServices/LocationServices/Services/TrackingService.swift @@ -29,7 +29,7 @@ extension AWSTrackingServiceProtocol { let devicePositionUpdates: [LocationClientTypes.DevicePositionUpdate]? = [devicePositionUpdate] let input = BatchUpdateDevicePositionInput(trackerName: TrackingServiceConstant.collectionName, updates: devicePositionUpdates) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { + if let client = try await AmazonLocationClient.getCognitoLocationClient() { let result = try await client.batchUpdateDevicePosition(input: input) return result } else { @@ -45,7 +45,7 @@ extension AWSTrackingServiceProtocol { do { try await AWSLoginService.default().refreshLoginIfExpired() let input = GetDevicePositionHistoryInput(deviceId: TrackingServiceConstant.deviceId, nextToken: nextToken, trackerName: TrackingServiceConstant.collectionName) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { + if let client = try await AmazonLocationClient.getCognitoLocationClient() { let result = try await client.getDevicePositionHistory(input: input) var devicePositions = result.devicePositions ?? [] @@ -66,7 +66,7 @@ extension AWSTrackingServiceProtocol { do { try await AWSLoginService.default().refreshLoginIfExpired() let input = BatchDeleteDevicePositionHistoryInput(deviceIds: [TrackingServiceConstant.deviceId], trackerName: TrackingServiceConstant.collectionName) - if let client = try await AmazonLocationClient.defaultCognito()?.locationClient { + if let client = try await AmazonLocationClient.getCognitoLocationClient() { let result = try await client.batchDeleteDevicePositionHistory(input: input) return result } else { diff --git a/LocationServices/LocationServices/Views/Common/MapView/DefaultCommonMapView.swift b/LocationServices/LocationServices/Views/Common/MapView/DefaultCommonMapView.swift index b57ed672..a8b1cffe 100644 --- a/LocationServices/LocationServices/Views/Common/MapView/DefaultCommonMapView.swift +++ b/LocationServices/LocationServices/Views/Common/MapView/DefaultCommonMapView.swift @@ -16,12 +16,11 @@ private enum Constant { static let directionMapZoomValue: Double = 14 static let annotationMapZoomValue: Double = 10 static let locateMeMapZoomValue: Double = 14 - static let amazonHqMapPosition = (latitude: 47.61506909519956, longitude: -122.33826750882835) static let geofenceViewIdentifier = "GeofenceViewIdentifier" static let userLocationViewIdentifier = "UserLocationViewIdentifier" static let imageAnnotationViewIdentifier = "ImageAnnotationViewIdentifier" static let mainBundleNameObject = "AWSRegion" - static let dictinaryKeyIdentityPoolId = "IdentityPoolId" + static let dictionaryKeyIdentityPoolId = "IdentityPoolId" } final class DefaultCommonMapView: UIView, NavigationMapProtocol { @@ -31,7 +30,6 @@ final class DefaultCommonMapView: UIView, NavigationMapProtocol { var isDrawCirle = false var enableGeofenceDrag = false var geofenceAnnotationRadius: Int64 = 80 - private var signingDelegate: MLNOfflineStorageDelegate? private var isiPad = UIDevice.current.userInterfaceIdiom == .pad private(set) var mapMode: MapMode = .search @@ -76,27 +74,11 @@ final class DefaultCommonMapView: UIView, NavigationMapProtocol { } func setupMapView() { - let identityPoolId: String - if let customModel = UserDefaultsHelper.getObject(value: CustomConnectionModel.self, key: .awsConnect) { - identityPoolId = customModel.identityPoolId - } else { - identityPoolId = Bundle.main.object(forInfoDictionaryKey: Constant.dictinaryKeyIdentityPoolId) as! String - } - - let regionName = identityPoolId.toRegionString() - let mapName = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - DispatchQueue.main.async { [self] in - signingDelegate = AWSSignatureV4Delegate(region: regionName) - MLNOfflineStorage.shared.delegate = signingDelegate - mapView.styleURL = URL(string: "https://maps.geo.\(regionName).amazonaws.com/maps/v0/maps/\(mapName?.imageType.mapName ?? "EsriLight")/style-descriptor") + mapView.styleURL = DefaultMapStyles.getMapStyleUrl() + locateMeAction() + mapView.showsUserLocation = true } - - // it is just to force to redraw the mapView - mapView.zoomLevel = mapView.zoomLevel + 0.01 - - locateMeAction() - mapView.showsUserLocation = true } func isLocateMeButtonDisabled(state: Bool) { @@ -104,7 +86,7 @@ final class DefaultCommonMapView: UIView, NavigationMapProtocol { guard !state, let userCoordinates = mapView.userLocation?.coordinate, CLLocationCoordinate2DIsValid(userCoordinates) else { - mapView.setCenter(CLLocationCoordinate2D(latitude: Constant.amazonHqMapPosition.latitude, longitude: Constant.amazonHqMapPosition.longitude), zoomLevel: Constant.annotationMapZoomValue, animated: false) + mapView.setCenter(CLLocationCoordinate2D(latitude: AppConstants.amazonHqMapPosition.latitude, longitude: AppConstants.amazonHqMapPosition.longitude), zoomLevel: Constant.annotationMapZoomValue, animated: false) return } diff --git a/LocationServices/LocationServicesTests/AWSLocationTravelModeTests.swift b/LocationServices/LocationServicesTests/AWSLocationTravelModeTests.swift index bea33e4d..dc1244c7 100644 --- a/LocationServices/LocationServicesTests/AWSLocationTravelModeTests.swift +++ b/LocationServices/LocationServicesTests/AWSLocationTravelModeTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import LocationServices -import AWSLocation +import AWSGeoRoutes final class AWSLocationTravelModeTests: XCTestCase { @@ -21,17 +21,17 @@ final class AWSLocationTravelModeTests: XCTestCase { } func testInitWithWalking() throws { - let travelMode = LocationClientTypes.TravelMode(routeType: .walking) - XCTAssertEqual(travelMode, .walking, "Route mode Waking expected") + let travelMode = GeoRoutesClientTypes.RouteTravelMode(routeType: .pedestrian) + XCTAssertEqual(travelMode, .pedestrian, "Route mode Waking expected") } func testInitWithCar() throws { - let travelMode = LocationClientTypes.TravelMode(routeType: .car) + let travelMode = GeoRoutesClientTypes.RouteTravelMode(routeType: .car) XCTAssertEqual(travelMode, .car, "Route mode Car expected") } func testInitWithTruck() throws { - let travelMode = LocationClientTypes.TravelMode(routeType: .truck) + let travelMode = GeoRoutesClientTypes.RouteTravelMode(routeType: .truck) XCTAssertEqual(travelMode, .truck, "Route mode Waking expected") } diff --git a/LocationServices/LocationServicesTests/DataProviderViewModelTests.swift b/LocationServices/LocationServicesTests/DataProviderViewModelTests.swift deleted file mode 100644 index 49ec9c28..00000000 --- a/LocationServices/LocationServicesTests/DataProviderViewModelTests.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// DataProviderViewModelTests.swift -// LocationServicesTests -// -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 - -import XCTest -@testable import LocationServices - -final class DataProviderViewModelTests: XCTestCase { - - let dataProviderViewModel = DataProviderViewModel() - - override func setUpWithError() throws { - if let domain = Bundle.main.bundleIdentifier { - UserDefaults.standard.removePersistentDomain(forName: domain) - UserDefaults.standard.synchronize() - } - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testGetItemCount() throws { - XCTAssertEqual(dataProviderViewModel.getItemCount(), 2, "Expected count") - } - - func testGetItemFor() throws { - let indexPath = IndexPath(row: 0, section: 0) - XCTAssertEqual(dataProviderViewModel.getItemFor(indexPath).title, "Esri", "Expected count") - } - - func testSaveSelectedState() throws { - let indexPath = IndexPath(row: 0, section: 0) - dataProviderViewModel.saveSelectedState(indexPath) - let obj = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - XCTAssertEqual(obj?.title, "Light", "Expected saved selected state") - - } - - func testLoadData() throws { - let indexPath = IndexPath(row: 0, section: 0) - dataProviderViewModel.saveSelectedState(indexPath) - dataProviderViewModel.loadData() - let obj = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - XCTAssertEqual(obj?.title, "Light", "Expected saved selected state") - } - -} diff --git a/LocationServices/LocationServicesTests/DirectionViewModelTests.swift b/LocationServices/LocationServicesTests/DirectionViewModelTests.swift index 8c01ff16..acc332b8 100644 --- a/LocationServices/LocationServicesTests/DirectionViewModelTests.swift +++ b/LocationServices/LocationServicesTests/DirectionViewModelTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import LocationServices import CoreLocation -import AWSLocation +import AWSGeoRoutes final class DirectionViewModelTests: XCTestCase { @@ -175,8 +175,12 @@ final class DirectionViewModelTests: XCTestCase { } func testCalculateRouteWith() async throws { - let direction = DirectionPresentation(model:CalculateRouteOutput(), travelMode: .car) - routingService.putResult = [LocationClientTypes.TravelMode.car: .success(direction)] + let step = GeoRoutesClientTypes.RouteVehicleTravelStep(distance: 2, duration: 2, instruction: "continue", type: .continue) + let legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: [step]) + let routeLeg = GeoRoutesClientTypes.RouteLeg(travelMode: .car, vehicleLegDetails: legDetails) + let route = GeoRoutesClientTypes.Route(legs: [routeLeg]) + let direction = DirectionPresentation(model: route, travelMode: .car) + routingService.putResult = [GeoRoutesClientTypes.RouteTravelMode.car: .success(direction)] if let result = try await directionViewModel.calculateRouteWith(destinationPosition: CLLocationCoordinate2D(latitude: 40.75803155895524, longitude: -73.9855533309874) , departurePosition: CLLocationCoordinate2D(latitude: 40.75803155895524, longitude: -73.9855533309874)) { XCTAssertGreaterThan(result.0.count, 0, "Expected atleast 1 count") diff --git a/LocationServices/LocationServicesTests/ExploreMapStyleViewModelTests.swift b/LocationServices/LocationServicesTests/ExploreMapStyleViewModelTests.swift index 509e3a99..1236abef 100644 --- a/LocationServices/LocationServicesTests/ExploreMapStyleViewModelTests.swift +++ b/LocationServices/LocationServicesTests/ExploreMapStyleViewModelTests.swift @@ -40,9 +40,4 @@ final class ExploreMapStyleViewModelTests: XCTestCase { let exploreMapStyleViewModel = ExploreMapStyleViewModel() XCTAssertGreaterThan(exploreMapStyleViewModel.getItemsCount(), 0, "Expected data count greater than 0") } - - func testGetItem() throws { - let exploreMapStyleViewModel = ExploreMapStyleViewModel() - XCTAssertEqual(exploreMapStyleViewModel.getItem(with: 0), .esri, "Expected esri map style") - } } diff --git a/LocationServices/LocationServicesTests/ExploreViewModelTests.swift b/LocationServices/LocationServicesTests/ExploreViewModelTests.swift index 858aa426..c744143c 100644 --- a/LocationServices/LocationServicesTests/ExploreViewModelTests.swift +++ b/LocationServices/LocationServicesTests/ExploreViewModelTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import LocationServices import CoreLocation -import AWSLocation +import AWSGeoRoutes final class ExploreViewModelTests: XCTestCase { @@ -86,10 +86,10 @@ final class ExploreViewModelTests: XCTestCase { } func testReCalculateRouteReturnSuccess() async throws { - let direction = DirectionPresentation(model:CalculateRouteOutput(), travelMode: .car) + let direction = DirectionPresentation(model: GeoRoutesClientTypes.Route(), travelMode: .car) locationService.mockGetPlaceResult = .success(search) locationService.mockSearchWithPositionResult = .success([search]) - routingService.putResult = [LocationClientTypes.TravelMode.car: .success(direction)] + routingService.putResult = [GeoRoutesClientTypes.RouteTravelMode.car: .success(direction)] exploreViewModel.activateRoute(route: routeModel) try await exploreViewModel.reCalculateRoute(with: destinationLocation) @@ -119,7 +119,7 @@ final class ExploreViewModelTests: XCTestCase { await exploreViewModel.loadPlace(for: destinationLocation, userLocation: departureLocation) XCTWaiter().wait(until: { - return !self.delegate.hasAnnotationShown + return self.delegate.hasAnnotationShown }, timeout: Constants.waitRequestDuration, message: "Expected hasAnnotationShown true") } diff --git a/LocationServices/LocationServicesTests/GeofenceViewModelTests.swift b/LocationServices/LocationServicesTests/GeofenceViewModelTests.swift index 1a03895a..a90b7195 100644 --- a/LocationServices/LocationServicesTests/GeofenceViewModelTests.swift +++ b/LocationServices/LocationServicesTests/GeofenceViewModelTests.swift @@ -45,38 +45,38 @@ final class GeofenceViewModelTests: XCTestCase { viewModel.delegate = delegate } - func test_hasUserLoggedIn_signedIn() throws { + func testHasUserLoggedInSignedIn() throws { UserDefaultsHelper.setAppState(state: .loggedIn) let isLoggedIn = viewModel.hasUserLoggedIn() XCTAssertTrue(isLoggedIn) } - func test_hasUserLoggedIn_signedOut() throws { + func testHasUserLoggedInSignedOut() throws { UserDefaultsHelper.setAppState(state: .initial) let isLoggedIn = viewModel.hasUserLoggedIn() XCTAssertFalse(isLoggedIn) } - func test_getGeofence_withEmptyArray() throws { + func testGetGeofenceWithEmptyArray() throws { let result = viewModel.getGeofence(with: Constants.cityName) XCTAssertNil(result) } - func test_getGeofence_withoutExistsId() throws { + func testGetGeofenceWithoutExistsId() throws { viewModel.addGeofence(model: Constants.geofence) let result = viewModel.getGeofence(with: Constants.cityName + "111") XCTAssertNil(result) } - func test_getGeofence_withExistsId() throws { + func testGetGeofenceWithExistsId() throws { viewModel.addGeofence(model: Constants.geofence) let result = viewModel.getGeofence(with: Constants.cityName) result?.compare(id: Constants.cityName, lat: Constants.geofenceLatitude, long: Constants.geofenceLongitude, radius: Constants.geofenceRadius) } - func test_addGeofence_withNewValue() throws { + func testAddGeofenceWithNewValue() throws { viewModel.addGeofence(model: Constants.geofence) XCTAssertEqual(viewModel?.geofences.count, 1) @@ -84,7 +84,7 @@ final class GeofenceViewModelTests: XCTestCase { result?.compare(id: Constants.cityName, lat: Constants.geofenceLatitude, long: Constants.geofenceLongitude, radius: Constants.geofenceRadius) } - func test_addGeofence_withExistedValue() throws { + func testAddGeofenceWithExistedValue() throws { viewModel.addGeofence(model: Constants.geofence) XCTAssertEqual(viewModel?.geofences.count, 1) @@ -99,14 +99,14 @@ final class GeofenceViewModelTests: XCTestCase { updatedResult?.compare(id: Constants.cityName, lat: Constants.updateGeofenceLatitude, long: Constants.updateGeofenceLongitude, radius: Constants.updateGeofenceRadius) } - func test_fetchListOfGeofences_signedOut() async throws { + func testFetchListOfGeofencesSignedOut() async throws { delegate.models = nil UserDefaultsHelper.setAppState(state: .initial) await viewModel.fetchListOfGeofences() XCTAssertNil(delegate?.models) } - func test_fetchListOfGeofences_SignedIn_success() async throws { + func testFetchListOfGeofencesSignedInSuccess() async throws { UserDefaultsHelper.setAppState(state: .loggedIn) apiService.mockGetGeofenceListResult = .success([Constants.geofence]) @@ -120,7 +120,7 @@ final class GeofenceViewModelTests: XCTestCase { delegate.models?.first?.compare(id: Constants.cityName, lat: Constants.geofenceLatitude, long: Constants.geofenceLongitude, radius: Constants.geofenceRadius) } - func test_fetchListOfGeofences_signedIn_failure() async throws { + func testFetchListOfGeofencesSignedInFailure() async throws { UserDefaultsHelper.setAppState(state: .loggedIn) apiService.mockGetGeofenceListResult = .failure(Constants.defaultError) diff --git a/LocationServices/LocationServicesTests/LocationManagerTests.swift b/LocationServices/LocationServicesTests/LocationManagerTests.swift index a807a5cc..5490a280 100644 --- a/LocationServices/LocationServicesTests/LocationManagerTests.swift +++ b/LocationServices/LocationServicesTests/LocationManagerTests.swift @@ -28,50 +28,50 @@ class LocationManagerTests: XCTestCase { locationManager = nil } - func test_getAuthorizationStatus() { + func testGetAuthorizationStatus() { mockLocationManager.authorizationStatus = .authorizedAlways XCTAssertEqual(locationManager.getAuthorizationStatus(), .authorizedAlways) } - func test_setDelegate() { + func testsetDelegate() { let mockDelegate = MockCLLocationManagerDelegate() locationManager.setDelegate(mockDelegate) XCTAssertEqual(mockLocationManager.delegate as? MockCLLocationManagerDelegate, mockDelegate) } - func test_startUpdatingLocation_withPermissions() { + func testStartUpdatingLocationWithPermissions() { locationManager.startUpdatingLocation() XCTAssertTrue(mockLocationManager.didStartUpdatingLocation) XCTAssertTrue(mockLocationManager.didStartUpdatingHeading) } - func test_startUpdatingLocation_withoutPermissions() { + func testStartUpdatingLocationWithoutPermissions() { mockLocationManager.authorizationStatus = .notDetermined locationManager.startUpdatingLocation() XCTAssertFalse(mockLocationManager.didStartUpdatingLocation) XCTAssertFalse(mockLocationManager.didStartUpdatingHeading) } - func test_startUpdatingLocation_withDeclinedPermissions() { + func testStartUpdatingLocationWithDeclinedPermissions() { mockLocationManager.authorizationStatus = .denied locationManager.startUpdatingLocation() XCTAssertFalse(mockLocationManager.didStartUpdatingLocation) XCTAssertFalse(mockLocationManager.didStartUpdatingHeading) } - func test_requestPermissions_withGrantedAccess() { + func testRequestPermissionsWithGrantedAccess() { mockLocationManager.authorizationStatus = .authorizedAlways locationManager.requestPermissions() XCTAssertFalse(mockAlertPresenter.didShowAlert) } - func test_requestPermissions_withDeniedAccess() { + func testRequestPermissionsWithDeniedAccess() { mockLocationManager.authorizationStatus = .denied locationManager.requestPermissions() XCTAssertTrue(mockAlertPresenter.didShowAlert) } - func test_requestPermissions_withNotDeterminedAccess() { + func testRequestPermissionsWithNotDeterminedAccess() { mockLocationManager.authorizationStatus = .notDetermined locationManager.requestPermissions() XCTAssertFalse(mockAlertPresenter.didShowAlert) @@ -81,7 +81,7 @@ class LocationManagerTests: XCTestCase { XCTAssertEqual(locationManager.getAuthorizationStatus(), .authorizedWhenInUse) } - func test_performLocationDependentAction_withGrantedAccess() { + func testPerformLocationDependentActionWithGrantedAccess() { mockLocationManager.authorizationStatus = .authorizedWhenInUse let expectation = XCTestExpectation(description: "Location dependent action should be performed") @@ -93,7 +93,7 @@ class LocationManagerTests: XCTestCase { wait(for: [expectation], timeout: 1) } - func test_performLocationDependentAction_withoutGrantedAccess() { + func testPerformLocationDependentActionWithoutGrantedAccess() { mockLocationManager.authorizationStatus = .notDetermined let expectation = XCTestExpectation(description: "Location dependent action should not be performed") diff --git a/LocationServices/LocationServicesTests/LoginViewModelTests.swift b/LocationServices/LocationServicesTests/LoginViewModelTests.swift index af588edd..45346389 100644 --- a/LocationServices/LocationServicesTests/LoginViewModelTests.swift +++ b/LocationServices/LocationServicesTests/LoginViewModelTests.swift @@ -93,7 +93,6 @@ final class LoginViewModelTests: XCTestCase { } class LoginViewModelOutputDelegateMock: LoginViewModelOutputDelegate { - var hasCloudConnectionCompleted = false var hasCloudConnectionDisconnected = false var hasLoginCompleted = false @@ -113,6 +112,9 @@ class LoginViewModelOutputDelegateMock: LoginViewModelOutputDelegate { hasLoginCompleted = true } + func loginCancelled() { + } + func logoutCompleted() { hasLogoutCompleted = true } diff --git a/LocationServices/LocationServicesTests/MapStyleViewModelTests.swift b/LocationServices/LocationServicesTests/MapStyleViewModelTests.swift index 1154b26c..f7bc171c 100644 --- a/LocationServices/LocationServicesTests/MapStyleViewModelTests.swift +++ b/LocationServices/LocationServicesTests/MapStyleViewModelTests.swift @@ -25,29 +25,29 @@ final class MapStyleViewModelTests: XCTestCase { } func testGetSectionsCount() throws { - XCTAssertEqual(mapStyleViewModel.getSectionsCount(), 2, "Expected sections count") + XCTAssertEqual(mapStyleViewModel.getSectionsCount(), 1, "Expected sections count") } func testGetSectionTitle() throws { - XCTAssertEqual(mapStyleViewModel.getSectionTitle(at: 0), "Esri", "Expected section title") + XCTAssertEqual(mapStyleViewModel.getSectionTitle(at: 0), "Map Style", "Expected section title") } func testGetItemCount() throws { - XCTAssertEqual(mapStyleViewModel.getItemCount(at: 0), 6, "Expected item count") + XCTAssertEqual(mapStyleViewModel.getItemCount(at: 0), 4, "Expected item count") } func testGetCellItems() throws { let indexPath = IndexPath(row: 1, section: 0) let item = mapStyleViewModel.getCellItem(indexPath) - XCTAssertEqual(item?.title, "Streets", "Expected cell item title") + XCTAssertEqual(item?.title, "Monochrome", "Expected cell item title") } func testSaveSelectedState() throws { let indexPath = IndexPath(row: 1, section: 0) mapStyleViewModel.saveSelectedState(indexPath) let obj = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - XCTAssertEqual(obj?.title, "Streets", "Expected saved selected state") + XCTAssertEqual(obj?.title, "Monochrome", "Expected saved selected state") } @@ -56,7 +56,7 @@ final class MapStyleViewModelTests: XCTestCase { mapStyleViewModel.saveSelectedState(indexPath) mapStyleViewModel.loadLocalMapData() let obj = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - XCTAssertEqual(obj?.title, "Navigation", "Expected saved selected state") + XCTAssertEqual(obj?.title, "Hybrid", "Expected saved selected state") } } diff --git a/LocationServices/LocationServicesTests/Mocks/AWSLoginServiceMock.swift b/LocationServices/LocationServicesTests/Mocks/AWSLoginServiceMock.swift index 682fef6a..b226f0aa 100644 --- a/LocationServices/LocationServicesTests/Mocks/AWSLoginServiceMock.swift +++ b/LocationServices/LocationServicesTests/Mocks/AWSLoginServiceMock.swift @@ -9,6 +9,10 @@ import Foundation @testable import LocationServices class AWSLoginServiceMock : AWSLoginServiceProtocol { + func getAWSConfigurationModel() -> LocationServices.CustomConnectionModel? { + return CustomConnectionModel(identityPoolId: "mockidentityPoolId", userPoolClientId: "mockuserPoolClientId", userPoolId: "mockuserPoolId", userDomain: "mockuserDomain", webSocketUrl: "mockwebSocketUrl", apiKey: "mockapiKey", region: "mockregion") + } + func disconnectAWS() { } diff --git a/LocationServices/LocationServicesTests/Mocks/LocationAPIServiceMock.swift b/LocationServices/LocationServicesTests/Mocks/LocationAPIServiceMock.swift index abf169d3..a9760597 100644 --- a/LocationServices/LocationServicesTests/Mocks/LocationAPIServiceMock.swift +++ b/LocationServices/LocationServicesTests/Mocks/LocationAPIServiceMock.swift @@ -10,6 +10,7 @@ import Foundation import AWSLocation class LocationAPIServiceMock: LocationServiceable { + var mockSearchTextResult: Result<[SearchPresentation], Error> = .success([]) var mockSearchTextWithSuggestionResult: Result<[SearchPresentation], Error> = .success([]) var mockSearchWithPositionResult: Result<[SearchPresentation], Error> = .success([]) @@ -25,11 +26,11 @@ class LocationAPIServiceMock: LocationServiceable { return mockSearchTextResult } - func searchTextWithSuggestion(text: String, userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> { + func searchWithSuggest(text: String, userLat: Double?, userLong: Double?) async -> Result<[LocationServices.SearchPresentation], any Error> { return mockSearchTextWithSuggestionResult } - func searchWithPosition(position: [Double], userLat: Double?, userLong: Double?) async -> Result<[SearchPresentation], Error> { + func reverseGeocode(position: [Double], userLat: Double?, userLong: Double?) async -> Result<[LocationServices.SearchPresentation], any Error> { return mockSearchWithPositionResult } diff --git a/LocationServices/LocationServicesTests/Mocks/RoutingAPIServiceMock.swift b/LocationServices/LocationServicesTests/Mocks/RoutingAPIServiceMock.swift index a42962c0..46303ab4 100644 --- a/LocationServices/LocationServicesTests/Mocks/RoutingAPIServiceMock.swift +++ b/LocationServices/LocationServicesTests/Mocks/RoutingAPIServiceMock.swift @@ -8,16 +8,16 @@ import Foundation @testable import LocationServices -import AWSLocation +import AWSGeoRoutes import CoreLocation class RoutingAPIServiceMock: RoutingServiceable { - func calculateRouteWith(depaturePosition: CLLocationCoordinate2D, destinationPosition: CLLocationCoordinate2D, travelModes: [AWSLocation.LocationClientTypes.TravelMode], avoidFerries: Bool, avoidTolls: Bool) async throws -> [AWSLocation.LocationClientTypes.TravelMode : Result] { + func calculateRouteWith(depaturePosition: CLLocationCoordinate2D, destinationPosition: CLLocationCoordinate2D, travelModes: [GeoRoutesClientTypes.RouteTravelMode], avoidFerries: Bool, avoidTolls: Bool) async throws -> [GeoRoutesClientTypes.RouteTravelMode : Result] { let result = self.putResult return result! } - var putResult: [LocationClientTypes.TravelMode: Result]? + var putResult: [GeoRoutesClientTypes.RouteTravelMode: Result]? let delay: TimeInterval @@ -25,7 +25,7 @@ class RoutingAPIServiceMock: RoutingServiceable { self.delay = delay } - func calculateRouteWith(depaturePosition: CLLocationCoordinate2D, destinationPosition: CLLocationCoordinate2D, travelModes: [LocationClientTypes.TravelMode], avoidFerries: Bool, avoidTolls: Bool, completion: @escaping (([LocationClientTypes.TravelMode : Result]) -> Void)) { + func calculateRouteWith(depaturePosition: CLLocationCoordinate2D, destinationPosition: CLLocationCoordinate2D, travelModes: [GeoRoutesClientTypes.RouteTravelMode], avoidFerries: Bool, avoidTolls: Bool, completion: @escaping (([GeoRoutesClientTypes.RouteTravelMode : Result]) -> Void)) { perform { [weak self] in guard let result = self?.putResult else { return } completion(result) diff --git a/LocationServices/LocationServicesTests/NavigationVCViewModelTests.swift b/LocationServices/LocationServicesTests/NavigationVCViewModelTests.swift index 14cc2673..7976b626 100644 --- a/LocationServices/LocationServicesTests/NavigationVCViewModelTests.swift +++ b/LocationServices/LocationServicesTests/NavigationVCViewModelTests.swift @@ -7,7 +7,7 @@ import XCTest @testable import LocationServices -import AWSLocation +import AWSGeoRoutes final class NavigationVCViewModelTests: XCTestCase { @@ -24,113 +24,99 @@ final class NavigationVCViewModelTests: XCTestCase { } func testInitWithValidData() throws { - let step = LocationClientTypes.Step() - let steps = NavigationSteps(model: step) - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [steps], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) - XCTAssertEqual(navigationVCViewModel.firstDestionation?.placeName, "Times Square", "Expected Times Square place name") + let step = GeoRoutesClientTypes.RouteVehicleTravelStep(distance: 2, duration: 2, instruction: "continue", type: .continue) + let legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: [step]) + let routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) + XCTAssertEqual(navigationVCViewModel.firstDestination?.placeName, "Times Square", "Expected Times Square place name") } func testInitWithEmptySteps() throws { - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) - XCTAssertEqual(navigationVCViewModel.firstDestionation?.placeName, "Times Square", "Expected Times Square place name") + let legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: []) + let routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) + XCTAssertEqual(navigationVCViewModel.firstDestination?.placeName, "Times Square", "Expected Times Square place name") } func testInitWithStepsWithoutStreetNames() throws { let firstDestination = MapModel(placeName: "Times Square", placeAddress: nil, placeLat: 40.75804781268635, placeLong: -73.98554260340953) let secondDestination = MapModel(placeName: "CUNY Graduate Center", placeAddress: nil, placeLat: 40.7487776237092, placeLong: -73.98404872540857) - - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) - XCTAssertEqual(navigationVCViewModel.firstDestionation?.placeName, "Times Square", "Expected Times Square place name") + var legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: []) + var routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) + XCTAssertEqual(navigationVCViewModel.firstDestination?.placeName, "Times Square", "Expected Times Square place name") } func testUpdateWithValidData() throws { + var legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: []) + var routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) + + let step = GeoRoutesClientTypes.RouteVehicleTravelStep(distance: 2, duration: 2, instruction: "continue", type: .continue) + legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: [step]) + routeLegDetails = RouteLegDetails(model: legDetails) + navigationVCViewModel.update(routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 20)) - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) - - var step = LocationClientTypes.Step() - step.durationSeconds = 2 - let steps = NavigationSteps(model: step) - - navigationVCViewModel.update(steps: [steps], summaryData: (totalDistance: 0.7, totalDuration: 20)) - - XCTAssertEqual(navigationVCViewModel.steps[0].duration, 2, "Expected steps duration 2") + XCTAssertEqual(navigationVCViewModel.routeLegDetails[0].navigationSteps[0].duration, 2, "Expected steps duration 2") } func testGetSummaryData() throws { + var legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: []) + var routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) - - var step = LocationClientTypes.Step() - step.durationSeconds = 2 - let steps = NavigationSteps(model: step) + let step = GeoRoutesClientTypes.RouteVehicleTravelStep(distance: 2, duration: 2, instruction: "continue", type: .continue) + legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: [step]) + routeLegDetails = RouteLegDetails(model: legDetails) - navigationVCViewModel.update(steps: [steps], summaryData: (totalDistance: 0.7, totalDuration: 20)) + navigationVCViewModel.update(routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 20)) - XCTAssertEqual(navigationVCViewModel.getSummaryData().totalDistance, "700 m", "Expected summary total distance 700.0 m") + XCTAssertEqual(navigationVCViewModel.getSummaryData().totalDistance, "1 m", "Expected summary total distance 1 m") } func testGetDataWithZeroSteps() throws { - - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) + let legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: []) + let routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) XCTAssertEqual(navigationVCViewModel.getData().count, 0, "Expected get data count") } func testGetDataWithOneStep() throws { - - var step = LocationClientTypes.Step() - step.durationSeconds = 2 - step.distance = 0.01 - step.startPosition = [40.7487776237092, -73.98554260340953] - let steps = NavigationSteps(model: step) - - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [steps], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) + let step = GeoRoutesClientTypes.RouteVehicleTravelStep(distance: 1, duration: 2, instruction: "continue", type: .continue) + let legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: [step]) + let routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) XCTAssertEqual(navigationVCViewModel.getData().count, 0, "Expected get data count") } func testGetDataWithMultipleSteps() throws { - - var step1 = LocationClientTypes.Step() - step1.durationSeconds = 2 - step1.distance = 0.01 - step1.startPosition = [40.7487776237092, -73.98554260340953] - let steps1 = NavigationSteps(model: step1) - - var step2 = LocationClientTypes.Step() - step2.durationSeconds = 2 - step2.distance = 0.01 - step2.startPosition = [40.75617221372751, -73.98621241967182] - let steps2 = NavigationSteps(model: step1) - - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [steps1, steps2], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) + let step1 = GeoRoutesClientTypes.RouteVehicleTravelStep(distance: 1, duration: 2, instruction: "continue", type: .continue) + let step2 = GeoRoutesClientTypes.RouteVehicleTravelStep(distance: 2, duration: 5, instruction: "continue", type: .continue) + let legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: [step1, step2]) + let routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) XCTAssertEqual(navigationVCViewModel.getData().count, 0, "Expected get data count") } func testGetItemCountWithZeroSteps() throws { - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) + let legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: []) + let routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) XCTAssertEqual(navigationVCViewModel.getItemCount(), 0, "Expected get item count") } func testGetItemCountWithValidSteps() throws { - - var step1 = LocationClientTypes.Step() - step1.durationSeconds = 2 - step1.distance = 0.01 - step1.startPosition = [40.7487776237092, -73.98554260340953] - let steps1 = NavigationSteps(model: step1) - - var step2 = LocationClientTypes.Step() - step2.durationSeconds = 2 - step2.distance = 0.01 - step2.startPosition = [40.75617221372751, -73.98621241967182] - let steps2 = NavigationSteps(model: step1) - - let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), steps: [steps1, steps2], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestionation: firstDestination, secondDestionation: secondDestination) + let step1 = GeoRoutesClientTypes.RouteVehicleTravelStep(distance: 1, duration: 2, instruction: "continue", type: .continue) + let step2 = GeoRoutesClientTypes.RouteVehicleTravelStep(distance: 2, duration: 5, instruction: "continue", type: .continue) + let legDetails = GeoRoutesClientTypes.RouteVehicleLegDetails(travelSteps: [step1, step2]) + let routeLegDetails = RouteLegDetails(model: legDetails) + let navigationVCViewModel = NavigationVCViewModel(service: LocationService(), routeLegDetails: [routeLegDetails], summaryData: (totalDistance: 0.7, totalDuration: 15), firstDestination: firstDestination, secondDestination: secondDestination) XCTAssertEqual(navigationVCViewModel.getItemCount(), 0, "Expected get item count") } diff --git a/LocationServices/LocationServicesTests/POICardViewModelTests.swift b/LocationServices/LocationServicesTests/POICardViewModelTests.swift index ecca9c8f..c964c262 100644 --- a/LocationServices/LocationServicesTests/POICardViewModelTests.swift +++ b/LocationServices/LocationServicesTests/POICardViewModelTests.swift @@ -8,7 +8,7 @@ import XCTest @testable import LocationServices import CoreLocation -import AWSLocation +import AWSGeoRoutes final class POICardViewModelTests: XCTestCase { @@ -43,35 +43,16 @@ final class POICardViewModelTests: XCTestCase { XCTAssertEqual(delegate.populateDatasErrorMessage, "Location permission denied", "Expected Location permission denied") } - func testFetchDatasWithMaxDistance() async throws { - pOICardViewModel.setUserLocation(lat: userLocation.latitude, long: userLocation.longitude) - try await pOICardViewModel.fetchDatas() - - XCTWaiter().wait(until: { [weak self] in - return self?.delegate.populateDatasErrorMessage == "In DataSource Esri, all waypoints must be within 400km" - }, timeout: Constants.waitRequestDuration, message: "MapModel should've throw max distance error") - } - func testFetchDatasWithSuccess() async throws { pOICardViewModel.setUserLocation(lat: 40.4400930458457, long: -80.00348250162394) - let direction = DirectionPresentation(model:CalculateRouteOutput(), travelMode: .car) - routingService.putResult = [LocationClientTypes.TravelMode.car: .success(direction)] + let direction = DirectionPresentation(model: GeoRoutesClientTypes.Route(), travelMode: .car) + routingService.putResult = [GeoRoutesClientTypes.RouteTravelMode.car: .success(direction)] try await pOICardViewModel.fetchDatas() XCTWaiter().wait(until: { return self.delegate.populateDatasCalled }, timeout: Constants.waitRequestDuration, message: "MapModel should've valid distance") } - - func testFetchDatasWithFailure() async throws { - pOICardViewModel.setUserLocation(lat: 0, long: -100) - try await pOICardViewModel.fetchDatas() - - XCTWaiter().wait(until: { [weak self] in - return (self?.delegate.populateDatasErrorMessage == "In DataSource Esri, all waypoints must be within 400km") - }, timeout: Constants.waitRequestDuration, message: "populateDatas should've been called with failure") - } - } diff --git a/LocationServices/LocationServicesTests/UserDefaultsHelperTests.swift b/LocationServices/LocationServicesTests/UserDefaultsHelperTests.swift index 6c6b8e0e..54f864d2 100644 --- a/LocationServices/LocationServicesTests/UserDefaultsHelperTests.swift +++ b/LocationServices/LocationServicesTests/UserDefaultsHelperTests.swift @@ -35,7 +35,7 @@ final class UserDefaultsHelperTests: XCTestCase { let mapStyle: MapStyleModel = DefaultUserSettings.mapStyle UserDefaultsHelper.saveObject(value: mapStyle, key: .mapStyle) let savedMapStyle = UserDefaultsHelper.getObject(value: MapStyleModel.self, key: .mapStyle) - XCTAssertEqual(savedMapStyle?.type, mapStyle.type, "Expected \(mapStyle) value for this key.") + XCTAssertEqual(savedMapStyle?.imageType, mapStyle.imageType, "Expected \(mapStyle) value for this key.") } func testSetAndGetAppState() throws { diff --git a/LocationServices/LocationServicesUITests/MapUITests.swift b/LocationServices/LocationServicesUITests/MapUITests.swift index bde8347c..3ca17f5a 100644 --- a/LocationServices/LocationServicesUITests/MapUITests.swift +++ b/LocationServices/LocationServicesUITests/MapUITests.swift @@ -56,8 +56,8 @@ final class MapUITests: LocationServicesUITests { let app = startApp(allowPermissions: false) var exploreScreen = UITestExploreScreen(app: app) .waitForMapToBeRendered() - exploreScreen = testMapStyle(screen: exploreScreen, style: .street) - exploreScreen = testMapStyle(screen: exploreScreen, style: .Imagery) + exploreScreen = testMapStyle(screen: exploreScreen, style: .standard) + exploreScreen = testMapStyle(screen: exploreScreen, style: .hybrid) } func testMapStyle(screen: UITestExploreScreen, style: MapStyleImages) -> UITestExploreScreen { diff --git a/LocationServices/LocationServicesUITests/Models/UITestRouteType.swift b/LocationServices/LocationServicesUITests/Models/UITestRouteType.swift index 9c146be7..e9d5bd08 100644 --- a/LocationServices/LocationServicesUITests/Models/UITestRouteType.swift +++ b/LocationServices/LocationServicesUITests/Models/UITestRouteType.swift @@ -8,12 +8,12 @@ import Foundation enum RouteType { - case walk, car, truck + case pedestrian, car, truck var containerId: String { switch self { - case .walk: - return ViewsIdentifiers.Routing.walkContainer + case .pedestrian: + return ViewsIdentifiers.Routing.pedestrianContainer case .car: return ViewsIdentifiers.Routing.carContainer case .truck: diff --git a/LocationServices/LocationServicesUITests/NavigationUITests.swift b/LocationServices/LocationServicesUITests/NavigationUITests.swift index e5f0dd8a..a8bd8e0f 100644 --- a/LocationServices/LocationServicesUITests/NavigationUITests.swift +++ b/LocationServices/LocationServicesUITests/NavigationUITests.swift @@ -22,8 +22,8 @@ final class NavigationUITests: LocationServicesUITests { static let timesSquareAddress = "New York Times Square" - static let walkDepartureAddress = "cloverdale perth" - static let walkDestinationAddress = "Kewdale Perth" + static let pedestrianDepartureAddress = "cloverdale perth" + static let pedestrianDestinationAddress = "Kewdale Perth" static let navigationStartLocation = CLLocation(latitude: 40.728489, longitude: -74.007167) static let navigationMoveLocation = CLLocation(latitude: 40.741940, longitude: -73.974948) @@ -47,9 +47,13 @@ final class NavigationUITests: LocationServicesUITests { var screen = UITestExploreScreen(app: app) .waitForMapToBeRendered() .tapMapStyles() - .select(style: .street) - .select(style: .light) - .select(style: .light) + .select(style: .standard) + .select(style: .monochrome) + .select(style: .satellite) + .tapPoliticalViewButton() + .select(politicalView: PoliticalViewTypes.first) + .tapCloseButton() + .checkPoliticalViewButtonSubtitle(type: PoliticalViewTypes.first) .tapCloseButton() .tapRouting() .selectDepartureTextField() @@ -69,21 +73,21 @@ final class NavigationUITests: LocationServicesUITests { } func testRouteTypes() throws { - let app = startApp(allowPermissions: false) + let app = startApp(allowPermissions: true) var screen = UITestExploreScreen(app: app) .waitForMapToBeRendered() .tapRouting() .selectDepartureTextField() - .typeInDepartureTextField(text: Constants.walkDepartureAddress) + .typeInDepartureTextField(text: Constants.pedestrianDepartureAddress) .selectSearchResult(index: 1) .selectDestinationTextField() - .typeInDestinationTextField(text: Constants.walkDestinationAddress) + .typeInDestinationTextField(text: Constants.pedestrianDestinationAddress) .selectSearchResult(index: 1) .waitForRouteTypesContainer() .waitForNonEmptyRouteEstimatedTime(for: .car) .waitForNonEmptyRouteEstimatedDistance(for: .car) - .waitForNonEmptyRouteEstimatedTime(for: .walk) - .waitForNonEmptyRouteEstimatedDistance(for: .walk) + .waitForNonEmptyRouteEstimatedTime(for: .pedestrian) + .waitForNonEmptyRouteEstimatedDistance(for: .pedestrian) .waitForNonEmptyRouteEstimatedTime(for: .truck) .waitForNonEmptyRouteEstimatedDistance(for: .truck) .activate(mode: .car) @@ -95,7 +99,7 @@ final class NavigationUITests: LocationServicesUITests { .waitForRootView() .tapExitButton() .waitForRouteTypesContainer() - .activate(mode: .walk) + .activate(mode: .pedestrian) if UIDevice.current.userInterfaceIdiom == .pad { screen = screen.tapRoutesButton() @@ -130,6 +134,8 @@ final class NavigationUITests: LocationServicesUITests { let departureBeforeSwap = screenBeforeSwap.getDeparturePlace() let destinationBeforeSwap = screenBeforeSwap.getDestinationPlace() + Thread.sleep(forTimeInterval: 2) + let screenAfterSwap = screenBeforeSwap .swapRoute() @@ -228,19 +234,4 @@ final class NavigationUITests: LocationServicesUITests { .selectSearchResult(index: 1) .waitForRouteTypesContainer() } - - func testNavigation() throws { - XCUIDevice.shared.location = .init(location: Constants.navigationStartLocation) - let app = startApp() - var screen = UITestExploreScreen(app: app) - .waitForMapToBeRendered() - .tapSearchTextField() - .type(text: Constants.navigationEndCoordinateSearchString) - .waitForResultsInTable() - .tapFirstCell() - .waitForPoiCardView() - .tapDirectionButton() - .waitForRouteTypesContainer() - .activate(mode: .car) - } } diff --git a/LocationServices/LocationServicesUITests/Screens/UITestMapStyleScreen.swift b/LocationServices/LocationServicesUITests/Screens/UITestMapStyleScreen.swift index b2013139..f1213c8c 100644 --- a/LocationServices/LocationServicesUITests/Screens/UITestMapStyleScreen.swift +++ b/LocationServices/LocationServicesUITests/Screens/UITestMapStyleScreen.swift @@ -12,18 +12,36 @@ struct UITestMapStyleScreen: UITestScreen { private enum Identifiers { static var closeButton: String { ViewsIdentifiers.General.closeButton } + static var politicalViewButton: String { ViewsIdentifiers.General.politicalViewButton } + static var politicalViewSubitle: String { ViewsIdentifiers.General.politicalViewSubtitle } } func select(style: MapStyleImages) -> Self { - let header = getSourceHeader(for: style.sourceType) - header.tap() - let cell = getStyleCell(for: style) cell.tap() return self } + func tapPoliticalViewButton() -> UITestPoliticalViewScreen { + let button = getPoliticalViewButton() + button.tap() + + return UITestPoliticalViewScreen(app: app) + } + + func checkPoliticalViewButtonSubtitle(type: PoliticalViewType?) -> Self { + let button = getPoliticalViewButton() + let value = button.staticTexts[Identifiers.politicalViewSubitle].firstMatch.label + if let countryCode = type?.countryCode { + XCTAssert(value.starts(with: countryCode)) + } + else { + XCTFail("Political view subtitle not set correctly") + } + return self + } + func tapCloseButton() -> UITestExploreScreen { let button = getCloseButton() button.tap() @@ -37,12 +55,6 @@ struct UITestMapStyleScreen: UITestScreen { } // MARK: - Private - private func getSourceHeader(for sourceType: MapStyleSourceType) -> XCUIElement { - let view = app.staticTexts[sourceType.title].firstMatch - XCTAssertTrue(view.waitForExistence(timeout: UITestWaitTime.regular.time)) - return view - } - private func getStyleCell(for style: MapStyleImages, assert: Bool = true) -> XCUIElement { let cell = app.cells[style.mapName].firstMatch if assert { @@ -58,4 +70,10 @@ struct UITestMapStyleScreen: UITestScreen { XCTAssertTrue(button.waitForExistence(timeout: UITestWaitTime.regular.time)) return button } + + private func getPoliticalViewButton() -> XCUIElement { + let button = app.buttons[Identifiers.politicalViewButton] + XCTAssertTrue(button.waitForExistence(timeout: UITestWaitTime.regular.time)) + return button + } } diff --git a/LocationServices/LocationServicesUITests/Screens/UITestPoliticalViewScreen.swift b/LocationServices/LocationServicesUITests/Screens/UITestPoliticalViewScreen.swift new file mode 100644 index 00000000..ea398de4 --- /dev/null +++ b/LocationServices/LocationServicesUITests/Screens/UITestPoliticalViewScreen.swift @@ -0,0 +1,50 @@ +// +// UITestPoliticalViewScreen.swift +// LocationServicesUITests +// +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 + +import XCTest + +struct UITestPoliticalViewScreen: UITestScreen { + let app: XCUIApplication + + private enum Identifiers { + static var closeButton: String { ViewsIdentifiers.General.politicalViewCloseButton } + static var politicalViewCell: String { ViewsIdentifiers.General.politicalViewCell } + static var politicalViewTable: String { ViewsIdentifiers.General.politicalViewTable } + } + + func select(politicalView: PoliticalViewType?) -> Self { + let cell = getPoliticalViewCell(for: politicalView) + cell.tap() + + return self + } + + func tapCloseButton() -> UITestMapStyleScreen { + let button = getCloseButton() + button.tap() + + return UITestMapStyleScreen(app: app) + } + + // MARK: - Private + private func getPoliticalViewCell(for type: PoliticalViewType?, assert: Bool = true) -> XCUIElement { + let cell = app.tables[Identifiers.politicalViewTable].cells.firstMatch + if assert { + XCTAssertTrue(cell.waitForExistence(timeout: UITestWaitTime.regular.time)) + } else { + let _ = cell.waitForExistence(timeout: UITestWaitTime.regular.time) + } + return cell + } + + private func getCloseButton() -> XCUIElement { + let button = app.buttons[Identifiers.closeButton] + XCTAssertTrue(button.waitForExistence(timeout: UITestWaitTime.regular.time)) + return button + } +} + diff --git a/LocationServices/LocationServicesUITests/Screens/UITestSettingsDataProviderScreen.swift b/LocationServices/LocationServicesUITests/Screens/UITestSettingsDataProviderScreen.swift deleted file mode 100644 index a0a49cbc..00000000 --- a/LocationServices/LocationServicesUITests/Screens/UITestSettingsDataProviderScreen.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// UITestSettingsDataProviderScreen.swift -// Amazon Location Demo UITests -// -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: MIT-0 - -import XCTest - -struct UITestSettingsDataProviderScreen: UITestScreen { - let app: XCUIApplication - - private enum Identifiers { - } - - func select(sourceType: MapStyleSourceType) -> Self { - let cell = getSourceTypeCell(for: sourceType) - cell.tap() - - return self - } - - func tapBackButton() -> UITestSettingsScreen { - guard UIDevice.current.userInterfaceIdiom == .phone else { - return UITestSettingsScreen(app: app) - } - - let button = getBackButton() - button.tap() - - return UITestSettingsScreen(app: app) - } - - func isCellSelected(for sourceType: MapStyleSourceType) -> Bool { - let cell = getSourceTypeCell(for: sourceType) - return cell.isSelected - } - - // MARK: - Private - private func getSourceTypeCell(for style: MapStyleSourceType) -> XCUIElement { - let cell = app.cells[style.title].firstMatch - XCTAssertTrue(cell.waitForExistence(timeout: UITestWaitTime.regular.time)) - return cell - } - - private func getBackButton() -> XCUIElement { - return app.navigationBars.buttons.element(boundBy: 0) - } -} diff --git a/LocationServices/LocationServicesUITests/Screens/UITestSettingsScreen.swift b/LocationServices/LocationServicesUITests/Screens/UITestSettingsScreen.swift index 462af335..979f5617 100644 --- a/LocationServices/LocationServicesUITests/Screens/UITestSettingsScreen.swift +++ b/LocationServices/LocationServicesUITests/Screens/UITestSettingsScreen.swift @@ -27,11 +27,6 @@ struct UITestSettingsScreen: UITestScreen { return self } - func waittDataProviderRow() -> Self { - let _ = getDataProviderCell() - return self - } - func waitMapStyleRow() -> Self { let _ = getMapStyleCell() return self @@ -58,13 +53,6 @@ struct UITestSettingsScreen: UITestScreen { return UITestSettingsMapStyleScreen(app: app) } - func tapDataProviderRow() -> UITestSettingsDataProviderScreen { - let cell = getDataProviderCell() - cell.tap() - - return UITestSettingsDataProviderScreen(app: app) - } - func getTabBarScreen() -> UITestTabBarScreen { return UITestTabBarScreen(app: app) } @@ -83,13 +71,7 @@ struct UITestSettingsScreen: UITestScreen { XCTAssertTrue(cell.waitForExistence(timeout: UITestWaitTime.regular.time)) return cell } - - private func getDataProviderCell() -> XCUIElement { - app.activate() - let cell = app.cells[Identifiers.dataProviderCell] - XCTAssertTrue(cell.waitForExistence(timeout: UITestWaitTime.regular.time)) - return cell - } + private func getMapStyleCell() -> XCUIElement { app.activate() diff --git a/LocationServices/LocationServicesUITests/SearchUITests.swift b/LocationServices/LocationServicesUITests/SearchUITests.swift index 9a767320..1da9ed82 100644 --- a/LocationServices/LocationServicesUITests/SearchUITests.swift +++ b/LocationServices/LocationServicesUITests/SearchUITests.swift @@ -25,16 +25,6 @@ final class SearchUITests: LocationServicesUITests { super.tearDown() } -// func testSearchByAddressName() throws { -// let app = startApp() -// let _ = UITestExploreScreen(app: app) -// .tapSearchTextField() -// .waitForSearchRootView() -// .type(text: Constants.addressName) -// .tapKeyboardReturnButton() -// .waitForResultsInTable() -// } - func testSearchByGeocodeLocation() throws { let app = startApp() let _ = UITestExploreScreen(app: app) diff --git a/LocationServices/LocationServicesUITests/SettingsUITests.swift b/LocationServices/LocationServicesUITests/SettingsUITests.swift index f6d5db19..7f6e7f65 100644 --- a/LocationServices/LocationServicesUITests/SettingsUITests.swift +++ b/LocationServices/LocationServicesUITests/SettingsUITests.swift @@ -30,7 +30,6 @@ final class SettingsUITests: LocationServicesUITests { .waitAWSCloudRow() .waitMapStyleRow() .waitRouteOptionsRow() - .waittDataProviderRow() } func testRouteOptions() throws { @@ -75,32 +74,8 @@ final class SettingsUITests: LocationServicesUITests { var exploreScreen = UITestExploreScreen(app: app) .waitForMapToBeRendered() - exploreScreen = testMapStyle(screen: exploreScreen, style: .light) - exploreScreen = testMapStyle(screen: exploreScreen, style: .street) - } - - func testDataSourceChanges() throws { - let app = startApp(allowPermissions: false) - let exploreScreen = UITestExploreScreen(app: app) - .waitForMapToBeRendered() - - var dataSourceScreen = exploreScreen.getTabBarScreen() - .tapSettingsButton() - .tapDataProviderRow() - - XCTAssertTrue(dataSourceScreen.isCellSelected(for: .esri)) - - dataSourceScreen = dataSourceScreen - .select(sourceType: .here) - XCTAssertTrue(dataSourceScreen.isCellSelected(for: .here)) - - let mapStyleScreen = dataSourceScreen - .tapBackButton() - .getTabBarScreen() - .tapExploreButton() - .tapMapStyles() - - XCTAssertTrue(mapStyleScreen.isCellSelected(for: .explore)) + exploreScreen = testMapStyle(screen: exploreScreen, style: .standard) + exploreScreen = testMapStyle(screen: exploreScreen, style: .monochrome) } func testMapStyle(screen: UITestExploreScreen, style: MapStyleImages) -> UITestExploreScreen {