├── .env.sample ├── .gitignore ├── .swiftlint.yml ├── CarLens.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ ├── IDETemplateMacros.plist │ └── xcschemes │ ├── Development.xcscheme │ ├── Release.xcscheme │ └── Staging.xcscheme ├── CarLens.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── CarLens ├── Resources │ ├── Animations │ │ ├── Lottie animations │ │ │ ├── button-ripple.json │ │ │ ├── horizontal-progress-chart.json │ │ │ ├── horizontal-stars.json │ │ │ └── viewfinder_bracket.json │ │ └── Onboarding │ │ │ └── onboarding.mp4 │ ├── Assets.xcassets │ │ ├── App icons │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── IOS1024.png │ │ │ │ ├── IOS120-1.png │ │ │ │ ├── IOS120.png │ │ │ │ ├── IOS152.png │ │ │ │ ├── IOS167.png │ │ │ │ ├── IOS180.png │ │ │ │ ├── IOS20.png │ │ │ │ ├── IOS29.png │ │ │ │ ├── IOS40-1.png │ │ │ │ ├── IOS40-2.png │ │ │ │ ├── IOS40.png │ │ │ │ ├── IOS58-1.png │ │ │ │ ├── IOS58.png │ │ │ │ ├── IOS60.png │ │ │ │ ├── IOS76.png │ │ │ │ ├── IOS80-1.png │ │ │ │ ├── IOS80.png │ │ │ │ └── IOS87.png │ │ │ └── Contents.json │ │ ├── Augmented Reality │ │ │ ├── Contents.json │ │ │ └── ar-pin.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── ar-pin.pdf │ │ ├── Buttons │ │ │ ├── Contents.json │ │ │ ├── button-back-arrow.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── arrow.pdf │ │ │ ├── button-car-list-gray.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── button-car-list-gray.pdf │ │ │ ├── button-car-list-white.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── button-car-list-white.pdf │ │ │ ├── button-close.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── button-close.pdf │ │ │ ├── button-google.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── white-google.pdf │ │ │ └── button-scan-primary.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── prim-no-shadow.pdf │ │ ├── Cars │ │ │ ├── Contents.json │ │ │ ├── Images │ │ │ │ ├── Contents.json │ │ │ │ ├── FordFiesta.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── FordFiesta.png │ │ │ │ ├── FordFiesta_locked.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── FordFiesta_locked.png │ │ │ │ ├── HondaCivic.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── HondaCivic.png │ │ │ │ ├── HondaCivic_locked.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── HondaCivic_locked.png │ │ │ │ ├── NissanQashqai.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── NissanQashqai.png │ │ │ │ ├── NissanQashqai_locked.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── NissanQashqai_locked.png │ │ │ │ ├── ToyotaCorolla.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── ToyotaCorolla.png │ │ │ │ ├── ToyotaCorolla_locked.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── ToyotaCorolla_locked.png │ │ │ │ ├── VolkswagenPassat.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── VolkswagenPassat.png │ │ │ │ └── VolkswagenPassat_locked.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── VolkswagenPassat_locked.png │ │ │ └── Logos │ │ │ │ ├── Contents.json │ │ │ │ ├── Ford.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Ford.png │ │ │ │ ├── Ford_locked.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Ford Inactive.png │ │ │ │ ├── Honda.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Honda.png │ │ │ │ ├── Honda_locked.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Honda Inactive.png │ │ │ │ ├── Nissan.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Nissan.png │ │ │ │ ├── Nissan_locked.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Nissan Inactive.png │ │ │ │ ├── Toyota.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Toyota.png │ │ │ │ ├── Toyota_locked.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── Toyota Inactive.png │ │ │ │ ├── Volkswagen.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── VW.png │ │ │ │ └── Volkswagen_locked.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── VW Inactive.png │ │ ├── Common │ │ │ ├── Contents.json │ │ │ └── camera-image.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── camera-illu.pdf │ │ ├── Contents.json │ │ ├── Onboarding │ │ │ ├── Contents.json │ │ │ ├── button-next-page.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── primary-1.pdf │ │ │ ├── button-scan-with-shadow.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── button-scan@2x.pdf │ │ │ ├── onboarding-image-1.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── onboarding-image-1.pdf │ │ │ ├── onboarding-image-2.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── onboarding-image-2.pdf │ │ │ └── onboarding-image-3.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── onboarding-image-3.pdf │ │ └── Splash screen │ │ │ ├── Contents.json │ │ │ ├── application-logo.imageset │ │ │ ├── Contents.json │ │ │ └── application-logo.pdf │ │ │ ├── powered-by-netguru.imageset │ │ │ ├── Contents.json │ │ │ └── powered-by-netguru.pdf │ │ │ └── splash-screen-background.imageset │ │ │ ├── Contents.json │ │ │ └── splash-screen-background.pdf │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Fonts │ │ ├── BLOKKNeue-Regular.ttf │ │ └── gliscor-gothic.ttf │ ├── Local data │ │ └── cars.json │ ├── Localizations │ │ ├── Localizable.strings │ │ └── Localizable.swift │ └── ML Models │ │ ├── CarDetector.mlmodel │ │ └── CarRecognizer.mlmodel ├── Source Files │ ├── Application │ │ ├── ApplicationConstants.swift │ │ ├── ApplicationDelegate.swift │ │ ├── ApplicationDependencies.swift │ │ ├── ApplicationFactory.swift │ │ ├── ApplicationFlowController.swift │ │ └── ApplicationKeys.swift │ ├── Common │ │ ├── Abstraction │ │ │ ├── AlertPresentable.swift │ │ │ ├── CrashLogger.swift │ │ │ ├── FlowController.swift │ │ │ ├── Loader.swift │ │ │ ├── NavigationBarSetupable.swift │ │ │ ├── ViewProgressable.swift │ │ │ └── ViewSetupable.swift │ │ ├── Base classes │ │ │ ├── FlowAwareViewController.swift │ │ │ ├── KeyboardAwareViewController.swift │ │ │ ├── TableViewCell.swift │ │ │ ├── TypedViewController.swift │ │ │ └── View.swift │ │ ├── Extensions │ │ │ ├── ARAnchorExtension.swift │ │ │ ├── LOTAnimationViewExtension.swift │ │ │ ├── NSAttributedStringExtension.swift │ │ │ ├── NSObjectExtension.swift │ │ │ ├── UIApplicationExtension.swift │ │ │ ├── UICollectionViewExtension.swift │ │ │ ├── UIColorExtension.swift │ │ │ ├── UIDeviceExtension.swift │ │ │ ├── UIFontExtension.swift │ │ │ ├── UIPanGestureRecognizerExtensions.swift │ │ │ ├── UIStackViewExtension.swift │ │ │ ├── UITableViewExtension.swift │ │ │ ├── UIViewControllerExtension.swift │ │ │ └── UIViewExtension+Constraint.swift │ │ ├── Factories │ │ │ ├── NSAttributedStringFactory.swift │ │ │ └── SKNodeFactory.swift │ │ ├── Formatter │ │ │ ├── CRNumberFormatter.swift │ │ │ └── CRTimeFormatter.swift │ │ ├── Models │ │ │ ├── Car.swift │ │ │ ├── CarARConfiguration.swift │ │ │ ├── CarImage.swift │ │ │ ├── CarSpecificationChartConfiguration.swift │ │ │ ├── NodeShift.swift │ │ │ └── RecognitionResult.swift │ │ ├── Services │ │ │ ├── CarClassificationService.swift │ │ │ ├── CarsDataService.swift │ │ │ ├── CarsDatabaseService.swift │ │ │ ├── HockeyAppService.swift │ │ │ ├── InputNormalizationService.swift │ │ │ ├── LocalCarsDataService.swift │ │ │ ├── SearchService.swift │ │ │ ├── ToastDisplayer.swift │ │ │ ├── URLOpenerService.swift │ │ │ └── UserDefaultsService.swift │ │ ├── System Metrics │ │ │ └── SystemMetrics.swift │ │ └── Views │ │ │ ├── CarListCardView.swift │ │ │ ├── CarListNavigationBar.swift │ │ │ ├── CarScene.swift │ │ │ ├── DefaultLoader.swift │ │ │ ├── DetectionViewfinderView.swift │ │ │ ├── FullOvalProgressView.swift │ │ │ ├── HorizontalProgressChartView.swift │ │ │ ├── HorizontalStarsView.swift │ │ │ ├── LabeledCarImageView.swift │ │ │ ├── ModelNameView.swift │ │ │ ├── OvalProgressLayerView.swift │ │ │ ├── PartOvalProgressView.swift │ │ │ ├── SeparatorView.swift │ │ │ └── TextSwitcherView.swift │ └── Modules │ │ ├── Onboarding │ │ ├── OnboardingFlowController.swift │ │ └── Root │ │ │ ├── Animation Player │ │ │ ├── OnboardingAnimationPlayer.swift │ │ │ └── OnboardingTransitionAnimationState.swift │ │ │ ├── Indicator Animation │ │ │ └── OnboardingIndicatorAnimationView.swift │ │ │ ├── OnboardingView.swift │ │ │ ├── OnboardingViewController.swift │ │ │ └── Page View Controller │ │ │ ├── Content │ │ │ ├── OnboardingContentView.swift │ │ │ └── OnboardingContentViewController.swift │ │ │ └── OnboardingPageViewController.swift │ │ └── Recognition │ │ ├── Cars list │ │ ├── CarListCollectionViewCell.swift │ │ ├── CarListFlowLayout.swift │ │ ├── CarListLayoutAttributes.swift │ │ ├── CarsListView.swift │ │ └── CarsListViewController.swift │ │ ├── RecognitionFlowController.swift │ │ └── Root │ │ ├── AugmentedRealityViewController │ │ ├── AugmentedRealityView.swift │ │ └── AugmentedRealityViewController.swift │ │ ├── CameraAccessViewController │ │ ├── CameraAccessView.swift │ │ └── CameraAccessViewController.swift │ │ ├── CarCardViewController │ │ ├── CarCardView.swift │ │ └── CarCardViewController.swift │ │ ├── RecognitionView.swift │ │ └── RecognitionViewController.swift └── Supporting Files │ ├── Bridging-Header.h │ └── Info.plist ├── CarLensTests ├── Helpers │ └── JSON.swift ├── Mocks │ ├── ModelsFactory.swift │ └── UIApplicationMock.swift ├── Supporting Files │ ├── Info.plist │ ├── MockedCar.json │ └── MockedCars.json └── Tests │ ├── Common │ ├── FormattersTests.swift │ └── SystemMetricsTests.swift │ ├── Extensions │ ├── UIFontExtensionTests.swift │ └── UIStackViewExtensionTests.swift │ ├── Models │ ├── CarARConfigurationTests.swift │ ├── CarSpecificationChartConfigurationTests.swift │ ├── CarTests.swift │ └── RecognitionResultTests.swift │ └── Services │ ├── CarDatabaseServiceTests.swift │ ├── CarsDataServiceTests.swift │ ├── InputNormalizationServiceTests.swift │ ├── LocalCarsDataServiceTests.swift │ └── SearchServiceTests.swift ├── CarLensTestsImages ├── FailureDiffs │ └── CarLensUITests.CarsListTestCase │ │ ├── diff_testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png │ │ ├── diff_testHondaCivicScreenLook_initial_view_1242x2208@3x.png │ │ ├── diff_testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png │ │ ├── failed_testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png │ │ ├── failed_testHondaCivicScreenLook_initial_view_1242x2208@3x.png │ │ ├── failed_testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png │ │ ├── reference_testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png │ │ ├── reference_testFordFiestaScreenLook_after_2_swipe_left_1125x2436@3x.png │ │ ├── reference_testHondaCivicScreenLook_initial_view_1125x2436@3x.png │ │ ├── reference_testHondaCivicScreenLook_initial_view_1242x2208@3x.png │ │ ├── reference_testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png │ │ ├── reference_testNissanQashqaiScreenLook_after_3_swipe_left_1125x2436@3x.png │ │ ├── reference_testToyotaCorollaScreenLook_after_1_swipe_left_1125x2436@3x.png │ │ └── reference_testVolkswagenPassatScreenLook_after_4_swipe_left_1125x2436@3x.png └── ReferenceImages_64 │ └── CarLensUITests.CarsListTestCase │ ├── testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png │ ├── testFordFiestaScreenLook_after_1_swipe_left_750x1334@2x.png │ ├── testHondaCivicScreenLook_initial_view_1242x2208@3x.png │ ├── testHondaCivicScreenLook_initial_view_750x1334@2x.png │ ├── testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png │ ├── testNissanQashqaiScreenLook_after_2_swipe_left_750x1334@2x.png │ ├── testVolkswagenPassatScreenLook_after_3_swipe_left_1242x2208@3x.png │ └── testVolkswagenPassatScreenLook_after_3_swipe_left_750x1334@2x.png ├── CarLensUITests ├── Features │ └── CarsListTestCase.swift ├── Info.plist ├── Screen │ ├── CarsList.swift │ ├── Onboarding.swift │ ├── Recognition.swift │ └── Screen.swift └── Supporting Files │ ├── GherkinSyntax.swift │ ├── TestBuilder.swift │ ├── UIImage+RemovingStatusBar.swift │ └── XCUITestCase.swift ├── Cartfile ├── Cartfile.resolved ├── Configurations ├── App-Common.xcconfig ├── App-Development.xcconfig ├── App-Release.xcconfig ├── App-Staging.xcconfig ├── Test-Common.xcconfig └── TestUI-Common.xcconfig ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── Podfile ├── Podfile.lock ├── README.md └── bitrise.yml /.env.sample: -------------------------------------------------------------------------------- 1 | HOCKEYAPP_APP_ID_STAGING = example 2 | HOCKEYAPP_APP_ID_PRODUCTION = example 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # .gitignore 3 | # 4 | # Copyright © 2017 Netguru Sp. z o.o. All rights reserved. 5 | # 6 | 7 | .DS_Store 8 | .Trashes 9 | .localized 10 | 11 | build 12 | .build 13 | xcuserdata 14 | DerivedData 15 | 16 | *.mode1v3 17 | *.mode2v3 18 | *.perspectivev3 19 | *.pbxuser 20 | *.xccheckout 21 | *.xcuserstate 22 | *.xcscmblueprint 23 | *.moved-aside 24 | *.hmap 25 | *.o 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | 30 | timeline.xctimeline 31 | playground.xcworkspace 32 | 33 | .idea 34 | *.iml 35 | 36 | Pods 37 | Carthage 38 | Packages 39 | 40 | fastlane/report.xml 41 | fastlane/Preview.html 42 | fastlane/screenshots 43 | fastlane/test_output 44 | 45 | .env 46 | -------------------------------------------------------------------------------- /CarLens.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CarLens.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CarLens.xcodeproj/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | 7 | // ___FILENAME___ 8 | // ___PACKAGENAME___ 9 | // 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CarLens.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CarLens.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CarLens/Resources/Animations/Onboarding/onboarding.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Animations/Onboarding/onboarding.mp4 -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "IOS40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "IOS60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "IOS58.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "IOS87.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "IOS80.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "IOS120-1.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "IOS120.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "IOS180.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "IOS20.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "IOS40-2.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "IOS29.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "IOS58-1.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "IOS40-1.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "IOS80-1.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "IOS76.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "IOS152.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "IOS167.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "IOS1024.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS1024.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS120-1.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS120.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS152.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS167.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS180.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS20.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS29.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS40-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS40-1.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS40-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS40-2.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS40.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS58-1.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS58.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS60.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS76.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS80-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS80-1.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS80.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/App icons/AppIcon.appiconset/IOS87.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/App icons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Augmented Reality/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Augmented Reality/ar-pin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ar-pin.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Augmented Reality/ar-pin.imageset/ar-pin.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Augmented Reality/ar-pin.imageset/ar-pin.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-back-arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arrow.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-back-arrow.imageset/arrow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Buttons/button-back-arrow.imageset/arrow.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-car-list-gray.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "button-car-list-gray.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-car-list-gray.imageset/button-car-list-gray.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Buttons/button-car-list-gray.imageset/button-car-list-gray.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-car-list-white.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "button-car-list-white.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-car-list-white.imageset/button-car-list-white.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Buttons/button-car-list-white.imageset/button-car-list-white.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "button-close.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-close.imageset/button-close.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Buttons/button-close.imageset/button-close.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-google.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "white-google.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-google.imageset/white-google.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Buttons/button-google.imageset/white-google.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-scan-primary.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "prim-no-shadow.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Buttons/button-scan-primary.imageset/prim-no-shadow.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Buttons/button-scan-primary.imageset/prim-no-shadow.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/FordFiesta.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "FordFiesta.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/FordFiesta.imageset/FordFiesta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/FordFiesta.imageset/FordFiesta.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/FordFiesta_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "FordFiesta_locked.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/FordFiesta_locked.imageset/FordFiesta_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/FordFiesta_locked.imageset/FordFiesta_locked.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/HondaCivic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "HondaCivic.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/HondaCivic.imageset/HondaCivic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/HondaCivic.imageset/HondaCivic.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/HondaCivic_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "HondaCivic_locked.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/HondaCivic_locked.imageset/HondaCivic_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/HondaCivic_locked.imageset/HondaCivic_locked.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/NissanQashqai.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "NissanQashqai.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/NissanQashqai.imageset/NissanQashqai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/NissanQashqai.imageset/NissanQashqai.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/NissanQashqai_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "NissanQashqai_locked.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/NissanQashqai_locked.imageset/NissanQashqai_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/NissanQashqai_locked.imageset/NissanQashqai_locked.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/ToyotaCorolla.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ToyotaCorolla.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/ToyotaCorolla.imageset/ToyotaCorolla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/ToyotaCorolla.imageset/ToyotaCorolla.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/ToyotaCorolla_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ToyotaCorolla_locked.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/ToyotaCorolla_locked.imageset/ToyotaCorolla_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/ToyotaCorolla_locked.imageset/ToyotaCorolla_locked.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/VolkswagenPassat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "VolkswagenPassat.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/VolkswagenPassat.imageset/VolkswagenPassat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/VolkswagenPassat.imageset/VolkswagenPassat.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/VolkswagenPassat_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "VolkswagenPassat_locked.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Images/VolkswagenPassat_locked.imageset/VolkswagenPassat_locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Images/VolkswagenPassat_locked.imageset/VolkswagenPassat_locked.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Ford.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Ford.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Ford.imageset/Ford.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Ford.imageset/Ford.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Ford_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Ford Inactive.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Ford_locked.imageset/Ford Inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Ford_locked.imageset/Ford Inactive.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Honda.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Honda.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Honda.imageset/Honda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Honda.imageset/Honda.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Honda_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Honda Inactive.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Honda_locked.imageset/Honda Inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Honda_locked.imageset/Honda Inactive.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Nissan.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Nissan.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Nissan.imageset/Nissan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Nissan.imageset/Nissan.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Nissan_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Nissan Inactive.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Nissan_locked.imageset/Nissan Inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Nissan_locked.imageset/Nissan Inactive.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Toyota.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Toyota.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Toyota.imageset/Toyota.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Toyota.imageset/Toyota.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Toyota_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Toyota Inactive.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Toyota_locked.imageset/Toyota Inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Toyota_locked.imageset/Toyota Inactive.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Volkswagen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "VW.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Volkswagen.imageset/VW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Volkswagen.imageset/VW.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Volkswagen_locked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "VW Inactive.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Cars/Logos/Volkswagen_locked.imageset/VW Inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Cars/Logos/Volkswagen_locked.imageset/VW Inactive.png -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Common/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Common/camera-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "camera-illu.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Common/camera-image.imageset/camera-illu.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Common/camera-image.imageset/camera-illu.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/button-next-page.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "primary-1.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/button-next-page.imageset/primary-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Onboarding/button-next-page.imageset/primary-1.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/button-scan-with-shadow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "button-scan@2x.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/button-scan-with-shadow.imageset/button-scan@2x.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Onboarding/button-scan-with-shadow.imageset/button-scan@2x.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/onboarding-image-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "onboarding-image-1.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/onboarding-image-1.imageset/onboarding-image-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Onboarding/onboarding-image-1.imageset/onboarding-image-1.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/onboarding-image-2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "onboarding-image-2.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/onboarding-image-2.imageset/onboarding-image-2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Onboarding/onboarding-image-2.imageset/onboarding-image-2.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/onboarding-image-3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "onboarding-image-3.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Onboarding/onboarding-image-3.imageset/onboarding-image-3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Onboarding/onboarding-image-3.imageset/onboarding-image-3.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Splash screen/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Splash screen/application-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "application-logo.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Splash screen/application-logo.imageset/application-logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Splash screen/application-logo.imageset/application-logo.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Splash screen/powered-by-netguru.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "powered-by-netguru.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Splash screen/powered-by-netguru.imageset/powered-by-netguru.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Splash screen/powered-by-netguru.imageset/powered-by-netguru.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Splash screen/splash-screen-background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "splash-screen-background.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CarLens/Resources/Assets.xcassets/Splash screen/splash-screen-background.imageset/splash-screen-background.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Assets.xcassets/Splash screen/splash-screen-background.imageset/splash-screen-background.pdf -------------------------------------------------------------------------------- /CarLens/Resources/Fonts/BLOKKNeue-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Fonts/BLOKKNeue-Regular.ttf -------------------------------------------------------------------------------- /CarLens/Resources/Fonts/gliscor-gothic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/Fonts/gliscor-gothic.ttf -------------------------------------------------------------------------------- /CarLens/Resources/Local data/cars.json: -------------------------------------------------------------------------------- 1 | { 2 | "cars": [ 3 | { 4 | "id": "honda_civic", 5 | "brand": "Honda", 6 | "model": "Civic", 7 | "description": "Originally a subcompact, the Civic has gone through several generational changes, becoming both larger and more upmarket.", 8 | "stars": 2, 9 | "acceleration_mph": 10.0, 10 | "acceleration_kph": 10.4, 11 | "speed_mph": 135, 12 | "speed_kph": 217, 13 | "power": 120, 14 | "engine": 1000, 15 | "brand_logo_image": "Honda", 16 | "brand_logo_image_locked": "Honda_locked", 17 | "image": "HondaCivic", 18 | "image_locked": "HondaCivic_locked" 19 | }, 20 | { 21 | "id": "ford_fiesta", 22 | "brand": "Ford", 23 | "model": "Fiesta", 24 | "description": "Ford Fiesta has been marketed by Ford since 1976. Ford has sold over 16 million Fiestas since then, making it a best-seller.", 25 | "stars": 1, 26 | "acceleration_mph": 12.7, 27 | "acceleration_kph": 13.2, 28 | "speed_mph": 118, 29 | "speed_kph": 190, 30 | "power": 70, 31 | "engine": 1000, 32 | "brand_logo_image": "Ford", 33 | "brand_logo_image_locked": "Ford_locked", 34 | "image": "FordFiesta", 35 | "image_locked": "FordFiesta_locked" 36 | }, 37 | { 38 | "id": "nissan_qashqai", 39 | "brand": "Nissan", 40 | "model": "Qashqai", 41 | "description": "Qashqai is a compact crossover SUV produced by Nissan since 2006. Qashqai used to be called Nissan Dualis in Japan and Australia.", 42 | "stars": 3, 43 | "acceleration_mph": 9.0, 44 | "acceleration_kph": 9.3, 45 | "speed_mph": 140, 46 | "speed_kph": 225, 47 | "power": 163, 48 | "engine": 1600, 49 | "brand_logo_image": "Nissan", 50 | "brand_logo_image_locked": "Nissan_locked", 51 | "image": "NissanQashqai", 52 | "image_locked": "NissanQashqai_locked" 53 | }, 54 | { 55 | "id": "volkswagen_passat", 56 | "brand": "Volkswagen", 57 | "model": "Passat", 58 | "description": "Volkswagen Passat is a large family car manufactured and marketed by Volkswagen since 1973, now in its eighth generation. ", 59 | "stars": 4, 60 | "acceleration_mph": 8.0, 61 | "acceleration_kph": 8.3, 62 | "speed_mph": 148, 63 | "speed_kph": 238, 64 | "power": 280, 65 | "engine": 2000, 66 | "brand_logo_image": "Volkswagen", 67 | "brand_logo_image_locked": "Volkswagen_locked", 68 | "image": "VolkswagenPassat", 69 | "image_locked": "VolkswagenPassat_locked" 70 | } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /CarLens/Resources/Localizations/Localizable.strings: -------------------------------------------------------------------------------- 1 | // Common 2 | "lorem.ipsum" = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore"; 3 | "ok" = "OK"; 4 | "cancel" = "Cancel"; 5 | "confirm" = "Confirm"; 6 | "next" = "Next"; 7 | "back" = "Back"; 8 | "done" = "Done"; 9 | "close" = "Close"; 10 | "yes" = "Yes"; 11 | "no" = "No"; 12 | 13 | // Onboarding 14 | "onboarding.first.title" = "Recognize Cars"; 15 | "onboarding.first.description" = "Point the camera at the front\nof the car for the best results\nin image recognition."; 16 | "onboarding.second.title" = "Discover Cars"; 17 | "onboarding.second.description" = "Search and unlock the models\navaliable in the gallery.\nCatch them all."; 18 | "onboarding.third.title" = "Stay updated"; 19 | "onboarding.third.description" = "Expect updates with more cars\nand improved recognition\nalgorithm."; 20 | 21 | // Recognition 22 | "recognition.point.camera.at.car" = "Point the camera at a car"; 23 | "recognition.other.car" = "Car has been detected but does not belong to our data base"; 24 | "recognition.recognizing" = "Recognizing..."; 25 | 26 | // Car card 27 | "car.card.accelerate.0.to.60.mph" = "0 to 60 mph"; 28 | "car.card.accelerate.0.to.100.kph" = "0 to 100 km/h"; 29 | "car.card.top.speed" = "Top speed"; 30 | "car.card.power" = "Power"; 31 | "car.card.engine" = "Engine"; 32 | "car.card.mph" = "mph"; 33 | "car.card.kph" = "km/h"; 34 | "car.card.hp" = "hp"; 35 | "car.card.engine.capacity" = "cc"; 36 | "car.card.attach.pin.error" = "Could not atttach pin, please try again"; 37 | 38 | // Cars list 39 | "cars.list.title" = "Discovered"; 40 | 41 | // Camera Access 42 | "camera-access.information" = "Recognize cars and unlock\nthem in the Gallery"; 43 | "camera-access.button.text" = "Allow Camera Access"; 44 | 45 | // Error 46 | "error.title" = "Error"; 47 | "error.unknown.error.occurred" = "Unknown error occurred"; 48 | -------------------------------------------------------------------------------- /CarLens/Resources/Localizations/Localizable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Localizable.swift 3 | // CarLens 4 | // 5 | 6 | enum Localizable { 7 | 8 | enum Common { 9 | static let loremIpsum = localized("lorem.ipsum") 10 | static let ok = localized("ok") 11 | static let cancel = localized("cancel") 12 | static let confirm = localized("confirm") 13 | static let next = localized("next") 14 | static let back = localized("back") 15 | static let done = localized("done") 16 | static let close = localized("close") 17 | static let yes = localized("yes") 18 | static let no = localized("no") 19 | } 20 | 21 | enum Onboarding { 22 | enum Title { 23 | static let first = localized("onboarding.first.title") 24 | static let second = localized("onboarding.second.title") 25 | static let third = localized("onboarding.third.title") 26 | } 27 | enum Description { 28 | static let first = localized("onboarding.first.description") 29 | static let second = localized("onboarding.second.description") 30 | static let third = localized("onboarding.third.description") 31 | } 32 | } 33 | 34 | enum Recognition { 35 | static let pointCameraAtCar = localized("recognition.point.camera.at.car") 36 | static let otherCar = localized("recognition.other.car") 37 | static let recognizing = localized("recognition.recognizing") 38 | } 39 | 40 | enum CarCard { 41 | static let accelerate0to60mph = localized("car.card.accelerate.0.to.60.mph") 42 | static let accelerate0to100kph = localized("car.card.accelerate.0.to.100.kph") 43 | static let topSpeed = localized("car.card.top.speed") 44 | static let power = localized("car.card.power") 45 | static let engine = localized("car.card.engine") 46 | static let mph = localized("car.card.mph") 47 | static let kph = localized("car.card.kph") 48 | static let hp = localized("car.card.hp") 49 | static let cc = localized("car.card.engine.capacity") 50 | static let attachPinError = localized("car.card.attach.pin.error") 51 | } 52 | 53 | enum CarsList { 54 | static let title = localized("cars.list.title") 55 | } 56 | 57 | enum CameraAccess { 58 | static let information = localized("camera-access.information") 59 | static let accessButton = localized("camera-access.button.text") 60 | } 61 | 62 | enum Error { 63 | static let title = localized("error.title") 64 | static let unknownErrorOccurred = localized("error.unknown.error.occurred") 65 | } 66 | } 67 | 68 | private func localized(_ value: String) -> String { 69 | return NSLocalizedString(value, comment: "") 70 | } 71 | -------------------------------------------------------------------------------- /CarLens/Resources/ML Models/CarDetector.mlmodel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/ML Models/CarDetector.mlmodel -------------------------------------------------------------------------------- /CarLens/Resources/ML Models/CarRecognizer.mlmodel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLens/Resources/ML Models/CarRecognizer.mlmodel -------------------------------------------------------------------------------- /CarLens/Source Files/Application/ApplicationConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationConstants.swift 3 | // CarLens 4 | // 5 | 6 | /// Keeping the constant values used in the project. 7 | enum Constants { 8 | 9 | /// Constants used for onboarding animation. 10 | enum OnboardingAnimation { 11 | 12 | /// Video frames from which the animation should start. 13 | enum StartFrames { 14 | static let secondScreen: Int64 = 184 15 | static let thirdScreen: Int64 = 334 16 | } 17 | 18 | /// Video frames at which the animation should end. 19 | enum FinishFrames { 20 | static let firstScreen = StartFrames.secondScreen 21 | static let secondScreen = StartFrames.thirdScreen 22 | static let thirdScreen: Int64 = 452 23 | } 24 | 25 | static let initialAnimationDelayInMilliseconds = 400 26 | } 27 | 28 | /// Constants used in recognition process. 29 | enum Recognition { 30 | 31 | ///Thresholds for the recognition confidence values. 32 | enum Threshold { 33 | 34 | /// Minimum threshold filtering the results at the beginning. 35 | static let minimum: Float = 0.1 36 | 37 | /// Threshold stating whether the recognition of the car is in progress. 38 | static let neededToShowProgress: Float = 0.4 39 | 40 | /// Confidence needed to pin the AR label. 41 | static let neededToPinARLabel: Float = 0.75 42 | } 43 | 44 | /// Number of last results that should be normalized. 45 | static let normalizationCount = 5 46 | } 47 | 48 | /// ML class labels. 49 | enum Labels { 50 | 51 | /// Labels used in detection process. 52 | enum Detection { 53 | 54 | static let notCar = "0" 55 | 56 | static let car = "1" 57 | } 58 | 59 | /// Labels used in recognition process. 60 | enum Recognition { 61 | 62 | static let otherCar = "other_car" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CarLens/Source Files/Application/ApplicationDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationDelegate.swift 3 | // CarLens 4 | // 5 | 6 | import UIKit 7 | 8 | @UIApplicationMain 9 | final class ApplicationDelegate: UIResponder, UIApplicationDelegate { 10 | 11 | /// Shared dependencies used extensively in the application 12 | private let applicationDependencies = ApplicationDependencies() 13 | 14 | /// Main Flow controller of the app 15 | private lazy var flowController = ApplicationFlowController(window: window, dependencies: applicationDependencies) 16 | 17 | /// - SeeAlso: UIApplicationDelegate.window 18 | lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds) 19 | 20 | func application(_ application: UIApplication, 21 | willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 22 | applicationDependencies.crashLogger.start() 23 | return true 24 | } 25 | 26 | func application(_ application: UIApplication, 27 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 28 | flowController.startApp() 29 | return true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CarLens/Source Files/Application/ApplicationDependencies.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationDependencies.swift 3 | // CarLens 4 | // 5 | 6 | /// Shared dependencies used extensively in the application 7 | class ApplicationDependencies { 8 | 9 | lazy var applicationKeys: ApplicationKeys = ApplicationKeys(keys: CarLensKeys()) 10 | 11 | lazy var crashLogger: CrashLogger = HockeyAppService(keys: applicationKeys) 12 | 13 | lazy var carsDataService = CarsDataService() 14 | } 15 | -------------------------------------------------------------------------------- /CarLens/Source Files/Application/ApplicationFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationFactory.swift 3 | // CarLens 4 | // 5 | 6 | 7 | /// Factory class that creates view controllers with proper dependencies 8 | final class ApplicationFactory { 9 | 10 | private let applicationDependencies: ApplicationDependencies 11 | 12 | /// Initializes the factory with given dependencies 13 | /// 14 | /// - Parameter applicationDependencies: Dependencies used for creating controllers 15 | init(applicationDependencies: ApplicationDependencies) { 16 | self.applicationDependencies = applicationDependencies 17 | } 18 | 19 | /// Creates onboarding view controller 20 | /// 21 | /// - Returns: Created controller 22 | func onboardingViewController() -> OnboardingViewController { 23 | return OnboardingViewController(viewMaker: OnboardingView()) 24 | } 25 | 26 | /// Creates controller with live camera AR view and bottom classification preview 27 | /// 28 | /// - Returns: Created controller 29 | func recognitionViewController() -> RecognitionViewController { 30 | return RecognitionViewController( 31 | augmentedRealityViewController: augmentedRealityViewController(), 32 | classificationService: CarClassificationService(carsDataService: applicationDependencies.carsDataService), 33 | carsDataService: applicationDependencies.carsDataService 34 | ) 35 | } 36 | 37 | /// Creates controller with live augmented readlity camera preview 38 | /// 39 | /// - Returns: Created controller 40 | func augmentedRealityViewController() -> AugmentedRealityViewController { 41 | return AugmentedRealityViewController(viewMaker: AugmentedRealityView()) 42 | } 43 | 44 | /// Creates controller with list of available cars to discover 45 | /// 46 | /// - Parameter discoveredCar: Optional which indicates wheter the view is initialized with scanned car or not 47 | /// - Returns: Created controller 48 | func carsListViewController(with discoveredCar: Car? = nil) -> CarsListViewController { 49 | return CarsListViewController(discoveredCar: discoveredCar, 50 | carsDataService: applicationDependencies.carsDataService) 51 | } 52 | 53 | /// Creates controller with camera access handling 54 | /// 55 | /// - Returns: Created controller 56 | func cameraAccessViewController() -> CameraAccessViewController { 57 | return CameraAccessViewController(viewMaker: CameraAccessView()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /CarLens/Source Files/Application/ApplicationFlowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationFlowController.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | /// Main Flow controller of the app, has access to the main window and can change root controller 10 | final class ApplicationFlowController { 11 | 12 | private weak var window: UIWindow? 13 | 14 | private var rootFlowController: FlowController? 15 | 16 | private let dependencies: ApplicationDependencies 17 | 18 | private let applicationFactory: ApplicationFactory 19 | 20 | private let userDefaultsService: UserDefaultsService 21 | /// Initializes Main Flow Controllers 22 | /// 23 | /// - Parameters: 24 | /// - window: Main window of the app 25 | /// - dependencies: Deppendencies to use in the app 26 | init(window: UIWindow?, 27 | dependencies: ApplicationDependencies, 28 | userDefaultsService: UserDefaultsService = UserDefaultsService.shared) { 29 | guard let window = window else { 30 | fatalError("Window given to the App Flow Controller was nil") 31 | } 32 | self.window = window 33 | self.dependencies = dependencies 34 | applicationFactory = ApplicationFactory(applicationDependencies: dependencies) 35 | self.userDefaultsService = userDefaultsService 36 | } 37 | 38 | /// Call after application was loaded, it will set proper view controller as window root 39 | func startApp() { 40 | window?.backgroundColor = .white 41 | window?.makeKeyAndVisible() 42 | if userDefaultsService.shouldShowOnboarding { 43 | changeRootFlowController(to: makeOnboardingFlowController()) 44 | } else { 45 | changeRootFlowController(to: makeRecognitionFlowController()) 46 | } 47 | } 48 | 49 | private func makeOnboardingFlowController() -> FlowController { 50 | return OnboardingFlowController(dependencies: dependencies, applicationFactory: applicationFactory) 51 | } 52 | 53 | private func makeRecognitionFlowController() -> FlowController { 54 | return RecognitionFlowController(dependencies: dependencies, applicationFactory: applicationFactory) 55 | } 56 | 57 | private func changeRootFlowController(to flowController: FlowController) { 58 | rootFlowController = flowController 59 | window?.rootViewController = flowController.rootViewController 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CarLens/Source Files/Application/ApplicationKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationKeys.swift 3 | // CarLens 4 | // 5 | 6 | 7 | /// Common interface for securely providing keys 8 | struct ApplicationKeys { 9 | 10 | /// The cocoapods-keys instance 11 | let keys: CarLensKeys 12 | 13 | /// The Hockey App app identifier 14 | var hockeyAppIdentifier: String { 15 | #if ENV_PRODUCTION 16 | return keys.hOCKEYAPP_APP_ID_PRODUCTION 17 | #elseif ENV_STAGING || ENV_DEVELOPMENT 18 | return keys.hOCKEYAPP_APP_ID_STAGING 19 | #endif 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Abstraction/AlertPresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlertPresentable.swift 3 | // CarLens 4 | // 5 | 6 | import UIKit.UIAlertController 7 | 8 | /// Protocol adding convenience methods for showing alerts inside view controller 9 | protocol AlertPresentable { } 10 | 11 | extension AlertPresentable where Self: UIViewController { 12 | 13 | /// Shows default Alert with single OK button 14 | /// 15 | /// - Parameters: 16 | /// - message: Message to display on the alert 17 | /// - title: Title to display on the alert, nil means no title to display 18 | /// - onTap: Callback invoked after OK button was tapped 19 | func showAlert(withMessage message: String, title: String? = nil, onTap: ((UIAlertAction) -> Void)? = nil) { 20 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 21 | let OKAction = UIAlertAction(title: Localizable.Common.ok, style: .default, handler: onTap) 22 | alertController.addAction(OKAction) 23 | present(alertController, animated: true, completion: nil) 24 | } 25 | 26 | /// Shows confirmation alert with destructive styled confirmation button 27 | /// 28 | /// - Parameters: 29 | /// - title: Title of the alert 30 | /// - message: Message to display on the alert 31 | /// - confirmButtonText: Text of the confirmation button 32 | /// - onConfirmTap: Callback called after tapping confirmation button 33 | func showDestructiveConfirmationAlert(withTitle title: String, 34 | message: String, 35 | confirmButtonText: String, 36 | onConfirmTap: @escaping (UIAlertAction) -> Void) { 37 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 38 | let confirmAction = UIAlertAction(title: confirmButtonText, style: .destructive, handler: onConfirmTap) 39 | alertController.addAction(UIAlertAction(title: Localizable.Common.cancel, style: .cancel)) 40 | alertController.addAction(confirmAction) 41 | present(alertController, animated: true, completion: nil) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Abstraction/CrashLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrashLogger.swift 3 | // CarLens 4 | // 5 | 6 | 7 | /// Interface for the crash logger 8 | protocol CrashLogger { 9 | 10 | /// Starts crash logger service integration 11 | func start() 12 | } 13 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Abstraction/FlowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlowController.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.UIViewController 8 | 9 | /// Interface for the flow controller 10 | protocol FlowController { 11 | 12 | /// Root view controller of the given flow 13 | var rootViewController: UIViewController { get } 14 | } 15 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Abstraction/Loader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Loader.swift 3 | // CarLens 4 | // 5 | 6 | /// Interface for displaying loader covering the view 7 | protocol Loader { 8 | 9 | /// Indicating if loader is currently spinning 10 | var isSpinning: Bool { get } 11 | 12 | /// Changes state of the loader 13 | /// 14 | /// - Parameters: 15 | /// - show: Indicating if loader shold be shown or hidden 16 | func toggle(_ show: Bool) 17 | } 18 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Abstraction/NavigationBarSetupable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationBarSetupable.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.UINavigationBar 8 | 9 | /// Interface for setting up the navigation bar 10 | protocol NavigationBarSetupable { 11 | 12 | /// Called to setup the navigation bar 13 | /// 14 | /// - Parameter navigationBar: Navigation bar to be customized 15 | func setup(navigationBar: UINavigationBar) 16 | } 17 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Abstraction/ViewProgressable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewProgressable.swift 3 | // CarLens 4 | // 5 | 6 | /// Interface for progressing the view 7 | protocol ViewProgressable { 8 | 9 | /// Invalidates the progress shown on the chart 10 | /// 11 | /// - Parameter animated: Indicating if invalidation should be animated 12 | func invalidateChart(animated: Bool) 13 | } 14 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Abstraction/ViewSetupable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewSetupable.swift 3 | // CarLens 4 | // 5 | 6 | /// Interface for setting up the view 7 | protocol ViewSetupable { 8 | 9 | /// Add subviews to the view when called 10 | func setupViewHierarchy() 11 | 12 | /// Add constraints to the view when called 13 | func setupConstraints() 14 | 15 | /// Setup required properties when called 16 | func setupProperties() 17 | } 18 | 19 | extension ViewSetupable { 20 | 21 | // Empty default implementation - not every class need this method 22 | func setupProperties() { } 23 | 24 | /// Calls all other setup methods in proper order 25 | func setupView() { 26 | setupViewHierarchy() 27 | setupConstraints() 28 | setupProperties() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Base classes/FlowAwareViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlowAwareViewController.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | /// Simple View controller class with helper callbacks for flow management 10 | class FlowAwareViewController: KeyboardAwareViewController { 11 | 12 | /// Closure triggered when view controller will be popped from navigation stack 13 | var willMoveToParentViewController: ((UIViewController?) -> Void)? 14 | 15 | /// Closure triggered when view controller has been popped from navigation stack 16 | var onMovingFromParentViewController: (() -> Void)? 17 | 18 | /// Closure triggered when view controller has been pushed into navigation stack 19 | var onMovingToParentViewController: (() -> Void)? 20 | 21 | /// - SeeAlso: UIViewController.viewWillAppear() 22 | override func viewWillAppear(_ animated: Bool) { 23 | super.viewWillAppear(animated) 24 | if let navigationBarCustomizable = self as? NavigationBarSetupable, 25 | let navigationController = navigationController { 26 | navigationBarCustomizable.setup(navigationBar: navigationController.navigationBar) 27 | } 28 | if isMovingToParent { 29 | onMovingToParentViewController?() 30 | } 31 | } 32 | 33 | /// - SeeAlso: UIViewController.viewWillDisappear() 34 | override func viewWillDisappear(_ animated: Bool) { 35 | super.viewWillDisappear(animated) 36 | if isMovingFromParent { 37 | onMovingFromParentViewController?() 38 | } 39 | } 40 | 41 | /// - SeeAlso: UIViewController.willMove() 42 | override func willMove(toParent parent: UIViewController?) { 43 | super.willMove(toParent: parent) 44 | willMoveToParentViewController?(parent) 45 | 46 | // Fix for the glitch when NavigationBar not changed it's style to the end of the pop animation 47 | guard 48 | let navigationController = navigationController, 49 | let controllerIndex = navigationController.viewControllers.index(of: self), 50 | controllerIndex > 0, 51 | let previousController = navigationController.viewControllers[controllerIndex-1] as? NavigationBarSetupable 52 | else { return } 53 | previousController.setup(navigationBar: navigationController.navigationBar) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Base classes/KeyboardAwareViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardAwareViewController.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | /// Simple View controller class with helper methods for keyboard management 10 | class KeyboardAwareViewController: UIViewController { 11 | 12 | /// Closure triggered when keyboard will show up 13 | var keyboardWillShow: (_ height: CGFloat, _ animationDuration: TimeInterval) -> Void = { _, _ in } 14 | 15 | /// Closure triggered when keyboard will hide 16 | var keyboardWillHide: (_ animationDuration: TimeInterval) -> Void = { _ in } 17 | 18 | /// - SeeAlso: UIViewController.viewDidLoad() 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | NotificationCenter.default.addObserver(self, 22 | selector: #selector(keyboardWillShowSelector), 23 | name: UIResponder.keyboardWillShowNotification, 24 | object: nil) 25 | NotificationCenter.default.addObserver(self, 26 | selector: #selector(keyboardWillHideSelector), 27 | name: UIResponder.keyboardWillHideNotification, 28 | object: nil) 29 | } 30 | 31 | @objc private func keyboardWillShowSelector(notification: NSNotification) { 32 | guard let userInfo = notification.userInfo else { return } 33 | let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue) 34 | let keyboardAnimation: TimeInterval? = 35 | (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue 36 | guard let keyboardRect = keyboardFrame?.cgRectValue, 37 | let keyboardAnimationDuration = keyboardAnimation else { return } 38 | let convertedKeyboardRect = view.convert(keyboardRect, from: nil) 39 | let height = convertedKeyboardRect.size.height 40 | keyboardWillShow(height, keyboardAnimationDuration) 41 | } 42 | 43 | @objc private func keyboardWillHideSelector(notification: NSNotification) { 44 | guard let userInfo = notification.userInfo else { return } 45 | let keyboardAnimation: TimeInterval? = 46 | (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue 47 | guard let keyboardAnimationDuration = keyboardAnimation else { return } 48 | keyboardWillHide(keyboardAnimationDuration) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Base classes/TableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewCell.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | /// Base class reducing boilerplate inside UITableViewCell subclasses 10 | class TableViewCell: UITableViewCell { 11 | 12 | /// - SeeAlso: UITableViewCell 13 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 14 | super.init(style: style, reuseIdentifier: reuseIdentifier) 15 | (self as? ViewSetupable)?.setupView() 16 | } 17 | 18 | /// - SeeAlso: UITableViewCell 19 | required init?(coder aDecoder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Base classes/TypedViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TypedViewController.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | /// Base class for view controllers with programatically created `View` 10 | class TypedViewController: FlowAwareViewController { 11 | 12 | /// Custom View 13 | let customView: View 14 | 15 | /// Initializes view controller with given View 16 | /// 17 | /// - Parameter viewMaker: Maker for the UIView 18 | init(viewMaker: @escaping @autoclosure () -> View) { 19 | self.customView = viewMaker() 20 | super.init(nibName: nil, bundle: nil) 21 | } 22 | 23 | @available(*, unavailable) 24 | required init?(coder aDecoder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | /// - SeeAlso: UIViewController.loadView() 29 | override func loadView() { 30 | view = customView 31 | view.clipsToBounds = true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Base classes/View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | /// Base class for UIView sublclasses to remove boilerplate from custom views 10 | class View: UIView { 11 | 12 | /// Indicating if keyboard should be closed on touch 13 | var closeKeyboardOnTouch = true 14 | 15 | /// - SeeAlso: NSCoding.init?(coder:) 16 | @available(*, unavailable) required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | /// Initialize an instance and calls required methods 21 | init() { 22 | super.init(frame: .zero) 23 | guard let setupableView = self as? ViewSetupable else { return } 24 | setupableView.setupView() 25 | } 26 | 27 | /// - SeeAlso: UIView.touchesBegan() 28 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 29 | super.touchesBegan(touches, with: event) 30 | if closeKeyboardOnTouch { 31 | endEditing(true) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/ARAnchorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARAnchorExtension.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import ARKit 8 | 9 | extension ARAnchor { 10 | 11 | /// Initializes Augemnted Reality anchor from given parameters 12 | /// 13 | /// - Parameters: 14 | /// - hitTest: Result of the hit test 15 | /// - camera: Camera used for the hit test 16 | /// - nodeShift: Shift to be applied to the anchor 17 | convenience init(from hitTest: ARHitTestResult, camera: ARCamera, nodeShift: NodeShift) { 18 | var translation = matrix_identity_float4x4 19 | translation.columns.3.z = Float(-hitTest.distance) - nodeShift.depth 20 | translation.columns.3.x = -nodeShift.elevation 21 | let transform = simd_mul(camera.transform, translation) 22 | self.init(transform: transform) 23 | } 24 | 25 | /// Calculates distance between current anchor and given one 26 | /// 27 | /// - Parameter anchor: Anchor to calculate distance to 28 | /// - Returns: Calculated distance in meters 29 | func distance(from anchor: ARAnchor) -> Float { 30 | return simd_distance(transform.columns.3, anchor.transform.columns.3) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/LOTAnimationViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LOTAnimationViewExtension.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import Lottie 8 | 9 | extension LOTAnimationView { 10 | 11 | /// Sets the progress on the animation 12 | /// 13 | /// - Parameters: 14 | /// - progress: Progress to be set 15 | /// - animated: Indicating if change should be animated 16 | func set(progress: CGFloat, animated: Bool) { 17 | if animated { 18 | play(toProgress: progress) 19 | } else { 20 | animationProgress = progress 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/NSObjectExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSObjectExtension.swift 3 | // CarLens 4 | // 5 | 6 | 7 | extension NSObject { 8 | 9 | /// Name of the class 10 | class var className: String { 11 | let namespaceClassName = NSStringFromClass(self) 12 | return namespaceClassName.components(separatedBy: ".").last! 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/UIApplicationExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplicationExtension.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import Foundation 8 | import UIKit 9 | 10 | extension UIApplication: URLOpener {} 11 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/UICollectionViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewExtension.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.UICollectionView 8 | 9 | extension UICollectionView { 10 | 11 | /// - SeeAlso: UICollectionView.register 12 | func register(cell: T.Type) { 13 | register(T.self, forCellWithReuseIdentifier: T.className) 14 | } 15 | 16 | /// - SeeAlso: UICollectionView.dequeueReusableCell 17 | func dequeueReusableCell(for indexPath: IndexPath) -> T? { 18 | return dequeueReusableCell(withReuseIdentifier: T.className, for: indexPath) as? T 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/UIColorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColorExtension.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.UIColor 8 | 9 | extension UIColor { 10 | 11 | /// Convenience intitializer for hex format 12 | /// 13 | /// - Parameter hex: color in hex format (for example 0x121212) 14 | convenience init(hex: Int) { 15 | let red = CGFloat((hex >> 16) & 0xff) / 255 16 | let green = CGFloat((hex >> 08) & 0xff) / 255 17 | let blue = CGFloat((hex >> 00) & 0xff) / 255 18 | self.init(red: red, green: green, blue: blue, alpha: 1) 19 | } 20 | 21 | class var crBackgroundGray: UIColor { 22 | return .init(hex: 0xE9EEF0) 23 | } 24 | 25 | class var crBackgroundLightGray: UIColor { 26 | return .init(hex: 0xEEF3F5) 27 | } 28 | 29 | class var crFontDark: UIColor { 30 | return .init(hex: 0x1A1A1A) 31 | } 32 | 33 | class var crFontGray: UIColor { 34 | return .init(hex: 0x7994A9) 35 | } 36 | 37 | class var crFontLightGray: UIColor { 38 | return .init(hex: 0x8199AC) 39 | } 40 | 41 | class var crFontGrayLocked: UIColor { 42 | return .init(hex: 0xD7E2E6) 43 | } 44 | 45 | class var crShadowOrange: UIColor { 46 | return .init(hex: 0xFF5969) 47 | } 48 | 49 | class var crProgressDarkGray: UIColor { 50 | return .init(hex: 0xC4D0D6) 51 | } 52 | 53 | class var crDeepOrange: UIColor { 54 | return .init(hex: 0xFF8D3D) 55 | } 56 | 57 | class var crOnboardingDeepOrange: UIColor { 58 | return .init(hex: 0xFF8742) 59 | } 60 | 61 | class var crOnboardingLightOrange: UIColor { 62 | return .init(hex: 0xFFD2B8) 63 | } 64 | 65 | class var crOnboardingFontDarkGray: UIColor { 66 | return .init(hex: 0x2D2D2E) 67 | } 68 | 69 | class var crOnboardingFontLightGray: UIColor { 70 | return .init(hex: 0x7C93A6) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/UIDeviceExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDeviceExtension.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.UIDevice 8 | 9 | extension UIDevice { 10 | 11 | /// Indicates if screen size of the device is bigger than 4 inches 12 | static var screenSizeBiggerThan4Inches: Bool { 13 | return UIScreen.main.bounds.height > 568 14 | } 15 | 16 | /// Indicates if screen size of the device is bigger than 4.7 inches 17 | static var screenSizeBiggerThan4Point7Inches: Bool { 18 | return UIScreen.main.bounds.height > 667 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/UIFontExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFontExtension.swift 3 | // CarLens 4 | // 5 | 6 | import UIKit.UIFont 7 | 8 | extension UIFont { 9 | 10 | /// Returns the `Gliscor Gothic` font object in the specified size 11 | /// 12 | /// - Parameters: 13 | /// - size: The size (in points) to which the font is scaled. This value must be greater than 0.0 14 | /// - Returns: Font object of the specified size 15 | static func gliscorGothicFont(ofSize size: CGFloat) -> UIFont { 16 | return UIFont(name: "GliscorGothic", size: size)! 17 | } 18 | 19 | /// Returns the `BLOKK Neue` font object in the specified size 20 | /// 21 | /// - Parameters: 22 | /// - size: The size (in points) to which the font is scaled. This value must be greater than 0.0 23 | /// - Returns: Font object of the specified size 24 | static func blokkNeueFont(ofSize size: CGFloat) -> UIFont { 25 | return UIFont(name: "BLOKKNeue-Regular", size: size)! 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/UIPanGestureRecognizerExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIPanGestureRecognizerExtensions.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | /// Enum describing possible Pan Gesture directions 10 | /// 11 | /// - undefined: undefined state 12 | /// - bottomToTop: pan gesture from bottom to top 13 | /// - topToBottom: pan gesture from top to bottom 14 | /// - rightToLeft: pan gesture from right to left 15 | /// - leftToRight: pan gesture from left to right 16 | enum UIPanGestureRecognizerDirection { 17 | case undefined 18 | case bottomToTop 19 | case topToBottom 20 | case rightToLeft 21 | case leftToRight 22 | } 23 | 24 | extension UIPanGestureRecognizer { 25 | 26 | /// Holds information about the direction of Pan gesture 27 | var direction: UIPanGestureRecognizerDirection { 28 | let velocity = self.velocity(in: view) 29 | let isVertical = abs(velocity.y) > abs(velocity.x) 30 | 31 | let direction: UIPanGestureRecognizerDirection 32 | 33 | if isVertical { 34 | direction = velocity.y > 0 ? .topToBottom : .bottomToTop 35 | } else { 36 | direction = velocity.x > 0 ? .leftToRight : .rightToLeft 37 | } 38 | return direction 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/UIStackViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIStackViewExtension.swift 3 | // CarLens 4 | // 5 | 6 | import UIKit 7 | 8 | extension UIStackView { 9 | 10 | /// Convenience maker for the UIStackView 11 | /// 12 | /// - Parameters: 13 | /// - axis: Axis of the stack view 14 | /// - views: View that should be embeded into the stack view 15 | /// - spacing: Spacing of the stack view (0 by default) 16 | /// - Returns: Created stack view 17 | static func make(axis: NSLayoutConstraint.Axis, 18 | with views: [UIView], 19 | spacing: CGFloat = 0, 20 | distribution: UIStackView.Distribution = .fill) -> UIStackView { 21 | let stackView = UIStackView(arrangedSubviews: views) 22 | stackView.axis = axis 23 | stackView.spacing = spacing 24 | stackView.distribution = distribution 25 | return stackView.layoutable() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/UITableViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewExtension.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.UITableView 8 | 9 | extension UITableView { 10 | 11 | /// - SeeAlso: UITableView.register 12 | func register(cell: T.Type) { 13 | register(T.self, forCellReuseIdentifier: T.className) 14 | } 15 | 16 | /// - SeeAlso: UITableView.dequeueReusableCell 17 | func dequeueReusableCell(for indexPath: IndexPath) -> T? { 18 | return dequeueReusableCell(withIdentifier: T.className, for: indexPath) as? T 19 | } 20 | 21 | /// - SeeAlso: UITableView.dequeueReusableHeaderFooter 22 | func dequeueReusableHeaderFooter() -> T? { 23 | return dequeueReusableHeaderFooterView(withIdentifier: T.className) as? T 24 | } 25 | 26 | /// Workaround for not working AutoLayout with UITableView.tableHeaderView 27 | /// This function needs to be called after calculating TableView position 28 | /// In case of UIViewController: viewDidLayoutSubviews() 29 | /// In case of UIView: layoutSubviews() 30 | func relayoutTableHeaderView() { 31 | guard let tableHeaderView = tableHeaderView else { return } 32 | tableHeaderView.layoutIfNeeded() 33 | tableHeaderView.frame.size = tableHeaderView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) 34 | self.tableHeaderView = tableHeaderView 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Extensions/UIViewControllerExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewControllerExtension.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.UIViewController 8 | 9 | extension UIViewController { 10 | 11 | /// Adds view controller as a child (calls all required methods automatically) 12 | /// 13 | /// - Parameters: 14 | /// - child: Controller to be added as child 15 | /// - container: Container to which child should be added 16 | func add(_ child: UIViewController, inside container: UIView) { 17 | addChild(child) 18 | container.addSubview(child.view) 19 | child.didMove(toParent: self) 20 | child.view.translatesAutoresizingMaskIntoConstraints = false 21 | child.view.constraintToSuperviewEdges() 22 | } 23 | 24 | /// Removes view controller from parent if added as child (calls all required methods automatically) 25 | func remove() { 26 | guard parent != nil else { 27 | return 28 | } 29 | willMove(toParent: nil) 30 | removeFromParent() 31 | view.removeFromSuperview() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Factories/NSAttributedStringFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedStringFactory.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.NSAttributedString 8 | 9 | enum NSAttributedStringFactory { 10 | 11 | /// Available tracking types 12 | enum Tracking: CGFloat { 13 | case veryCondensed = -3 14 | case condensed = -0.5 15 | case normal = 0 16 | } 17 | 18 | /// Creates attributed string with applied proper kerning calculated from given Adobe tracking value 19 | /// 20 | /// - Parameters: 21 | /// - text: Text to be attributed 22 | /// - font: Font to be used 23 | /// - tracking: Tracking type to be applied 24 | /// - Returns: Attributed text 25 | static func trackingApplied(_ text: String?, font: UIFont, tracking: Tracking) -> NSAttributedString { 26 | guard let text = text else { return NSAttributedString(string: "") } 27 | return NSAttributedString( 28 | string: text, 29 | attributes: [ 30 | .font: font, 31 | .kern: tracking.rawValue 32 | ] 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Formatter/CRNumberFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRNumberFormatter.swift 3 | // CarLens 4 | // 5 | 6 | 7 | enum CRNumberFormatter { 8 | 9 | static private let numberFormatter = NumberFormatter() 10 | 11 | /// Returns percentage formatted string from given number. 12 | /// 13 | /// - Parameter value: Value to be formatted 14 | /// - Returns: Formatted value. Empty in case of error. 15 | static func percentageFormatted(_ value: Float) -> String { 16 | numberFormatter.numberStyle = .percent 17 | return numberFormatter.string(from: NSNumber(value: value)) ?? "" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Formatter/CRTimeFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRTimeFormatter.swift 3 | // CarLens 4 | // 5 | 6 | 7 | enum CRTimeFormatter { 8 | 9 | /// Returns miliseconds formatted interval string from given time interval. 10 | /// 11 | /// - Parameter value: Value to be formatted 12 | /// - Returns: Formatted value. 13 | static func intervalMilisecondsFormatted(_ value: TimeInterval) -> String { 14 | let miliseconds = Int((value.truncatingRemainder(dividingBy: 1)) * 1000) 15 | return "\(miliseconds) ms" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Models/CarARConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarARConfiguration.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | /// Model containing configuration used for application augmented reality view 10 | struct CarARConfiguration { 11 | 12 | /// Node shift from the place of detected anchor 13 | let nodeShift: NodeShift 14 | 15 | /// Minimum distance from the device that allow pinning nodes 16 | let minimumDistanceFromDevice: CGFloat 17 | 18 | /// Maximum distance from the device that allow pinning nodes 19 | let maximumDistanceFromDevice: CGFloat 20 | 21 | /// Minimum distance between existing nodes that allows pinning another one 22 | let minimumDistanceBetweenNodes: Float 23 | 24 | /// Place for doing the hit test 25 | let pointForHitTest = CGPoint(x: 0.5, y: 0.5) 26 | 27 | init() { 28 | #if ENV_DEVELOPMENT 29 | nodeShift = NodeShift(depth: 0, elevation: 0) 30 | minimumDistanceFromDevice = 0.1 31 | minimumDistanceBetweenNodes = 0.2 32 | maximumDistanceFromDevice = 2 33 | #else 34 | nodeShift = NodeShift(depth: 1, elevation: 1.6) 35 | minimumDistanceFromDevice = 0.5 36 | minimumDistanceBetweenNodes = 2 37 | maximumDistanceFromDevice = 5 38 | #endif 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Models/CarImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarImage.swift 3 | // CarLens 4 | // 5 | 6 | import UIKit.UIImage 7 | 8 | /// Internal struct that holds information about locked and unlocked images for concrete car 9 | struct CarImage: Equatable { 10 | let unlocked: UIImage 11 | let locked: UIImage 12 | let logoUnlocked: UIImage 13 | let logoLocked: UIImage 14 | 15 | /// SeeAlso: Equatable 16 | static func == (lhs: CarImage, rhs: CarImage) -> Bool { 17 | return lhs.unlocked == rhs.unlocked && 18 | lhs.locked == rhs.locked && 19 | lhs.logoUnlocked == rhs.logoUnlocked && 20 | lhs.logoLocked == rhs.logoLocked 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Models/CarSpecificationChartConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarSpecificationChartConfiguration.swift 3 | // CarLens 4 | // 5 | 6 | 7 | /// Model containing configuration used when displaying car specification charts 8 | struct CarSpecificationChartConfiguration { 9 | 10 | /// Speed metrics type to be used for referenceSpeed calculating 11 | private let speedType: SpeedMetricsType 12 | 13 | /// Reference horse power that will be used as 100% 14 | let referenceHorsePower = 320 15 | 16 | /// Reference speed that will be used as 100% depending on the user's system metrics 17 | var referenceSpeed: Int { 18 | switch speedType { 19 | case .mph: 20 | return referenceSpeedInMiles 21 | case .kph: 22 | return referenceSpeedInKilometers 23 | } 24 | } 25 | 26 | /// Reference speed that will be used as 100% (in miles) 27 | private let referenceSpeedInMiles = 200 28 | 29 | /// Reference speed that will be used as 100% (in kilometers) 30 | private let referenceSpeedInKilometers = 322 31 | 32 | /// Reference engine volume that will be used as 100% (in centimeters) 33 | let referenceEngineVolume = 2000 34 | 35 | /// Reference accelerate time that will be used as 0% (in seconds) 36 | let referenceMaxAccelerate: TimeInterval = 20 37 | 38 | /// Reference accelerate time that will be used as 100% (in seconds) 39 | let referenceMinAccelerate: TimeInterval = 2.9 40 | 41 | /// Initializes the CarSpecificationChartConfiguration. 42 | /// 43 | /// - Parameter speedType: Speed Metrics type used for referenceSpeed calculating. 44 | init(with speedType: SpeedMetricsType = SystemMetrics.shared.speedType) { 45 | self.speedType = speedType 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Models/NodeShift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeShift.swift 3 | // CarLens 4 | // 5 | 6 | 7 | struct NodeShift: Equatable { 8 | 9 | let depth: Float 10 | let elevation: Float 11 | 12 | /// SeeAlso: Equatable 13 | static func == (lhs: NodeShift, rhs: NodeShift) -> Bool { 14 | return lhs.depth == rhs.depth && lhs.elevation == rhs.elevation 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Models/RecognitionResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecognitionResult.swift 3 | // CarLens 4 | // 5 | 6 | 7 | /// Describes car recognized by the classifier 8 | struct RecognitionResult: CustomStringConvertible { 9 | 10 | /// Available recognitions 11 | enum Recognition: Equatable { 12 | case car(Car) 13 | case otherCar 14 | case notCar 15 | 16 | static func == (lhs: Recognition, rhs: Recognition) -> Bool { 17 | switch (lhs, rhs) { 18 | case (let .car(carLHS), let .car(carRHS)): 19 | return carLHS == carRHS 20 | case (.otherCar, .otherCar): 21 | return true 22 | case (.notCar, .notCar): 23 | return true 24 | default: 25 | return false 26 | } 27 | } 28 | } 29 | 30 | let recognition: Recognition 31 | 32 | let confidence: Float 33 | 34 | /// Label returned by a model. 35 | let label: String 36 | 37 | /// Initializes the object from given parameters 38 | /// 39 | /// - Parameters: 40 | /// - label: Label received from model 41 | /// - confidence: Confidence received from model 42 | /// - carsDataService: Data Service with cars 43 | init?(label: String, confidence: Float, carsDataService: CarsDataService) { 44 | self.confidence = confidence 45 | self.label = label 46 | if let car = carsDataService.map(classifierLabel: label) { 47 | recognition = .car(car) 48 | } else if label == Constants.Labels.Recognition.otherCar { 49 | recognition = .otherCar 50 | } else if label == Constants.Labels.Detection.notCar { 51 | recognition = .notCar 52 | } else { 53 | return nil 54 | } 55 | } 56 | 57 | /// SeeAlso: CustomStringConvertible 58 | var description: String { 59 | switch recognition { 60 | case .car(let car): 61 | return "\(car.model)\n(\(CRNumberFormatter.percentageFormatted(confidence)))" 62 | case .otherCar: 63 | return "other car\n(\(CRNumberFormatter.percentageFormatted(confidence)))" 64 | case .notCar: 65 | return "not car\n(\(CRNumberFormatter.percentageFormatted(confidence)))" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Services/CarsDataService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarsDataService.swift 3 | // CarLens 4 | // 5 | 6 | 7 | final class CarsDataService { 8 | 9 | /// Local data service with initial data. 10 | private let localDataService: LocalCarsDataService 11 | // Data service used to save data after user interaction with it. 12 | private let databaseService: CarsDatabaseService 13 | 14 | /// Initializes the Cars Data Service. 15 | /// 16 | /// - Parameters: 17 | /// - localDataService: Local data service with initial data. 18 | /// - databaseService: Data service used to save data after user interaction with it. 19 | init(localDataService: LocalCarsDataService = LocalCarsDataService(), 20 | databaseService: CarsDatabaseService = CarsDatabaseService()) { 21 | self.localDataService = localDataService 22 | self.databaseService = databaseService 23 | } 24 | 25 | /// Maps label received from classifier to car object 26 | /// 27 | /// - Parameter classifierLabel: Label received from classifier 28 | /// - Returns: Mapped object 29 | func map(classifierLabel: String) -> Car? { 30 | guard var car = localDataService.cars.first(where: { $0.id == classifierLabel }) else { return nil } 31 | databaseService.mapDiscoveredParameter(car: &car) 32 | return car 33 | } 34 | 35 | /// Gets all available cars with proper recognized states 36 | /// 37 | /// - Returns: Fetched cars 38 | func getAvailableCars() -> [Car] { 39 | var cars = localDataService.cars 40 | databaseService.mapDiscoveredParameter(for: &cars) 41 | return cars 42 | } 43 | 44 | /// Gets number of available cars 45 | /// 46 | /// - Returns: Fetched cars 47 | func getNumberOfCars() -> Int { 48 | return localDataService.cars.count 49 | } 50 | 51 | /// Gets number of discovered cars 52 | /// 53 | /// - Returns: Fetched cars 54 | func getNumberOfDiscoveredCars() -> Int { 55 | let discoveredCars = getAvailableCars().filter { $0.isDiscovered } 56 | return discoveredCars.count 57 | } 58 | 59 | /// Marks given car as discovered or not 60 | /// 61 | /// - Parameters: 62 | /// - car: Car to be set 63 | /// - discovered: Indicating if the car was discovered or not 64 | func mark(car: Car, asDiscovered discovered: Bool) { 65 | databaseService.mark(car: car, asDiscovered: discovered) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Services/CarsDatabaseService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarsDatabaseService.swift 3 | // CarLens 4 | // 5 | 6 | import Foundation 7 | 8 | final class CarsDatabaseService { 9 | 10 | private let userDefaults: UserDefaults 11 | 12 | /// Initializes the Cars Database Service. 13 | /// 14 | /// - Parameter userDefaults: User Defaults used to save the data. 15 | init(with userDefaults: UserDefaults = UserDefaults()) { 16 | self.userDefaults = userDefaults 17 | } 18 | 19 | /// Maps car `isDiscovered` parameter to proper value 20 | /// 21 | /// - Parameter car: Car to be mapped 22 | func mapDiscoveredParameter(car: inout Car) { 23 | car.isDiscovered = isAlreadyAddedAsRecognized(car: car) 24 | } 25 | 26 | /// Maps cars `isDiscovered` parameter to proper value 27 | /// 28 | /// - Parameter cars: Cars to be mapped 29 | func mapDiscoveredParameter(for cars: inout [Car]) { 30 | for index in 0.. Bool { 60 | return userDefaults.bool(forKey: car.id) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Services/HockeyAppService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HockeyAppService.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import HockeySDK 8 | 9 | class HockeyAppService: CrashLogger { 10 | 11 | private lazy var hockeyManager: BITHockeyManager? = { [unowned self] in 12 | #if ENV_DEVELOPMENT 13 | return nil 14 | #else 15 | BITHockeyManager.shared().configure(withIdentifier: self.keys.hockeyAppIdentifier) 16 | BITHockeyManager.shared().crashManager.crashManagerStatus = .autoSend 17 | return BITHockeyManager.shared() 18 | #endif 19 | }() 20 | 21 | private let keys: ApplicationKeys 22 | 23 | /// Initialize an instance of the receiver 24 | /// 25 | /// - Parameter keys: Keys that securely provides credentials 26 | init(keys: ApplicationKeys) { 27 | self.keys = keys 28 | } 29 | 30 | /// - SeeAlso: CrashLogger.start() 31 | func start() { 32 | hockeyManager?.start() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Services/InputNormalizationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputNormalizationService.swift 3 | // CarLens 4 | // 5 | 6 | 7 | final class InputNormalizationService { 8 | 9 | /// Number of recognition results needed for normalization to begin. 10 | private let numberOfValuesNeeded: Int 11 | 12 | /// Storage of recognition results to be normalized. 13 | private var recognitionResults: [RecognitionResult] = [] 14 | 15 | private let carsDataService: CarsDataService 16 | 17 | /// Initializes the normalizer 18 | /// 19 | /// - Parameters: 20 | /// - numberOfValues: Number of last saved values that will be used for normalization. Must be greater than 0. 21 | /// - carsDataService: Data Service with cars. 22 | init(numberOfValues: Int, carsDataService: CarsDataService) { 23 | guard numberOfValues > 0 else { 24 | fatalError("Cannot initialize normalizer with value \(numberOfValues). It must be greater than 0") 25 | } 26 | self.numberOfValuesNeeded = numberOfValues 27 | self.carsDataService = carsDataService 28 | } 29 | 30 | /// Normalizes given value. 31 | /// 32 | /// - Parameter recognitionData: Recognition Results to be normalized. 33 | /// - Returns: Normalized RecognitionResult value. 34 | func normalizeConfidence(from recognitionData: [RecognitionResult]) -> RecognitionResult? { 35 | let sortedResults = recognitionData.sorted(by: { $0.confidence > $1.confidence }) 36 | let numberOfResultsNeeded = numberOfValuesNeeded - recognitionResults.count 37 | let resultsNeeded = sortedResults.prefix(numberOfResultsNeeded) 38 | recognitionResults.append(contentsOf: resultsNeeded) 39 | guard recognitionResults.count == numberOfValuesNeeded else { return nil } 40 | let averageConfidencesForEachLabel: [String: Float] = dictionaryWithValuesSum(from: recognitionResults) 41 | .mapValues { countAverage($0) } 42 | guard let bestResult = averageConfidencesForEachLabel.max(by: { $0.value < $1.value }) else { return nil } 43 | return RecognitionResult(label: bestResult.0, confidence: bestResult.1, carsDataService: carsDataService) 44 | } 45 | 46 | /// Resets the normalizer. 47 | func reset() { 48 | recognitionResults = [] 49 | } 50 | 51 | private func dictionaryWithValuesSum(from recognitionResults: [RecognitionResult]) -> [String: [Float]] { 52 | return recognitionResults.reduce(into: [:]) { counts, result in 53 | counts[result.label, default: []].append(result.confidence) 54 | } 55 | } 56 | 57 | private func countAverage(_ array: [Float]) -> Float { 58 | return array.reduce(0, { $0 + $1 }) / Float(array.count) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Services/LocalCarsDataService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalCarsDataService.swift 3 | // CarLens 4 | // 5 | 6 | 7 | final class LocalCarsDataService { 8 | 9 | /// Array of local car objects 10 | var cars: [Car] = [] 11 | 12 | /// Initializes the service feeding `localCars` parameter with fetched data 13 | init(with jsonPath: String? = Bundle.main.path(forResource: "cars", ofType: "json")) { 14 | guard 15 | let path = jsonPath, 16 | let data = try? Data(contentsOf: URL(fileURLWithPath: path)), 17 | let decoded = try? JSONDecoder().decode(LocalCars.self, from: data) 18 | else { 19 | return 20 | } 21 | cars = decoded.cars 22 | } 23 | } 24 | 25 | private struct LocalCars: Decodable { 26 | let cars: [Car] 27 | } 28 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Services/SearchService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchService.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.UIApplication 8 | 9 | final class SearchService { 10 | 11 | /// Services available to search with added base URLs as rawValue 12 | enum Service: String { 13 | case google = "https://www.google.com/search?q=" 14 | } 15 | 16 | /// Service used for opening an URL 17 | private let urlOpenerService: URLOpenerService 18 | 19 | /// Initializes the Search Service 20 | /// 21 | /// - Parameter urlOpenerService: service to be used for URL opening 22 | init(with urlOpenerService: URLOpenerService = URLOpenerService()) { 23 | self.urlOpenerService = urlOpenerService 24 | } 25 | 26 | /// Opens search in given service for given car 27 | /// 28 | /// - Parameters: 29 | /// - service: Service to be searched 30 | /// - car: Car to be used for constructing query 31 | /// - completion: Completion to be called when URL will open 32 | func search(_ service: Service, for car: Car, completion: ((Bool) -> Void)? = nil) { 33 | let phrase = "\(car.make) \(car.model)" 34 | let encoded = phrase.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" 35 | guard let url = URL(string: service.rawValue + encoded) else { return } 36 | urlOpenerService.open(url, completionHandler: completion) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Services/ToastDisplayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToastDisplayer.swift 3 | // CarLens 4 | // 5 | 6 | import UIKit 7 | 8 | enum ToastDisplayer { 9 | 10 | private enum Constants { 11 | static let attachPinErrorLabelHeight: CGFloat = 40 12 | static let attachPinErrorLabelFontSize: CGFloat = 20 13 | static let animationDuration = 0.3 14 | } 15 | 16 | private static var toastLabel: UILabel = { 17 | let label = UILabel() 18 | label.backgroundColor = .black 19 | label.alpha = 0.0 20 | label.layer.cornerRadius = Constants.attachPinErrorLabelHeight / 2.0 21 | label.clipsToBounds = true 22 | label.textColor = .white 23 | label.translatesAutoresizingMaskIntoConstraints = false 24 | label.textAlignment = .center 25 | return label 26 | }() 27 | 28 | static func show(in view: UIView, text: String) { 29 | toastLabel.text = text 30 | 31 | view.addSubview(toastLabel) 32 | 33 | toastLabel.constraintCenterToSuperview(axis: [.horizontal]) 34 | NSLayoutConstraint.activate([ 35 | toastLabel.heightAnchor.constraint(equalToConstant: Constants.attachPinErrorLabelHeight), 36 | toastLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9), 37 | toastLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20) 38 | ]) 39 | toastLabel.heightAnchor.constraint(equalToConstant: Constants.attachPinErrorLabelHeight) 40 | toastLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9) 41 | 42 | UIView.animate(withDuration: Constants.animationDuration, 43 | delay: 1.0, 44 | options: [], 45 | animations: { 46 | self.toastLabel.alpha = 0.8 47 | }) { _ in 48 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.0, execute: { 49 | UIView.animate(withDuration: Constants.animationDuration, 50 | animations: { 51 | self.toastLabel.alpha = 0.0 52 | }) { _ in 53 | self.toastLabel.removeFromSuperview() 54 | } 55 | }) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Services/URLOpenerService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLOpenerService.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import Foundation 8 | import UIKit 9 | 10 | // Interface for opening an URL 11 | protocol URLOpener { 12 | 13 | /// - SeeAlso: UIApplication 14 | func canOpenURL(_ url: URL) -> Bool 15 | 16 | /// - SeeAlso: UIApplication 17 | func open(_ url: URL, 18 | options: [UIApplication.OpenExternalURLOptionsKey: Any], 19 | completionHandler completion: ((Bool) -> Void)?) 20 | } 21 | 22 | /// Struct used for opening an URL. 23 | struct URLOpenerService { 24 | 25 | /// Application used to open URL. 26 | private let application: URLOpener 27 | 28 | /// Initializing the URL Opener Service 29 | /// - Parameter application: Application used to open URL. Default set to UIApplication.shared. 30 | init(with application: URLOpener = UIApplication.shared) { 31 | self.application = application 32 | } 33 | 34 | /// - SeeAlso: UIApplication 35 | func open(_ url: URL, 36 | options: [UIApplication.OpenExternalURLOptionsKey: Any] = [:], 37 | completionHandler completion: ((Bool) -> Void)? = nil) { 38 | if application.canOpenURL(url) { 39 | application.open(url, options: options, completionHandler: completion) 40 | } else { 41 | completion?(false) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Services/UserDefaultsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultsService.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import Foundation 8 | 9 | /// UserDefaults Service used for storing data. 10 | final class UserDefaultsService { 11 | 12 | private enum Keys { 13 | static let didShowOnboardingKey = "didShowOnboardingKey" 14 | } 15 | 16 | /// UserDefaultsService shared instance. 17 | static let shared = UserDefaultsService() 18 | 19 | /// UserDefaults used for storing data. 20 | private var userDefaults: UserDefaults 21 | 22 | /// Initializing the service. 23 | /// - Parameter userDefaults: userDefaults used for storing data. 24 | private init(userDefaults: UserDefaults = UserDefaults.standard) { 25 | self.userDefaults = userDefaults 26 | } 27 | 28 | /// Indicating whether the onboarding should be shown. 29 | var shouldShowOnboarding: Bool { 30 | return !userDefaults.bool(forKey: Keys.didShowOnboardingKey) 31 | } 32 | 33 | /// Storing the info about shown onboarding. 34 | func store(didShowOnboarding: Bool) { 35 | userDefaults.set(didShowOnboarding, forKey: Keys.didShowOnboardingKey) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/System Metrics/SystemMetrics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemMetrics.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import Foundation 8 | 9 | /// Enum describing the speed metrics options. 10 | /// 11 | /// - mph: Miles per hour. 12 | /// - kph: Kilometers per hour. 13 | enum SpeedMetricsType { 14 | case mph 15 | case kph 16 | } 17 | 18 | /// A struct used for determining user's metrics. 19 | struct SystemMetrics { 20 | 21 | /// The shared instance of SystemMetrics. 22 | static let shared: SystemMetrics = SystemMetrics() 23 | 24 | /// Current metrics used for speed. 25 | let speedType: SpeedMetricsType 26 | 27 | /// Initializes a new instance of SystemMetrics. 28 | /// - Parameter locale: the user's locale for which metrics should be configured. 29 | init(with locale: Locale = Locale.current) { 30 | self.speedType = locale.usesMetricSystem ? .kph : .mph 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Views/CarListNavigationBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarListNavigationBar.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | final class CarListNavigationBar: View, ViewSetupable { 10 | 11 | private let maximumNumber: Int 12 | 13 | private let currentNumber: Int 14 | 15 | private lazy var titleLabel: UILabel = { 16 | let view = UILabel() 17 | view.font = .gliscorGothicFont(ofSize: 22) 18 | view.textColor = .black 19 | view.textAlignment = .center 20 | view.attributedText = NSAttributedStringFactory.trackingApplied(Localizable.CarsList.title.uppercased(), 21 | font: view.font, 22 | tracking: .condensed) 23 | return view.layoutable() 24 | }() 25 | 26 | /// Back button with arrow 27 | let backButton: UIButton = { 28 | let view = UIButton() 29 | view.setImage(#imageLiteral(resourceName: "button-back-arrow"), for: .normal) 30 | return view.layoutable() 31 | }() 32 | 33 | /// Progress view displayed as full oval figure 34 | lazy var progressView = FullOvalProgressView(currentNumber: currentNumber, 35 | maximumNumber: maximumNumber, 36 | invalidateChartInstantly: false).layoutable() 37 | 38 | /// Initializes CarListNavigationBar 39 | /// 40 | /// - Parameters: 41 | /// - currentNumber: Current value of progress view 42 | /// - maximumNumber: Maximum value of progress view 43 | init(currentNumber: Int, maximumNumber: Int) { 44 | self.maximumNumber = maximumNumber 45 | self.currentNumber = currentNumber 46 | super.init() 47 | 48 | accessibilityIdentifier = "carsList/navigationBar/main" 49 | } 50 | 51 | /// - SeeAlso: ViewSetupable 52 | func setupViewHierarchy() { 53 | [titleLabel, backButton, progressView].forEach(addSubview) 54 | } 55 | 56 | /// - SeeAlso: ViewSetupable 57 | func setupConstraints() { 58 | titleLabel.constraintCenterToSuperview() 59 | backButton.constraintToSuperviewEdges(excludingAnchors: [.right], 60 | withInsets: .init(top: 25, left: 37, bottom: 25, right: 0)) 61 | backButton.widthAnchor.constraint(equalToConstant: 24).isActive = true 62 | progressView.constraintToSuperviewEdges(excludingAnchors: [.left], 63 | withInsets: .init(top: 10, left: 0, bottom: 10, right: 37)) 64 | progressView.widthAnchor.constraint(equalToConstant: 50).isActive = true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Views/CarScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarScene.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import SpriteKit 8 | 9 | final class CarScene: SKScene { 10 | 11 | /// Callback called when user tapped AR pin with given car id 12 | var didTapCar: ((String) -> Void)? 13 | 14 | /// Callback called when user tapped view excluding the AR pin 15 | var didTapBackgroundView: (() -> Void)? 16 | 17 | /// SeeAlso: SKScene 18 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 19 | super.touchesBegan(touches, with: event) 20 | var didTapCarLabel = false 21 | for touch in touches { 22 | for node in nodes(at: touch.location(in: self)) { 23 | guard let carId = node.name else { continue } 24 | didTapCar?(carId) 25 | didTapCarLabel = true 26 | } 27 | } 28 | guard !didTapCarLabel else { return } 29 | didTapBackgroundView?() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Views/DefaultLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultLoader.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | class DefaultLoader: Loader { 10 | 11 | private let baseView: UIView 12 | 13 | private var contentView: UIView? 14 | 15 | /// Initialize the loader inside given view 16 | /// 17 | /// - Parameter baseView: View to show loader on 18 | init(inside baseView: UIView) { 19 | self.baseView = baseView 20 | } 21 | 22 | /// - SeeAlso: Loader.isSpinning 23 | var isSpinning: Bool { 24 | return contentView != nil 25 | } 26 | 27 | /// - SeeAlso: Loader.toggle() 28 | func toggle(_ show: Bool) { 29 | guard show else { 30 | UIView.animate(withDuration: 0.2, 31 | animations: { 32 | self.contentView?.transform = CGAffineTransform(scaleX: 0.01, y: 0.01) 33 | }, completion: { _ in 34 | self.contentView?.removeFromSuperview() 35 | self.baseView.isUserInteractionEnabled = true 36 | }) 37 | return 38 | } 39 | contentView = makeLoaderView() 40 | guard let contentView = contentView else { return } 41 | contentView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01) 42 | baseView.addSubview(contentView) 43 | baseView.bringSubviewToFront(contentView) 44 | baseView.isUserInteractionEnabled = false 45 | UIView.animate(withDuration: 0.2, animations: { 46 | self.contentView?.transform = .identity 47 | }) 48 | } 49 | 50 | private func makeLoaderView(dimmedBackground: Bool = true, withMessage message: String? = nil) -> UIView { 51 | let background = UIView().layoutable() 52 | background.backgroundColor = UIColor.black.withAlphaComponent(0.6) 53 | background.layer.cornerRadius = 15 54 | let loader = UIActivityIndicatorView(style: .whiteLarge).layoutable() 55 | loader.startAnimating() 56 | 57 | background.addSubview(loader) 58 | baseView.addSubview(background) 59 | 60 | background.constraintCenterToSuperview() 61 | loader.constraintCenterToSuperview() 62 | NSLayoutConstraint.activate([ 63 | background.widthAnchor.constraint(equalTo: baseView.widthAnchor, multiplier: 0.4), 64 | background.heightAnchor.constraint(equalTo: background.widthAnchor) 65 | ]) 66 | return background 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Views/DetectionViewfinderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetectionViewfinderView.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | import Lottie 9 | 10 | final class DetectionViewfinderView: View, ViewSetupable { 11 | 12 | /// Error that can occur durning updating state 13 | enum DetectionViewfinderViewError: Error { 14 | case wrongValueProvided 15 | } 16 | 17 | private lazy var viewfinderAnimationView = LOTAnimationView(name: "viewfinder_bracket").layoutable() 18 | 19 | private lazy var informationSwitcherView = TextSwitcherView(currentText: Localizable.Recognition.pointCameraAtCar) 20 | .layoutable() 21 | 22 | /// Updates the detection state 23 | /// 24 | /// - Parameters: 25 | /// - result: Result of the detection 26 | func update(to result: RecognitionResult) { 27 | viewfinderAnimationView.animationProgress = CGFloat(result.confidence) 28 | guard result.confidence > Constants.Recognition.Threshold.neededToShowProgress else { 29 | informationSwitcherView.switchLabelsWithText(Localizable.Recognition.pointCameraAtCar) 30 | return 31 | } 32 | let text: String 33 | switch result.recognition { 34 | case .car(_): 35 | text = Localizable.Recognition.recognizing 36 | case .otherCar: 37 | text = Localizable.Recognition.otherCar 38 | case .notCar: 39 | text = Localizable.Recognition.pointCameraAtCar 40 | } 41 | informationSwitcherView.switchLabelsWithText(text) 42 | } 43 | 44 | /// - SeeAlso: ViewSetupable 45 | func setupViewHierarchy() { 46 | [viewfinderAnimationView, informationSwitcherView].forEach(addSubview) 47 | } 48 | 49 | /// - SeeAlso: ViewSetupable 50 | func setupConstraints() { 51 | viewfinderAnimationView.constraintToSuperviewEdges(excludingAnchors: [.bottom]) 52 | informationSwitcherView.constraintToSuperviewEdges(excludingAnchors: [.top], 53 | withInsets: .init(top: 0, left: 8, bottom: 0, right: 8)) 54 | NSLayoutConstraint.activate([ 55 | viewfinderAnimationView.bottomAnchor.constraint(equalTo: informationSwitcherView.topAnchor, constant: 60), 56 | informationSwitcherView.centerXAnchor.constraint(equalTo: centerXAnchor), 57 | informationSwitcherView.heightAnchor.constraint(equalToConstant: 80) 58 | ]) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Views/HorizontalStarsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalStarsView.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | import Lottie 9 | 10 | final class HorizontalStarsView: View, ViewSetupable { 11 | 12 | private var starCount: Int 13 | 14 | private var isLocked: Bool 15 | 16 | private let numberOfStars = 5 17 | 18 | private lazy var animationView = LOTAnimationView(name: "horizontal-stars").layoutable() 19 | 20 | /// Initializes the view with given parameters 21 | /// 22 | /// - Parameters: 23 | /// - starCount: Count of achieved stars 24 | /// - invalidateChartInstantly: Chart will be updated instantly without animation if this value indicates false. 25 | /// When passing false, remember to use method `invalidatChart(animated:)` also 26 | /// - isLocked: Indicating if the info should be locked 27 | init(starCount: Int, invalidateChartInstantly: Bool, isLocked: Bool = false) { 28 | self.starCount = starCount 29 | self.isLocked = isLocked 30 | super.init() 31 | setup(starCount: starCount, invalidateChartInstantly: invalidateChartInstantly) 32 | } 33 | 34 | /// Setups the view with given parameters. Use only inside reusable views. 35 | /// 36 | /// - Parameters: 37 | /// - starCount: Count of achieved stars 38 | /// - invalidateChartInstantly: Chart will be updated instantly without animation if this value indicates false. 39 | /// When passing false, remember to use method `invalidatChart(animated:)` also 40 | func setup(starCount: Int, invalidateChartInstantly: Bool, isLocked: Bool = false) { 41 | guard starCount >= 0, starCount <= 5 else { 42 | fatalError("Wrong input provided for stars chart") 43 | } 44 | animationView.set(progress: 0, animated: false) 45 | self.starCount = starCount 46 | self.isLocked = isLocked 47 | if invalidateChartInstantly { 48 | invalidateChart(animated: false) 49 | } 50 | } 51 | 52 | /// Invalidates the progress shown on the chart 53 | /// 54 | /// - Parameter animated: Indicating if invalidation should be animated 55 | func invalidateChart(animated: Bool) { 56 | var progress = Double(starCount) / Double(numberOfStars + 1) 57 | if isLocked { 58 | progress = 0 59 | } 60 | animationView.set(progress: CGFloat(progress), animated: animated) 61 | } 62 | 63 | /// Clear the progress shown on the chart 64 | /// 65 | /// - Parameter animated: Indicating if progress change should be animated 66 | func clearChart(animated: Bool) { 67 | animationView.set(progress: 0, animated: animated) 68 | } 69 | 70 | /// - SeeAlso: ViewSetupable 71 | func setupViewHierarchy() { 72 | addSubview(animationView) 73 | } 74 | 75 | /// - SeeAlso: ViewSetupable 76 | func setupConstraints() { 77 | animationView.constraintToSuperviewEdges() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Views/LabeledCarImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LabeledCarImageView.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | final class LabeledCarImageView: View, ViewSetupable { 10 | 11 | private lazy var modelLabel: UILabel = { 12 | let view = UILabel() 13 | view.font = .gliscorGothicFont(ofSize: UIDevice.screenSizeBiggerThan4Inches ? 102 : 72) 14 | view.textColor = UIColor(hex: 0xC4D0D6) 15 | view.numberOfLines = 1 16 | view.textAlignment = .center 17 | return view.layoutable() 18 | }() 19 | 20 | private lazy var carImageView: UIImageView = { 21 | let view = UIImageView() 22 | view.contentMode = .scaleAspectFit 23 | return view.layoutable() 24 | }() 25 | 26 | /// Initializes the view with given car 27 | /// 28 | /// - Parameter car: Car to be used for updating the view 29 | init(car: Car? = nil) { 30 | super.init() 31 | guard let car = car else { return } 32 | setup(with: car) 33 | } 34 | 35 | /// Setups the view with given car. Use only inside reusable views. 36 | /// 37 | /// - Parameter car: Car to be used for updating the view 38 | func setup(with car: Car) { 39 | modelLabel.attributedText = NSAttributedStringFactory.trackingApplied(car.model.uppercased(), 40 | font: modelLabel.font, 41 | tracking: .veryCondensed) 42 | carImageView.image = car.isDiscovered ? car.image.unlocked : car.image.locked 43 | } 44 | 45 | /// - SeeAlso: ViewSetupable 46 | func setupViewHierarchy() { 47 | [modelLabel, carImageView].forEach(addSubview) 48 | } 49 | 50 | /// - SeeAlso: ViewSetupable 51 | func setupConstraints() { 52 | modelLabel.constraintCenterToSuperview(withConstant: .init(x: 0, y: -40)) 53 | carImageView.constraintCenterToSuperview(withConstant: .init(x: 0, 54 | y: UIDevice.screenSizeBiggerThan4Inches ? 20 : 15)) 55 | carImageView.constraintToConstant(.init(width: 320, height: 220)) 56 | } 57 | 58 | /// - SeeAlso: ViewSetupable 59 | func setupProperties() { 60 | backgroundColor = .clear 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Views/ModelNameView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelNameView.swift 3 | // CarLens 4 | // 5 | 6 | import UIKit 7 | 8 | final class ModelNameView: View, ViewSetupable { 9 | 10 | /// Car object used to initlaize labels 11 | private let car: Car 12 | 13 | /// StackView with model and brand labels 14 | private lazy var stackView: UIStackView = { 15 | let stackView = UIStackView.make(axis: .vertical, with: [modelLabel, brandLabel], spacing: -2) 16 | modelLabel.text = car.model 17 | brandLabel.text = car.make 18 | return stackView 19 | }() 20 | 21 | /// Model label 22 | private let modelLabel: UILabel = { 23 | let label = UILabel() 24 | label.font = UIFont.systemFont(ofSize: 32.0, weight: .bold) 25 | label.textColor = .crFontDark 26 | return label.layoutable() 27 | }() 28 | 29 | /// Brand label 30 | private let brandLabel: UILabel = { 31 | let label = UILabel() 32 | label.font = UIFont.systemFont(ofSize: 18.0) 33 | label.textColor = .crFontGray 34 | label.clipsToBounds = false 35 | return label.layoutable() 36 | }() 37 | 38 | /// Initializes the model view with given car parameter 39 | /// 40 | /// - Parameter car: Car instance used to instantiate labels 41 | init(car: Car) { 42 | self.car = car 43 | super.init() 44 | } 45 | 46 | /// - SeeAlso: ViewSetupable 47 | func setupViewHierarchy() { 48 | addSubview(stackView) 49 | } 50 | 51 | /// - SeeAlso: ViewSetupable 52 | func setupConstraints() { 53 | stackView.constraintToSuperviewEdges() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /CarLens/Source Files/Common/Views/SeparatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SeparatorView.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | /// Empty separator view, mainly for UIStackView 10 | final class SeparatorView: View { 11 | 12 | private let axis: NSLayoutConstraint.Axis 13 | 14 | private let thickness: CGFloat 15 | 16 | /// Initialize an view 17 | /// 18 | /// - Parameters: 19 | /// - axis: The axis of the separator view. 20 | /// - thickness: The thickness of the separator view. 21 | /// - color: The background color of the separator view. Transparent by default 22 | init(axis: NSLayoutConstraint.Axis, thickness: CGFloat, color: UIColor = .clear) { 23 | self.axis = axis 24 | self.thickness = thickness 25 | super.init() 26 | translatesAutoresizingMaskIntoConstraints = false 27 | backgroundColor = color 28 | } 29 | 30 | override var intrinsicContentSize: CGSize { 31 | return CGSize(width: axis == .horizontal ? thickness : 0, height: axis == .vertical ? thickness : 0) 32 | } 33 | } 34 | 35 | extension UIView { 36 | 37 | /// Initialize an separator view 38 | /// 39 | /// - Parameters: 40 | /// - axis: The axis of the separator view. 41 | /// - thickness: The thickness of the separator view. 42 | /// - color: The background color of the separator view. Transparent by default 43 | static func separator(axis: NSLayoutConstraint.Axis, thickness: CGFloat, color: UIColor = .clear) -> UIView { 44 | return SeparatorView(axis: axis, thickness: thickness, color: color) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Onboarding/OnboardingFlowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingFlowController.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | final class OnboardingFlowController: FlowController { 10 | 11 | /// Root view controler of the flow 12 | private(set) var rootViewController = UIViewController() 13 | 14 | /// Next Flow Controller to which user should be transitioned from this view 15 | private(set) var nextFlowController: FlowController? 16 | 17 | private let dependencies: ApplicationDependencies 18 | 19 | private let applicationFactory: ApplicationFactory 20 | 21 | /// Initializes flow controllers with given dependencies 22 | /// 23 | /// - Parameters: 24 | /// - dependencies: Dependencies to use in the class 25 | /// - applicationFactory: Factory used to create controller with injected dependencies 26 | init(dependencies: ApplicationDependencies, applicationFactory: ApplicationFactory) { 27 | self.dependencies = dependencies 28 | self.applicationFactory = applicationFactory 29 | rootViewController = makeOnboardingViewController() 30 | } 31 | 32 | private func makeOnboardingViewController() -> UIViewController { 33 | let viewController = applicationFactory.onboardingViewController() 34 | viewController.eventTriggered = { [unowned self] event in 35 | switch event { 36 | case .didTriggerFinishOnboarding: 37 | self.nextFlowController = self.makeRecognitionFlowController() 38 | let recognitionController = self.nextFlowController?.rootViewController 39 | viewController.present(recognitionController!, animated: false, completion: nil) 40 | } 41 | } 42 | return viewController 43 | } 44 | 45 | private func makeRecognitionFlowController() -> FlowController { 46 | return RecognitionFlowController(dependencies: dependencies, applicationFactory: applicationFactory) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Onboarding/Root/Animation Player/OnboardingAnimationPlayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingAnimationPlayer.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import Foundation 8 | import AVKit 9 | 10 | /// Class which handles onboarding animation by using AVPlayerViewController. 11 | final class OnboardingAnimationPlayer { 12 | 13 | private var bundle: Bundle 14 | 15 | private var animationState: OnboardingTransitionAnimationState = .onFirst 16 | 17 | private var playerTimeObserver: Any? 18 | 19 | init(with bundle: Bundle = Bundle.main) { 20 | self.bundle = bundle 21 | } 22 | 23 | /// Video View Controller which playes video as an animation. 24 | lazy var playerViewController: AVPlayerViewController = { 25 | let viewController = AVPlayerViewController() 26 | guard let videoPath = bundle.path(forResource: "onboarding", ofType: "mp4") else { return viewController } 27 | let videoUrl = URL(fileURLWithPath: videoPath) 28 | viewController.player = AVPlayer(url: videoUrl) 29 | viewController.showsPlaybackControls = false 30 | viewController.view.backgroundColor = UIColor.clear 31 | return viewController 32 | }() 33 | 34 | /// Handling animation of the view by changing the content video. 35 | /// 36 | /// - Parameters: 37 | /// - previousPageIndex: Previous page from which user transitioned. 38 | /// - currentPageIndex: Page on which user is currently now. 39 | func animate(fromPage previousPageIndex: Int, to currentPageIndex: Int) { 40 | guard let state = OnboardingTransitionAnimationState(fromPage: previousPageIndex, to: currentPageIndex) 41 | else { return } 42 | animationState = state 43 | switch animationState { 44 | case .onFirst: 45 | let deadlineTime = DispatchTime.now() + 46 | .milliseconds(Constants.OnboardingAnimation.initialAnimationDelayInMilliseconds) 47 | DispatchQueue.main.asyncAfter(deadline: deadlineTime, execute: { 48 | self.addTimeObserver(for: state.endingTime) 49 | self.playerViewController.player?.play() 50 | }) 51 | return 52 | case .fromFirstToSecond, .fromSecondToFirst: 53 | removeTimeObserver() 54 | addTimeObserver(for: state.endingTime) 55 | case .fromSecondToThird: 56 | removeTimeObserver() 57 | case .fromThirdToSecond: 58 | addTimeObserver(for: state.endingTime) 59 | } 60 | playerViewController.player?.seek(to: state.startingTime) 61 | playerViewController.player?.play() 62 | } 63 | 64 | private func removeTimeObserver() { 65 | guard let playerTimeObserver = playerTimeObserver else { return } 66 | playerViewController.player?.removeTimeObserver(playerTimeObserver) 67 | self.playerTimeObserver = nil 68 | } 69 | 70 | private func addTimeObserver(for time: CMTime) { 71 | self.playerTimeObserver = playerViewController.player? 72 | .addBoundaryTimeObserver(forTimes: [NSValue.init(time: time)], queue: nil, using: { [weak self] in 73 | self?.playerViewController.player?.pause() 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Onboarding/Root/Animation Player/OnboardingTransitionAnimationState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingTransitionAnimationState.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import Foundation 8 | import AVKit 9 | 10 | /// Enum indicating the state of the transition between pages. 11 | enum OnboardingTransitionAnimationState { 12 | 13 | case onFirst, fromFirstToSecond, fromSecondToThird, fromThirdToSecond, fromSecondToFirst 14 | 15 | var startingTime: CMTime { 16 | let value: Int64 17 | switch self { 18 | case .onFirst, 19 | .fromSecondToFirst: 20 | value = 0 21 | case .fromFirstToSecond, 22 | .fromThirdToSecond: 23 | value = Constants.OnboardingAnimation.StartFrames.secondScreen 24 | case .fromSecondToThird: 25 | value = Constants.OnboardingAnimation.StartFrames.thirdScreen 26 | } 27 | return CMTimeMake(value: value, timescale: 60) 28 | } 29 | 30 | var endingTime: CMTime { 31 | let value: Int64 32 | switch self { 33 | case .onFirst, 34 | .fromSecondToFirst: 35 | value = Constants.OnboardingAnimation.FinishFrames.firstScreen 36 | case .fromFirstToSecond, 37 | .fromThirdToSecond: 38 | value = Constants.OnboardingAnimation.FinishFrames.secondScreen 39 | case .fromSecondToThird: 40 | value = Constants.OnboardingAnimation.FinishFrames.thirdScreen 41 | } 42 | return CMTimeMake(value: value, timescale: 60) 43 | } 44 | 45 | /// Initializing the OnboardingTransitionAnimationState instance. 46 | /// 47 | /// - Parameters: 48 | /// - previousPageIndex: Previous page from which user transitioned. 49 | /// - currentPageIndex: Page on which user is currently now. 50 | init?(fromPage previousPageIndex: Int, to currentPageIndex: Int) { 51 | let pages = (previousPageIndex, currentPageIndex) 52 | switch pages { 53 | case (0, 0): 54 | self = .onFirst 55 | case (0, 1): 56 | self = .fromFirstToSecond 57 | case (1, 2): 58 | self = .fromSecondToThird 59 | case (2, 1): 60 | self = .fromThirdToSecond 61 | case (1, 0): 62 | self = .fromSecondToFirst 63 | default: 64 | return nil 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Onboarding/Root/OnboardingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingView.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | final class OnboardingView: View, ViewSetupable { 10 | 11 | /// Animation View which is responsible for showing the onboarding animation. 12 | lazy var animationView = UIView().layoutable() 13 | 14 | /// Page View with onboarding screens. 15 | lazy var pageView = UIView().layoutable() 16 | 17 | /// Button indicating a possibility of moving to the next page. 18 | lazy var nextButton: UIButton = { 19 | let view = UIButton(type: .system) 20 | view.accessibilityIdentifier = "onboarding/button/next" 21 | view.setImage(#imageLiteral(resourceName: "button-next-page"), for: .normal) 22 | view.imageView?.contentMode = .scaleAspectFit 23 | return view.layoutable() 24 | }() 25 | 26 | /// Page Indicator animation view. 27 | let indicatorAnimationView = OnboardingIndicatorAnimationView().layoutable() 28 | 29 | // MARK: - Setup 30 | func setupViewHierarchy() { 31 | [animationView, pageView, indicatorAnimationView, nextButton].forEach { addSubview($0) } 32 | } 33 | 34 | func setupConstraints() { 35 | pageView.constraintToSuperviewEdges(excludingAnchors: [.top, .bottom]) 36 | let animationViewHeightMultiplier: CGFloat = UIDevice.screenSizeBiggerThan4Inches ? 0.5 : 0.4 37 | NSLayoutConstraint.activate([ 38 | animationView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: animationViewHeightMultiplier), 39 | animationView.widthAnchor.constraint(equalTo: animationView.heightAnchor, multiplier: 1.1), 40 | animationView.centerXAnchor.constraint(equalTo: centerXAnchor), 41 | animationView.bottomAnchor.constraint(equalTo: pageView.topAnchor, constant: 4), 42 | pageView.heightAnchor.constraint(equalToConstant: 125), 43 | pageView.bottomAnchor.constraint(equalTo: indicatorAnimationView.topAnchor, constant: -32), 44 | indicatorAnimationView.widthAnchor.constraint(equalToConstant: indicatorAnimationView.viewWidth), 45 | indicatorAnimationView.heightAnchor.constraint(equalToConstant: indicatorAnimationView.viewHeight), 46 | indicatorAnimationView.centerXAnchor.constraint(equalTo: centerXAnchor), 47 | indicatorAnimationView.bottomAnchor.constraint(equalTo: nextButton.topAnchor, constant: -42), 48 | nextButton.centerXAnchor.constraint(equalTo: centerXAnchor), 49 | nextButton.heightAnchor.constraint(equalTo: widthAnchor, multiplier: 0.3), 50 | nextButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -18) 51 | ]) 52 | } 53 | 54 | func setupProperties() { 55 | backgroundColor = UIColor.crBackgroundGray 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Onboarding/Root/Page View Controller/Content/OnboardingContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingView.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | final class OnboardingContentView: View, ViewSetupable { 10 | 11 | lazy var titleLabel: UILabel = { 12 | let view = UILabel() 13 | view.font = .systemFont(ofSize: 22, weight: .semibold) 14 | view.textColor = UIColor.crOnboardingFontDarkGray 15 | return view.layoutable() 16 | }() 17 | 18 | lazy var infoLabel: UILabel = { 19 | let view = UILabel() 20 | view.textColor = UIColor.crOnboardingFontLightGray 21 | view.numberOfLines = 0 22 | return view.layoutable() 23 | }() 24 | 25 | // MARK: - Setup 26 | /// Setup method for the view 27 | /// 28 | /// - Parameters: 29 | /// - image: Image to be used for an onboarding information. 30 | /// - titleText: The text to be set as a title. 31 | /// - infoText: The description label text. 32 | func setup(with titleText: String, infoText: String) { 33 | titleLabel.text = titleText 34 | infoLabel.attributedText = NSAttributedString(string: infoText) 35 | .withKerning(-0.15) 36 | .withLineSpacing(4.5, NSTextAlignment.center) 37 | .withFont(.systemFont(ofSize: 16)) 38 | } 39 | 40 | func setupViewHierarchy() { 41 | [titleLabel, infoLabel].forEach { addSubview($0) } 42 | } 43 | 44 | func setupConstraints() { 45 | NSLayoutConstraint.activate([ 46 | titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor), 47 | titleLabel.bottomAnchor.constraint(equalTo: infoLabel.topAnchor, constant: -28), 48 | infoLabel.widthAnchor.constraint(greaterThanOrEqualTo: widthAnchor, multiplier: 0.6), 49 | infoLabel.centerXAnchor.constraint(equalTo: centerXAnchor), 50 | infoLabel.bottomAnchor.constraint(equalTo: bottomAnchor) ]) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Onboarding/Root/Page View Controller/Content/OnboardingContentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingViewController.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | // Intefrace for notifying parent view controller about current page 10 | protocol OnboardingContentPresentable: AnyObject { 11 | 12 | /// Notifying that soon the new page will be presented. 13 | /// 14 | /// - Parameter onboardingContentViewController: View Controller to be presented. 15 | func willPresentOnboardingContentViewController(_ onboardingContentViewController: OnboardingContentViewController) 16 | 17 | /// Notifying that the new page was presented. 18 | /// 19 | /// - Parameter onboardingContentViewController: View Controller that was presented. 20 | func didPresentOnboardingContentViewController(_ onboardingContentViewController: OnboardingContentViewController) 21 | } 22 | 23 | final class OnboardingContentViewController: TypedViewController { 24 | 25 | /// Delegate used to inform about current page 26 | weak var delegate: OnboardingContentPresentable? 27 | 28 | /// The index of the current view controller. 29 | private(set) var type: ContentType 30 | 31 | enum ContentType: Int { 32 | case first 33 | case second 34 | case third 35 | 36 | var title: String { 37 | switch self { 38 | case .first: 39 | return Localizable.Onboarding.Title.first 40 | case .second: 41 | return Localizable.Onboarding.Title.second 42 | case .third: 43 | return Localizable.Onboarding.Title.third 44 | } 45 | } 46 | 47 | var info: String { 48 | switch self { 49 | case .first: 50 | return Localizable.Onboarding.Description.first 51 | case .second: 52 | return Localizable.Onboarding.Description.second 53 | case .third: 54 | return Localizable.Onboarding.Description.third 55 | } 56 | } 57 | } 58 | 59 | init(type: ContentType) { 60 | self.type = type 61 | super.init(viewMaker: OnboardingContentView()) 62 | customView.setup(with: type.title, infoText: type.info) 63 | } 64 | 65 | /// SeeAlso: UIViewController 66 | override func viewWillAppear(_ animated: Bool) { 67 | super.viewWillAppear(animated) 68 | delegate?.willPresentOnboardingContentViewController(self) 69 | } 70 | 71 | override func viewDidAppear(_ animated: Bool) { 72 | super.viewDidAppear(animated) 73 | delegate?.didPresentOnboardingContentViewController(self) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Recognition/Cars list/CarListCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarListCollectionViewCell.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit.UICollectionView 8 | 9 | final class CarListCollectionViewCell: UICollectionViewCell, ViewSetupable { 10 | 11 | /// Indicates if the cell is currently displayed as primary cell 12 | var isCurrentlyPrimary = false 13 | 14 | private let topViewHeight: CGFloat = UIDevice.screenSizeBiggerThan4Inches ? 200 : 170 15 | 16 | private lazy var topView = LabeledCarImageView().layoutable() 17 | 18 | private lazy var cardView: CarListCardView = { 19 | let view = CarListCardView() 20 | view.backgroundColor = .white 21 | view.layer.cornerRadius = 10 22 | return view.layoutable() 23 | }() 24 | 25 | /// - SeeAlso: UICollectionViewCell.init 26 | override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | setupView() 29 | } 30 | 31 | /// - SeeAlso: UICollectionViewCell.init 32 | required init?(coder aDecoder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | /// Setups the cell with given car 37 | /// 38 | /// - Parameter car: Car to be used for updating the cell 39 | func setup(with car: Car) { 40 | topView.setup(with: car) 41 | cardView.setup(with: car) 42 | } 43 | 44 | /// Invalidates the charts visible on the cell 45 | /// 46 | /// - Parameter animated: Indicating if invalidation should be animated 47 | func invalidateCharts(animated: Bool) { 48 | cardView.invalidateCharts(animated: animated) 49 | } 50 | 51 | /// Clear the progress shown on charts 52 | /// 53 | /// - Parameter animated: Indicating if progress change should be animated 54 | func clearCharts(animated: Bool) { 55 | cardView.clearCharts(animated: false) 56 | } 57 | 58 | private func animateViews(toProgress progress: Double) { 59 | let offset = topViewHeight - (CGFloat(progress) * topViewHeight) 60 | cardView.transform = .init(translationX: 0, y: -offset) 61 | } 62 | 63 | /// - SeeAlso: ViewSetupable 64 | func setupViewHierarchy() { 65 | [topView, cardView].forEach(contentView.addSubview) 66 | } 67 | 68 | /// - SeeAlso: ViewSetupable 69 | func setupConstraints() { 70 | topView.constraintToSuperviewEdges(excludingAnchors: [.bottom]) 71 | cardView.constraintToSuperviewEdges(excludingAnchors: [.top]) 72 | NSLayoutConstraint.activate([ 73 | topView.heightAnchor.constraint(equalToConstant: topViewHeight), 74 | cardView.topAnchor.constraint(equalTo: topView.bottomAnchor) 75 | ]) 76 | } 77 | 78 | /// - SeeAlso: UICollectionViewCell 79 | override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { 80 | super.apply(layoutAttributes) 81 | guard let attributes = layoutAttributes as? CarListLayoutAttributes else { return } 82 | isCurrentlyPrimary = !(attributes.progress == 0) 83 | animateViews(toProgress: attributes.progress) 84 | } 85 | 86 | /// - SeeAlso: UICollectionViewCell 87 | override func prepareForReuse() { 88 | super.prepareForReuse() 89 | clearCharts(animated: false) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Recognition/Cars list/CarListLayoutAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarListLayoutAttributes.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | final class CarListLayoutAttributes: UICollectionViewLayoutAttributes { 10 | 11 | /// Progress towards the center of the screen, value between 0 and 1. 12 | var progress = 0.0 13 | 14 | /// SeeAlso: UICollectionViewLayoutAttributes 15 | override func copy(with zone: NSZone?) -> Any { 16 | guard let attributes = super.copy(with: zone) as? CarListLayoutAttributes else { return super.copy(with: zone) } 17 | attributes.progress = progress 18 | return attributes 19 | } 20 | 21 | /// SeeAlso: UICollectionViewLayoutAttributes 22 | override func isEqual(_ object: Any?) -> Bool { 23 | guard let attributes = object as? CarListLayoutAttributes else { return false } 24 | guard attributes.progress == progress else { return false } 25 | return super.isEqual(object) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Recognition/Cars list/CarsListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarsListView.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | final class CarsListView: View, ViewSetupable { 10 | 11 | /// Cars list collectiom view 12 | lazy var collectionView: UICollectionView = { 13 | let view = UICollectionView(frame: .zero, collectionViewLayout: CarListFlowLayout()) 14 | view.backgroundColor = .clear 15 | view.showsHorizontalScrollIndicator = false 16 | view.accessibilityIdentifier = "carsList/collectionView/cars" 17 | return view.layoutable() 18 | }() 19 | 20 | /// Recognize button visible at the bottom 21 | lazy var recognizeButton: UIButton = { 22 | let view = UIButton(type: .system) 23 | view.setImage(#imageLiteral(resourceName: "button-scan-primary"), for: .normal) 24 | view.layer.shadowOpacity = 0.4 25 | view.layer.shadowColor = UIColor.crShadowOrange.withAlphaComponent(0.4).cgColor 26 | view.layer.shadowOffset = CGSize(width: 0, height: 12) 27 | view.accessibilityIdentifier = "carsList/button/recognize" 28 | return view.layoutable() 29 | }() 30 | 31 | /// Custom navigation bar added at the top 32 | lazy var topView = CarListNavigationBar(currentNumber: 0, maximumNumber: availableCars).layoutable() 33 | 34 | private let discoveredCar: Car? 35 | 36 | private let availableCars: Int 37 | 38 | /// Initializes the view with given car 39 | /// 40 | /// - Parameters: 41 | /// - discoveredCar: Car parameter that was displayed on the card when opening this view 42 | /// - availableCars: Number of available cars 43 | init(discoveredCar: Car? = nil, availableCars: Int) { 44 | self.discoveredCar = discoveredCar 45 | self.availableCars = availableCars 46 | super.init() 47 | } 48 | 49 | /// - SeeAlso: ViewSetupable 50 | func setupViewHierarchy() { 51 | [topView, collectionView, recognizeButton].forEach(addSubview) 52 | } 53 | 54 | /// - SeeAlso: ViewSetupable 55 | func setupConstraints() { 56 | topView.constraintToSuperviewLayoutGuide(excludingAnchors: [.bottom]) 57 | collectionView.constraintToSuperviewEdges(excludingAnchors: [.top, .bottom]) 58 | recognizeButton.constraintToConstant(.init(width: 80, height: 80)) 59 | NSLayoutConstraint.activate([ 60 | topView.heightAnchor.constraint(equalToConstant: 70), 61 | collectionView.topAnchor.constraint(equalTo: topView.bottomAnchor, 62 | constant: UIDevice.screenSizeBiggerThan4Inches ? 20 : 0), 63 | collectionView.bottomAnchor.constraint(equalTo: recognizeButton.topAnchor, constant: -20), 64 | recognizeButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -8), 65 | recognizeButton.centerXAnchor.constraint(equalTo: centerXAnchor) 66 | ]) 67 | } 68 | 69 | /// - SeeAlso: ViewSetupable 70 | func setupProperties() { 71 | backgroundColor = .crBackgroundGray 72 | topView.backButton.isHidden = discoveredCar == nil 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Recognition/Root/AugmentedRealityViewController/AugmentedRealityView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AugmentedRealityView.swift 3 | // CarLens 4 | // 5 | 6 | import UIKit 7 | import ARKit 8 | 9 | final class AugmentedRealityView: View, ViewSetupable { 10 | 11 | /// View with camera preview 12 | lazy var previewView: ARSKView = { 13 | let view = ARSKView() 14 | view.presentScene(sceneView) 15 | return view.layoutable() 16 | }() 17 | 18 | /// Blur effect view presented when camera is not ready to show content 19 | let blurEffectView: UIVisualEffectView = { 20 | let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light) 21 | let view = UIVisualEffectView(effect: blurEffect) 22 | return view.layoutable() 23 | }() 24 | 25 | /// Augmented Reality scene presented on the camera preview 26 | lazy var sceneView = CarScene() 27 | 28 | /// View with animated bracket showing detection progress 29 | lazy var detectionViewfinderView = DetectionViewfinderView().layoutable() 30 | 31 | /// Dimming black view covering whole camera screen 32 | lazy var dimmView: UIView = { 33 | let view = UIView() 34 | view.backgroundColor = UIColor.black.withAlphaComponent(0.5) 35 | return view.layoutable() 36 | }() 37 | 38 | /// - SeeAlso: ViewSetupable 39 | func setupViewHierarchy() { 40 | [previewView, dimmView, detectionViewfinderView, blurEffectView].forEach(addSubview) 41 | } 42 | 43 | /// - SeeAlso: ViewSetupable 44 | func setupConstraints() { 45 | dimmView.constraintToSuperviewEdges() 46 | previewView.constraintToSuperviewEdges() 47 | detectionViewfinderView.constraintCenterToSuperview(withConstant: .init(x: 0, y: -50)) 48 | blurEffectView.constraintToSuperviewEdges() 49 | } 50 | 51 | /// - SeeAlso: ViewSetupable 52 | func setupProperties() { 53 | [dimmView, detectionViewfinderView].forEach { $0.isUserInteractionEnabled = false } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Recognition/Root/CameraAccessViewController/CameraAccessView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraAccessView.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | final class CameraAccessView: View, ViewSetupable { 10 | 11 | /// Struct with view's dimensions 12 | enum Dimensions { 13 | static let topOfffset = UIScreen.main.bounds.height * 0.25 14 | static let informationFontSize: CGFloat = 16 15 | } 16 | 17 | /// Cars list button in the left bottom corner 18 | let carsListButton: UIButton = { 19 | let view = UIButton(type: .system) 20 | view.setImage(#imageLiteral(resourceName: "button-car-list-gray"), for: .normal) 21 | return view.layoutable() 22 | }() 23 | 24 | /// Access button in the bottom center 25 | let accessButton: UIButton = { 26 | let view = UIButton(type: .system) 27 | view.setTitle(Localizable.CameraAccess.accessButton, for: .normal) 28 | view.setTitleColor(UIColor(hex: 0xFF6163), for: .normal) 29 | return view.layoutable() 30 | }() 31 | 32 | private let cameraImageView: UIImageView = { 33 | let view = UIImageView() 34 | view.image = #imageLiteral(resourceName: "camera-image") 35 | return view.layoutable() 36 | }() 37 | 38 | private let informationLabel: UILabel = { 39 | let view = UILabel() 40 | view.text = Localizable.CameraAccess.information 41 | view.font = UIFont.systemFont(ofSize: Dimensions.informationFontSize) 42 | view.textAlignment = .center 43 | view.numberOfLines = 0 44 | view.textColor = .white 45 | return view.layoutable() 46 | }() 47 | 48 | /// - SeeAlso: ViewSetupable 49 | func setupViewHierarchy() { 50 | [cameraImageView, informationLabel, accessButton, carsListButton].forEach(addSubview) 51 | } 52 | 53 | /// - SeeAlso: ViewSetupable 54 | func setupConstraints() { 55 | cameraImageView.constraintToSuperviewEdges(excludingAnchors: [.bottom], 56 | withInsets: .init(top: Dimensions.topOfffset, left: 44, 57 | bottom: 0, right: 44)) 58 | 59 | NSLayoutConstraint.activate([ 60 | informationLabel.topAnchor.constraint(equalTo: cameraImageView.bottomAnchor, constant: 43), 61 | accessButton.topAnchor.constraint(equalTo: informationLabel.bottomAnchor, constant: 38), 62 | carsListButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 32), 63 | carsListButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -16) 64 | ]) 65 | informationLabel.constraintToSuperviewEdges(excludingAnchors: [.top, .bottom], 66 | withInsets: .init(top: 0, left: 88, bottom: 0, right: 88)) 67 | carsListButton.constraintToConstant(.init(width: 45, height: 45)) 68 | accessButton.constraintToSuperviewEdges(excludingAnchors: [.top, .bottom], 69 | withInsets: .init(top: 0, left: 100, bottom: 0, right: 100)) 70 | } 71 | 72 | /// - SeeAlso: ViewSetupable 73 | func setupProperties() { 74 | backgroundColor = UIColor(hex: 0x2F3031) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CarLens/Source Files/Modules/Recognition/Root/CameraAccessViewController/CameraAccessViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraAccessViewController.swift 3 | // CarLens 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | final class CameraAccessViewController: TypedViewController { 10 | 11 | /// Enum describing events that can be triggered by this controller 12 | /// 13 | /// - didTriggerShowCarsList: Send when user should see the list of available cars passing car 14 | /// if any is displayed be the bottom sheet. 15 | /// - didTriggerRequestAccess: Send when user should open Settings with camera access. 16 | enum Event { 17 | case didTriggerShowCarsList 18 | case didTriggerRequestAccess 19 | } 20 | 21 | /// - SeeAlso: 'UIViewController' 22 | override var preferredStatusBarStyle: UIStatusBarStyle { 23 | return .lightContent 24 | } 25 | 26 | /// Callback with triggered event 27 | var eventTriggered: ((Event) -> Void)? 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | setupCallbacks() 32 | } 33 | 34 | private func setupCallbacks() { 35 | customView.carsListButton.addTarget(self, action: #selector(carsListButtonTapAction), for: .touchUpInside) 36 | customView.accessButton.addTarget(self, action: #selector(accessButtonTapAction), for: .touchUpInside) 37 | } 38 | 39 | @objc private func carsListButtonTapAction() { 40 | eventTriggered?(.didTriggerShowCarsList) 41 | } 42 | 43 | @objc private func accessButtonTapAction() { 44 | eventTriggered?(.didTriggerRequestAccess) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CarLens/Supporting Files/Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Bridging-Header.h 3 | // CarLens 4 | // 5 | 6 | 7 | #ifndef Bridging_Header_h 8 | #define Bridging_Header_h 9 | 10 | #import "CarLensKeys.h" 11 | 12 | #endif /* Bridging_Header_h */ 13 | -------------------------------------------------------------------------------- /CarLens/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | CarLens 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(PRODUCT_BUNDLE_VERSION_STRING) 21 | CFBundleVersion 22 | $(PRODUCT_BUNDLE_VERSION) 23 | LSApplicationCategoryType 24 | 25 | LSRequiresIPhoneOS 26 | 27 | NSCameraUsageDescription 28 | Camera is used for car detection 29 | NSPhotoLibraryUsageDescription 30 | This app requires access to the photo library 31 | UIAppFonts 32 | 33 | BLOKKNeue-Regular.ttf 34 | gliscor-gothic.ttf 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIRequiredDeviceCapabilities 39 | 40 | arkit 41 | armv7 42 | 43 | UIRequiresFullScreen 44 | 45 | UIStatusBarHidden 46 | 47 | UIStatusBarStyle 48 | UIStatusBarStyleDefault 49 | UISupportedInterfaceOrientations 50 | 51 | UIInterfaceOrientationPortrait 52 | 53 | UISupportedInterfaceOrientations~ipad 54 | 55 | UIInterfaceOrientationPortrait 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /CarLensTests/Helpers/JSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSON.swift 3 | // CarLensTests 4 | // 5 | 6 | final class JSON { 7 | static func readFile(name: String) -> Data? { 8 | let bundle = Bundle(for: self) 9 | return bundle.url(forResource: name, withExtension: ".json") 10 | .flatMap { try? Data(contentsOf: $0) } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CarLensTests/Mocks/ModelsFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelsFactory.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | 8 | extension Car { 9 | static func make() -> Car? { 10 | guard let data = JSON.readFile(name: "MockedCar") else { return nil } 11 | return try? JSONDecoder().decode(Car.self, from: data) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CarLensTests/Mocks/UIApplicationMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplicationMock.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import UIKit 8 | 9 | final class UIApplicationMock: URLOpener { 10 | 11 | var urlString: String = "" 12 | 13 | var canOpen: Bool = true 14 | 15 | func canOpenURL(_ url: URL) -> Bool { 16 | return canOpen 17 | } 18 | 19 | func open(_ url: URL, 20 | options: [UIApplication.OpenExternalURLOptionsKey: Any] = [:], 21 | completionHandler completion: ((Bool) -> Void)? = nil) { 22 | if canOpen { 23 | self.urlString = url.absoluteString 24 | completion?(true) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CarLensTests/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CarLensTests/Supporting Files/MockedCar.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "FordFiesta", 3 | "brand": "Ford", 4 | "model": "Fiesta", 5 | "description": "Ford Fiesta has been marketed by Ford since 1976. Ford has sold over 16 million Fiestas since then, making it a best-seller.", 6 | "stars": 1, 7 | "acceleration_mph": 12.7, 8 | "acceleration_kph": 13.2, 9 | "speed_mph": 118, 10 | "speed_kph": 190, 11 | "power": 70, 12 | "engine": 1000, 13 | "brand_logo_image": "Ford", 14 | "brand_logo_image_locked": "Ford_locked", 15 | "image": "FordFiesta", 16 | "image_locked": "FordFiesta_locked" 17 | } 18 | -------------------------------------------------------------------------------- /CarLensTests/Supporting Files/MockedCars.json: -------------------------------------------------------------------------------- 1 | { 2 | "cars": [ 3 | { 4 | "id": "ToyotaPrius", 5 | "brand": "Toyota", 6 | "model": "Prius", 7 | "description": "The Toyota Corolla is a line of cars manufactured by Toyota. Introduced in 1966, it has been the best-selling car worldwide since 1974.", 8 | "stars": 2, 9 | "acceleration_mph": 10.1, 10 | "acceleration_kph": 10.5, 11 | "speed_mph": 130, 12 | "speed_kph": 209, 13 | "power": 90, 14 | "engine": 1330, 15 | "brand_logo_image": "Toyota", 16 | "brand_logo_image_locked": "Toyota_locked", 17 | "image": "ToyotaCorolla", 18 | "image_locked": "ToyotaCorolla_locked" 19 | }, 20 | { 21 | "id": "HondaCanada", 22 | "brand": "Honda", 23 | "model": "Canada", 24 | "description": "Originally a subcompact, the Civic has gone through several generational changes, becoming both larger and more upmarket.", 25 | "stars": 2, 26 | "acceleration_mph": 10.0, 27 | "acceleration_kph": 10.4, 28 | "speed_mph": 135, 29 | "speed_kph": 217, 30 | "power": 120, 31 | "engine": 1000, 32 | "brand_logo_image": "Honda", 33 | "brand_logo_image_locked": "Honda_locked", 34 | "image": "HondaCivic", 35 | "image_locked": "HondaCivic_locked" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Common/FormattersTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormattersTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class FormattersTests: XCTestCase { 10 | 11 | func testNumberFormatterFor10Percent() { 12 | let givenValue = Float(0.1) 13 | let percentageFormattedString = CRNumberFormatter.percentageFormatted(givenValue) 14 | XCTAssertEqual(percentageFormattedString, 15 | "\(Int(givenValue * 100))%", 16 | "Should return percentage formatted string from float.") 17 | } 18 | 19 | func testTimeFormatterFor50Miliseconds() { 20 | let timeInterval = 0.05 21 | let formattedTime = CRTimeFormatter.intervalMilisecondsFormatted(timeInterval) 22 | XCTAssertEqual(formattedTime, "50 ms", "Should return miliseconds formatted string from time interval.") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Common/SystemMetricsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemMetricsTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class SystemMetricsTests: XCTestCase { 10 | 11 | var sut: SystemMetrics! 12 | 13 | func testSpeedMetricsForUSLocale() { 14 | let locale = Locale(identifier: "en_US") 15 | sut = SystemMetrics(with: locale) 16 | XCTAssertEqual(sut.speedType, SpeedMetricsType.mph, "Should return miles for US locale") 17 | } 18 | 19 | func testSpeedMetricsForPolishLocale() { 20 | let locale = Locale(identifier: "pl_PL") 21 | sut = SystemMetrics(with: locale) 22 | XCTAssertEqual(sut.speedType, SpeedMetricsType.kph, "Should return kilometers for Polish locale") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Extensions/UIFontExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFontExtensionTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class UIFontExtensionTests: XCTestCase { 10 | 11 | private enum MockedParameters { 12 | static let fontSize: CGFloat = 12 13 | static let blokkNeueFontName = "BLOKKNeue-Regular" 14 | static let gliscorGothicFontName = "GliscorGothic" 15 | } 16 | 17 | var sut: UIFont! 18 | 19 | func testGliscorGothicFont() { 20 | sut = UIFont.gliscorGothicFont(ofSize: MockedParameters.fontSize) 21 | XCTAssertEqual(sut.pointSize, MockedParameters.fontSize, "Font size should be \(MockedParameters.fontSize)") 22 | XCTAssertEqual(sut.fontName, 23 | MockedParameters.gliscorGothicFontName, 24 | "Font name should be \(MockedParameters.gliscorGothicFontName)") 25 | } 26 | 27 | func testBlokkNeueFont() { 28 | sut = UIFont.blokkNeueFont(ofSize: MockedParameters.fontSize) 29 | XCTAssertEqual(sut.pointSize, MockedParameters.fontSize, "Font size should be '\(MockedParameters.fontSize)'") 30 | XCTAssertEqual(sut.fontName, 31 | MockedParameters.blokkNeueFontName, 32 | "Font name should be \(MockedParameters.blokkNeueFontName)") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Extensions/UIStackViewExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIStackViewExtensionTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class UIStackViewExtensionTests: XCTestCase { 10 | 11 | private enum MockedParameters { 12 | static let arrangedSubviews = [UIView(), UIButton(), UILabel()] 13 | static let spacing: CGFloat = 10 14 | static let distribution: UIStackView.Distribution = .fillEqually 15 | static let axis: NSLayoutConstraint.Axis = .horizontal 16 | } 17 | 18 | var sut: UIStackView! 19 | 20 | func testStackViewInitialization() { 21 | sut = .make( 22 | axis: MockedParameters.axis, 23 | with: MockedParameters.arrangedSubviews, 24 | spacing: MockedParameters.spacing, 25 | distribution: MockedParameters.distribution 26 | ) 27 | XCTAssertNotNil(sut, "UIStackView shouldn't be nil") 28 | XCTAssertEqual(sut.axis, MockedParameters.axis, "Axis should be equal to \(MockedParameters.axis)") 29 | XCTAssertEqual(sut.arrangedSubviews, 30 | MockedParameters.arrangedSubviews, 31 | "Arranged subviews should be equal to \(MockedParameters.arrangedSubviews)") 32 | XCTAssertEqual(sut.spacing, MockedParameters.spacing, "Spacing should be equal to \(MockedParameters.spacing)") 33 | XCTAssertEqual(sut.distribution, 34 | MockedParameters.distribution, 35 | "Distributiion should be equal to \(MockedParameters.distribution)") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Models/CarARConfigurationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarARConfigurationTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class CarARConfigurationTests: XCTestCase { 10 | 11 | private enum DesiredParameters { 12 | static let pointForHitTest = CGPoint(x: 0.5, y: 0.5) 13 | static let neededConfidenceToPinLabel = 0.96 14 | static let normalizationCount = 10 15 | static let nodeShift = NodeShift(depth: 0, elevation: 0) 16 | static let minimumDistanceFromDevice: CGFloat = 0.1 17 | static let minimumDistanceBetweenNodes: Float = 0.2 18 | static let maximumDistanceFromDevice: CGFloat = 2 19 | } 20 | 21 | var sut: CarARConfiguration! 22 | 23 | override func setUp() { 24 | super.setUp() 25 | sut = CarARConfiguration() 26 | } 27 | 28 | override func tearDown() { 29 | super.tearDown() 30 | sut = nil 31 | } 32 | 33 | func testConfigurationParameters() { 34 | XCTAssertEqual(sut.pointForHitTest, 35 | DesiredParameters.pointForHitTest, 36 | "Point for hit test should be equal to \(DesiredParameters.pointForHitTest)") 37 | } 38 | 39 | func testEnvironmentVariables() { 40 | #if ENV_TESTS 41 | XCTAssertEqual(sut.nodeShift, 42 | DesiredParameters.nodeShift, 43 | "Node shift should be equal to \(DesiredParameters.nodeShift)") 44 | XCTAssertEqual(sut.minimumDistanceFromDevice, 45 | DesiredParameters.minimumDistanceFromDevice, 46 | "Minimal distance from device should be equal to" + 47 | "\(DesiredParameters.minimumDistanceFromDevice)") 48 | XCTAssertEqual(sut.minimumDistanceBetweenNodes, 49 | DesiredParameters.minimumDistanceBetweenNodes, 50 | "Minimal distance between nodes should be equal to" + 51 | "\(DesiredParameters.minimumDistanceBetweenNodes)") 52 | XCTAssertEqual(sut.maximumDistanceFromDevice, 53 | DesiredParameters.maximumDistanceFromDevice, 54 | "Maximal distance from device should be equal to" + 55 | "\(DesiredParameters.maximumDistanceFromDevice)") 56 | #endif 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Models/CarSpecificationChartConfigurationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarSpecificationChartConfigurationTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class CarSpecificationChartConfigurationTests: XCTestCase { 10 | 11 | private enum DesiredParameters { 12 | static let referenceHorsePower = 320 13 | static let referenceSpeedInMiles = 200 14 | static let referenceSpeedInKilometers = 322 15 | static let referenceEngineVolume = 2000 16 | static let referenceMaxAccelerate = 20.0 17 | static let referenceMinAccelerate = 2.9 18 | } 19 | 20 | var sut: CarSpecificationChartConfiguration! 21 | 22 | override func setUp() { 23 | super.setUp() 24 | sut = CarSpecificationChartConfiguration() 25 | } 26 | 27 | override func tearDown() { 28 | super.tearDown() 29 | sut = nil 30 | } 31 | 32 | func testCarSpecificationParameters() { 33 | XCTAssertEqual(sut.referenceHorsePower, 34 | DesiredParameters.referenceHorsePower, 35 | "Reference horse power should be equal to \(DesiredParameters.referenceHorsePower)") 36 | XCTAssertEqual(sut.referenceEngineVolume, 37 | DesiredParameters.referenceEngineVolume, 38 | "Reference engine volume should be equal to \(DesiredParameters.referenceEngineVolume)") 39 | XCTAssertEqual(sut.referenceMaxAccelerate, 40 | DesiredParameters.referenceMaxAccelerate, 41 | "Reference maximum accelerate should be equal to \(DesiredParameters.referenceMaxAccelerate)") 42 | XCTAssertEqual(sut.referenceMinAccelerate, 43 | DesiredParameters.referenceMinAccelerate, 44 | "Reference minimum accelerate should be equal to \(DesiredParameters.referenceMinAccelerate)") 45 | } 46 | 47 | func testReferenceSpeedForMiles() { 48 | // given 49 | let speedType = SpeedMetricsType.mph 50 | // when 51 | sut = CarSpecificationChartConfiguration(with: speedType) 52 | // then 53 | XCTAssertEqual(sut.referenceSpeed, 54 | DesiredParameters.referenceSpeedInMiles, 55 | "Reference speed for miles should be equal to \(DesiredParameters.referenceSpeedInMiles)") 56 | } 57 | 58 | func testReferenceSpeedForKilometers() { 59 | // given 60 | let speedType = SpeedMetricsType.kph 61 | // when 62 | sut = CarSpecificationChartConfiguration(with: speedType) 63 | // then 64 | XCTAssertEqual(sut.referenceSpeed, 65 | DesiredParameters.referenceSpeedInKilometers, 66 | "Reference speed for kilometers should be equal to" + 67 | String(DesiredParameters.referenceSpeedInKilometers)) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Models/CarTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class CarTests: XCTestCase { 10 | 11 | private enum DesiredParameters { 12 | static let makeName = "Ford" 13 | static let modelName = "Fiesta" 14 | static let engine = 1000 15 | static let power = 70 16 | static let id = "FordFiesta" 17 | static let stars = 1 18 | static let image = CarImage(unlocked: #imageLiteral(resourceName: "FordFiesta"), locked: #imageLiteral(resourceName: "FordFiesta_locked"), logoUnlocked: #imageLiteral(resourceName: "Ford"), logoLocked: #imageLiteral(resourceName: "Ford_locked")) 19 | static let description = "Ford Fiesta has been marketed by Ford since 1976. " + 20 | "Ford has sold over 16 million Fiestas since then, making it a best-seller." 21 | static let isDiscovered = false 22 | } 23 | 24 | var sut: Car! 25 | 26 | override func setUp() { 27 | super.setUp() 28 | sut = Car.make() 29 | } 30 | 31 | override func tearDown() { 32 | super.tearDown() 33 | sut = nil 34 | } 35 | 36 | func testCarInitialization() { 37 | XCTAssertNotNil(sut, "Car shouldn't be nil") 38 | } 39 | 40 | func testCarProperties() { 41 | XCTAssertEqual(sut.make, 42 | DesiredParameters.makeName, 43 | "Car's make should be equal to \(DesiredParameters.makeName)") 44 | XCTAssertEqual(sut.model, 45 | DesiredParameters.modelName, 46 | "Car's model should be equal to \(DesiredParameters.modelName)") 47 | XCTAssertEqual(sut.engine, 48 | DesiredParameters.engine, 49 | "Car's engine should be equal to \(DesiredParameters.engine)") 50 | XCTAssertEqual(sut.power, 51 | DesiredParameters.power, 52 | "Car's power should be equal to \(DesiredParameters.power)") 53 | XCTAssertEqual(sut.id, 54 | DesiredParameters.id, 55 | "Car's id should be equal to \(DesiredParameters.id)") 56 | XCTAssertEqual(sut.stars, 57 | DesiredParameters.stars, 58 | "Car's stars should be equal to \(DesiredParameters.stars)") 59 | XCTAssertEqual(sut.image, 60 | DesiredParameters.image, 61 | "Car's image should be equal to \(DesiredParameters.image)") 62 | XCTAssertEqual(sut.description, 63 | DesiredParameters.description, 64 | "Car's description should be equal to \(DesiredParameters.description)") 65 | XCTAssertEqual(sut.isDiscovered, 66 | DesiredParameters.isDiscovered, 67 | "Car's idDiscovered property should be equal to \(DesiredParameters.isDiscovered)") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Models/RecognitionResultTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecognitionResultTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class RecognitionResultTests: XCTestCase { 10 | 11 | enum Labels { 12 | static let otherCar = "other_car" 13 | static let notCar = "0" 14 | static let unknown = "unknown label" 15 | } 16 | 17 | var sut: RecognitionResult! 18 | 19 | var localCarsDataService: LocalCarsDataService! 20 | 21 | var carsDataService: CarsDataService! 22 | 23 | override func setUp() { 24 | super.setUp() 25 | let path = Bundle(for: type(of: self)).path(forResource: "MockedCars", ofType: "json") 26 | localCarsDataService = LocalCarsDataService(with: path) 27 | carsDataService = CarsDataService(localDataService: localCarsDataService) 28 | } 29 | 30 | override func tearDown() { 31 | sut = nil 32 | localCarsDataService = nil 33 | carsDataService = nil 34 | super.tearDown() 35 | } 36 | 37 | func testInitizationForCar() { 38 | guard let firstCar = localCarsDataService.cars.first else { 39 | XCTFail("Local car data base should not be empty.") 40 | return 41 | } 42 | testResult(for: firstCar.id) 43 | guard let result = sut else { return } 44 | XCTAssertEqual(result.recognition, 45 | RecognitionResult.Recognition.car(firstCar), 46 | "Recognition Result should return car for its label.") 47 | } 48 | 49 | func testInitializationForOtherCar() { 50 | testResult(for: Labels.otherCar) 51 | guard let result = sut else { return } 52 | XCTAssertEqual(result.recognition, 53 | RecognitionResult.Recognition.otherCar, 54 | "Recognition Result should return otherCar enum type for label that's not in local data base.") 55 | } 56 | 57 | func testInitializationForNotCar() { 58 | testResult(for: Labels.notCar) 59 | guard let result = sut else { return } 60 | XCTAssertEqual(result.recognition, 61 | RecognitionResult.Recognition.notCar, 62 | "Recognition Result should return notCar enum type for label 'not car'.") 63 | } 64 | 65 | func testInitializationForUnknownLabel() { 66 | sut = RecognitionResult(label: Labels.unknown, confidence: 0.9, carsDataService: CarsDataService()) 67 | XCTAssertNil(sut, "Recognition Result should fail to initialize for an unknown label.") 68 | } 69 | 70 | private func testResult(for label: String) { 71 | sut = RecognitionResult(label: label, 72 | confidence: 0.9, 73 | carsDataService: CarsDataService(localDataService: localCarsDataService)) 74 | XCTAssertNotNil(sut, "Recognition Result should not be empty.") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Services/CarDatabaseServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarDatabaseServiceTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class CarsDatabaseServiceTests: XCTestCase { 10 | 11 | var sut: CarsDatabaseService! 12 | 13 | var car: Car! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | sut = CarsDatabaseService() 18 | car = Car.make() 19 | } 20 | 21 | override func tearDown() { 22 | super.tearDown() 23 | car = nil 24 | sut = nil 25 | } 26 | 27 | func testCarAsDiscovered() { 28 | XCTAssertNotNil(car, "Car should't be nil") 29 | guard var car = car else { return } 30 | sut.mark(car: car, asDiscovered: true) 31 | sut.mapDiscoveredParameter(car: &car) 32 | XCTAssertTrue(car.isDiscovered, "Car should be marked as discovered") 33 | } 34 | 35 | func testCarAsNotDiscovered() { 36 | XCTAssertNotNil(car, "Car should't be nil") 37 | guard var car = car else { return } 38 | sut.mark(car: car, asDiscovered: false) 39 | sut.mapDiscoveredParameter(car: &car) 40 | XCTAssertFalse(car.isDiscovered, "Car should be marked as undiscovered") 41 | } 42 | 43 | func testMultipleCarsAsDiscovered() { 44 | XCTAssertNotNil(car, "Car should't be nil") 45 | guard let car = car else { return } 46 | sut.mark(car: car, asDiscovered: true) 47 | var cars = [car, car, car, car] 48 | sut.mapDiscoveredParameter(for: &cars) 49 | for mappedCar in cars { 50 | XCTAssertTrue(mappedCar.isDiscovered, "Car should be marked as discovered") 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Services/CarsDataServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarsDataServiceTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class CarsDataServiceTests: XCTestCase { 10 | 11 | var sut: CarsDataService! 12 | 13 | var localCarsDataService: LocalCarsDataService! 14 | 15 | var databaseService: CarsDatabaseService! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | let path = Bundle(for: type(of: self)).path(forResource: "MockedCars", ofType: "json") 20 | localCarsDataService = LocalCarsDataService(with: path) 21 | databaseService = CarsDatabaseService() 22 | sut = CarsDataService(localDataService: localCarsDataService, databaseService: databaseService) 23 | } 24 | 25 | override func tearDown() { 26 | localCarsDataService = nil 27 | databaseService = nil 28 | sut = nil 29 | super.tearDown() 30 | } 31 | 32 | func testMapClassifierForLabel() { 33 | // given 34 | guard let car = testFirstCar() else { return } 35 | // when 36 | let result = sut.map(classifierLabel: car.id) 37 | // then 38 | XCTAssertEqual(result, car, "Cars Data Service should return car object for its label.") 39 | } 40 | 41 | func testMapClassifierForUnknownLabel() { 42 | // given 43 | let unknownLabel = "unknown" 44 | // when 45 | let result = sut.map(classifierLabel: unknownLabel) 46 | // then 47 | XCTAssertNil(result, "Cars Data Service should return nil for unknown label.") 48 | } 49 | 50 | func testAvailableCars() { 51 | // given 52 | var expectedCars = localCarsDataService.cars 53 | guard var firstCar = expectedCars.first else { 54 | XCTFail("Cars local data for tests should not be empty.") 55 | return 56 | } 57 | sut.mark(car: firstCar, asDiscovered: true) 58 | firstCar.isDiscovered = true 59 | expectedCars[0] = firstCar 60 | // when 61 | let result = sut.getAvailableCars() 62 | // then 63 | XCTAssertEqual(result, 64 | expectedCars, 65 | "Cars received from the Cars Data Service should be the same as in the local data.") 66 | } 67 | 68 | func testNumberOfCars() { 69 | XCTAssertEqual(sut.getNumberOfCars(), 70 | localCarsDataService.cars.count, 71 | "Number of cars received from the Cars Data Service should be the same as in the local data.") 72 | } 73 | 74 | func testMarkCar() { 75 | // given 76 | guard let car = testFirstCar() else { return } 77 | // when 78 | sut.mark(car: car, asDiscovered: true) 79 | guard let markedCar = sut.getAvailableCars().first(where: { $0.id == car.id }) else { 80 | XCTFail("Updated car after marking should not be nil.") 81 | return 82 | } 83 | // then 84 | XCTAssertEqual(car, markedCar, "Car should be marked as discovered.") 85 | } 86 | 87 | private func testFirstCar() -> Car? { 88 | let firstCar = localCarsDataService.cars.first 89 | XCTAssertNotNil(firstCar, "First car in local data base should not be empty.") 90 | return firstCar 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Services/LocalCarsDataServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalCarsDataServiceTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class LocalCarsDataServiceTests: XCTestCase { 10 | 11 | var sut: LocalCarsDataService! 12 | 13 | override func setUp() { 14 | super.setUp() 15 | let path = Bundle(for: type(of: self)).path(forResource: "MockedCars", ofType: "json") 16 | sut = LocalCarsDataService(with: path) 17 | } 18 | 19 | override func tearDown() { 20 | sut = nil 21 | super.tearDown() 22 | } 23 | 24 | func testCarsInitialization() { 25 | XCTAssert(!sut.cars.isEmpty, "Local Cars Data Service should initialize the not empty cars array from JSON.") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CarLensTests/Tests/Services/SearchServiceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchServiceTests.swift 3 | // CarLensTests 4 | // 5 | 6 | @testable import CarLens 7 | import XCTest 8 | 9 | final class SearchServiceTests: XCTestCase { 10 | 11 | var sut: SearchService! 12 | 13 | var applicationMock: UIApplicationMock! 14 | 15 | var urlOpenerService: URLOpenerService! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | applicationMock = UIApplicationMock() 20 | urlOpenerService = URLOpenerService(with: applicationMock) 21 | sut = SearchService(with: urlOpenerService) 22 | } 23 | 24 | override func tearDown() { 25 | super.tearDown() 26 | applicationMock = nil 27 | urlOpenerService = nil 28 | sut = nil 29 | } 30 | 31 | func testSearchForGoogle() { 32 | // given 33 | let service = SearchService.Service.google 34 | guard let car = Car.make() else { 35 | XCTFail("Local car shouldn't be nil") 36 | return 37 | } 38 | let waitForURLToOpen = expectation(description: "Open a search service url.") 39 | // when 40 | sut.search(service, for: car, completion: { opened in 41 | waitForURLToOpen.fulfill() 42 | // then 43 | XCTAssert(self.applicationMock.urlString == "https://www.google.com/search?q=\(car.make)%20\(car.model)", 44 | "Should have generated a google search service url.") 45 | XCTAssert(opened == true, "Should have opened a search service url.") 46 | }) 47 | waitForExpectations(timeout: 0.1, handler: nil) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/diff_testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/diff_testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/diff_testHondaCivicScreenLook_initial_view_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/diff_testHondaCivicScreenLook_initial_view_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/diff_testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/diff_testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/failed_testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/failed_testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/failed_testHondaCivicScreenLook_initial_view_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/failed_testHondaCivicScreenLook_initial_view_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/failed_testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/failed_testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testFordFiestaScreenLook_after_2_swipe_left_1125x2436@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testFordFiestaScreenLook_after_2_swipe_left_1125x2436@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testHondaCivicScreenLook_initial_view_1125x2436@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testHondaCivicScreenLook_initial_view_1125x2436@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testHondaCivicScreenLook_initial_view_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testHondaCivicScreenLook_initial_view_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testNissanQashqaiScreenLook_after_3_swipe_left_1125x2436@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testNissanQashqaiScreenLook_after_3_swipe_left_1125x2436@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testToyotaCorollaScreenLook_after_1_swipe_left_1125x2436@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testToyotaCorollaScreenLook_after_1_swipe_left_1125x2436@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testVolkswagenPassatScreenLook_after_4_swipe_left_1125x2436@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/FailureDiffs/CarLensUITests.CarsListTestCase/reference_testVolkswagenPassatScreenLook_after_4_swipe_left_1125x2436@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testFordFiestaScreenLook_after_1_swipe_left_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testFordFiestaScreenLook_after_1_swipe_left_750x1334@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testFordFiestaScreenLook_after_1_swipe_left_750x1334@2x.png -------------------------------------------------------------------------------- /CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testHondaCivicScreenLook_initial_view_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testHondaCivicScreenLook_initial_view_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testHondaCivicScreenLook_initial_view_750x1334@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testHondaCivicScreenLook_initial_view_750x1334@2x.png -------------------------------------------------------------------------------- /CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testNissanQashqaiScreenLook_after_2_swipe_left_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testNissanQashqaiScreenLook_after_2_swipe_left_750x1334@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testNissanQashqaiScreenLook_after_2_swipe_left_750x1334@2x.png -------------------------------------------------------------------------------- /CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testVolkswagenPassatScreenLook_after_3_swipe_left_1242x2208@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testVolkswagenPassatScreenLook_after_3_swipe_left_1242x2208@3x.png -------------------------------------------------------------------------------- /CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testVolkswagenPassatScreenLook_after_3_swipe_left_750x1334@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netguru/CarLens-iOS/b2bf91f5118e7c81bd71abdeacb37380d3592378/CarLensTestsImages/ReferenceImages_64/CarLensUITests.CarsListTestCase/testVolkswagenPassatScreenLook_after_3_swipe_left_750x1334@2x.png -------------------------------------------------------------------------------- /CarLensUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CarLensUITests/Screen/CarsList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarsList.swift 3 | // CarLensUITests 4 | // 5 | 6 | 7 | import Foundation 8 | import XCTest 9 | 10 | final class CarsList: Screen { 11 | 12 | private lazy var recognizeButton = app.buttons["carsList/button/recognize"] 13 | private lazy var ovalProgressView = app.otherElements["carsList/view/ovalProgress"] 14 | private lazy var carsCollectionView = app.collectionViews["carsList/collectionView/cars"] 15 | private lazy var carsListNavigationBar = app.otherElements["carsList/navigationBar/main"] 16 | 17 | override var viewIdentifier: String { 18 | return "carsList/view/main" 19 | } 20 | 21 | var isProgressViewVisible: Bool { 22 | return exists(ovalProgressView) 23 | } 24 | 25 | var isCarsCollectionViewVisible: Bool { 26 | return exists(carsCollectionView) 27 | } 28 | 29 | var isCarsListNavigationBarVisible: Bool { 30 | return exists(carsListNavigationBar) 31 | } 32 | 33 | @discardableResult 34 | func goToRecognitionView() -> Screen { 35 | recognizeButton.tap() 36 | return self 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CarLensUITests/Screen/Onboarding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Onboarding.swift 3 | // CarLensUITests 4 | // 5 | 6 | 7 | import Foundation 8 | import FBSnapshotTestCase 9 | 10 | final class Onboarding: Screen { 11 | 12 | private lazy var nextButton = app.buttons["onboarding/button/next"] 13 | 14 | override var viewIdentifier: String { 15 | return "onboarding/view/main" 16 | } 17 | 18 | @discardableResult 19 | func goToRecognitionView() -> Screen { 20 | nextButton.tap() 21 | nextButton.tap() 22 | nextButton.tap() 23 | return self 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CarLensUITests/Screen/Recognition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Recognition.swift 3 | // CarLensUITests 4 | // 5 | 6 | 7 | import Foundation 8 | 9 | final class Recognition: Screen { 10 | 11 | private lazy var carsButton = app.buttons["recognition/button/cars"] 12 | 13 | override var viewIdentifier: String { 14 | return "recognition/view/main" 15 | } 16 | 17 | @discardableResult 18 | func goToCarsView() -> Screen { 19 | carsButton.tap() 20 | return self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CarLensUITests/Supporting Files/GherkinSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GherkinSyntax.swift 3 | // CarLensUITests 4 | // 5 | 6 | import XCTest 7 | 8 | extension XCTest { 9 | 10 | func given(_ text: String, step: (() -> Void )? = nil) { 11 | XCTContext.runActivity(named: "Given " + text) { _ in 12 | step?() 13 | } 14 | } 15 | 16 | func when(_ text: String, step: (() -> Void )? = nil) { 17 | XCTContext.runActivity(named: "When " + text) { _ in 18 | step?() 19 | } 20 | } 21 | 22 | func then(_ text: String, step: (() -> Void )? = nil) { 23 | XCTContext.runActivity(named: "Then " + text) { _ in 24 | step?() 25 | } 26 | } 27 | 28 | func and(_ text: String, step: (() -> Void )? = nil) { 29 | XCTContext.runActivity(named: "And " + text) { _ in 30 | step?() 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CarLensUITests/Supporting Files/TestBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestBuilder.swift 3 | // CarLensUITests 4 | // 5 | 6 | import XCTest 7 | 8 | final class TestBuilder { 9 | 10 | let app: XCUIApplication 11 | 12 | required init(_ app: XCUIApplication) { 13 | self.app = app 14 | } 15 | 16 | func reset() -> Self { 17 | app.launchArguments += ["-UITests"] 18 | app.launchEnvironment = ["DISABLE_ANIMATIONS": "1"] 19 | return self 20 | } 21 | 22 | func launch() -> Screen { 23 | app.launch() 24 | return Screen(app) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CarLensUITests/Supporting Files/UIImage+RemovingStatusBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+RemovingStatusBar.swift 3 | // CarLensUITests 4 | // 5 | 6 | 7 | import UIKit 8 | 9 | extension UIImage { 10 | 11 | var removingStatusBar: UIImage? { 12 | guard let cgImage = cgImage, let window = UIApplication.shared.keyWindow else { return nil } 13 | let isIPhoneX = window.frame.size == CGSize(width: 1125, height: 2436) 14 | let yOffset = Int((isIPhoneX ? 44 : 20) * scale) 15 | let rect = CGRect(x: 0, y: yOffset, width: cgImage.width, height: cgImage.height - yOffset) 16 | if let croppedCGImage = cgImage.cropping(to: rect) { 17 | return UIImage(cgImage: croppedCGImage, scale: scale, orientation: imageOrientation) 18 | } 19 | return nil 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CarLensUITests/Supporting Files/XCUITestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCUITestCase.swift 3 | // UnicornFeederUITests 4 | // 5 | 6 | import XCTest 7 | import FBSnapshotTestCase 8 | 9 | class XCUITestCase: FBSnapshotTestCase { 10 | 11 | private(set) var app: Screen! 12 | 13 | override func setUp() { 14 | super.setUp() 15 | continueAfterFailure = false 16 | app = TestBuilder(XCUIApplication()).reset().launch() 17 | } 18 | 19 | func setUpAlertHandler() { 20 | let allowButtonPredicate = NSPredicate(format: "label == 'Always Allow' || label == 'Allow' || label == 'OK'") 21 | _ = addUIInterruptionMonitor(withDescription: "Alert Handler") { alert -> Bool in 22 | let alertAllowButton = alert.buttons.matching(allowButtonPredicate).element.firstMatch 23 | if alertAllowButton.exists { 24 | alertAllowButton.tap() 25 | return true 26 | } 27 | return false 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | # 2 | # Cartfile 3 | # 4 | 5 | github "netguru/xcconfigs" == 0.6 6 | github "bitstadium/HockeySDK-iOS" ~> 5.0 7 | github "airbnb/lottie-ios" ~> 2.5 8 | github "uber/ios-snapshot-test-case" ~> 4.0 9 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "airbnb/lottie-ios" "2.5.2" 2 | github "bitstadium/HockeySDK-iOS" "5.1.4" 3 | github "netguru/xcconfigs" "0.6" 4 | github "uber/ios-snapshot-test-case" "4.0.1" 5 | -------------------------------------------------------------------------------- /Configurations/App-Common.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // App-Common.xcconfig 3 | // CarLens 4 | // 5 | 6 | #include "../Carthage/Checkouts/xcconfigs/Common/Common.xcconfig" 7 | #include "../Carthage/Checkouts/xcconfigs/Platforms/iOS.xcconfig" 8 | #include "../Carthage/Checkouts/xcconfigs/Common/Carthage.xcconfig" 9 | 10 | _BUILD_VERSION = 0.8 11 | _BUILD_NUMBER = 2 12 | _COMPILER_SWIFT_VERSION = 4.2 13 | 14 | _BUNDLE_NAME = CarLens 15 | _PRODUCT_NAME = CarLens 16 | _BUNDLE_INFOPLIST_PATH = $(SRCROOT)/CarLens/Supporting Files/Info.plist 17 | _BUNDLE_IDENTIFIER = co.netguru.car.recognition$(__PRODUCT_BUNDLE_IDENTIFIER_SUFFIX) 18 | 19 | _DEPLOYMENT_TARGET_IOS = 11.0 20 | _CODESIGN_STYLE = Manual 21 | 22 | SWIFT_OBJC_BRIDGING_HEADER = $(SRCROOT)/CarLens/Supporting Files/Bridging-Header.h 23 | _ASSET_ICON = AppIcon 24 | -------------------------------------------------------------------------------- /Configurations/App-Development.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // App-Development.xcconfig 3 | // CarLens 4 | // 5 | 6 | #include "../Carthage/Checkouts/xcconfigs/Targets/Application.xcconfig" 7 | #include "../Carthage/Checkouts/xcconfigs/Configurations/Debug.xcconfig" 8 | #include "Pods/Target Support Files/Pods-CarLens/Pods-CarLens.development.xcconfig" 9 | #include "App-Common.xcconfig" 10 | 11 | __PRODUCT_BUNDLE_IDENTIFIER_SUFFIX = .development 12 | 13 | _ENVIRONMENTS = ENV_DEVELOPMENT 14 | 15 | _CODESIGN_DEVELOPMENT_TEAM = 9HJ69C5G9K 16 | _CODESIGN_IDENTITY = iPhone Developer 17 | _CODESIGN_PROFILE_SPECIFIER = CarRecognition Development 18 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon 19 | -------------------------------------------------------------------------------- /Configurations/App-Release.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // App-Release.xcconfig 3 | // CarLens 4 | // 5 | #include "../Carthage/Checkouts/xcconfigs/Targets/Application.xcconfig" 6 | #include "../Carthage/Checkouts/xcconfigs/Configurations/Release.xcconfig" 7 | #include "Pods/Target Support Files/Pods-CarLens/Pods-CarLens.release.xcconfig" 8 | #include "App-Common.xcconfig" 9 | 10 | _ENVIRONMENTS = ENV_PRODUCTION 11 | 12 | _BUNDLE_IDENTIFIER = co.netguru.car.lens.release 13 | 14 | _CODESIGN_DEVELOPMENT_TEAM = SK8PDF7SG9 15 | _CODESIGN_IDENTITY = iPhone Distribution 16 | _CODESIGN_PROFILE_SPECIFIER = CarLens Release 17 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon 18 | -------------------------------------------------------------------------------- /Configurations/App-Staging.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // App-Staging.xcconfig 3 | // CarLens 4 | // 5 | #include "../Carthage/Checkouts/xcconfigs/Targets/Application.xcconfig" 6 | #include "../Carthage/Checkouts/xcconfigs/Configurations/Debug.xcconfig" 7 | #include "Pods/Target Support Files/Pods-CarLens/Pods-CarLens.staging.xcconfig" 8 | #include "App-Common.xcconfig" 9 | 10 | __PRODUCT_BUNDLE_IDENTIFIER_SUFFIX = .staging 11 | 12 | _ENVIRONMENTS = ENV_STAGING 13 | 14 | _CODESIGN_DEVELOPMENT_TEAM = 9HJ69C5G9K 15 | _CODESIGN_IDENTITY = iPhone Distribution 16 | _CODESIGN_PROFILE_SPECIFIER = CarRecognition Staging 17 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon 18 | -------------------------------------------------------------------------------- /Configurations/Test-Common.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Test-Common.xcconfig 3 | // CarLens 4 | // 5 | 6 | #include "../Carthage/Checkouts/xcconfigs/Targets/Tests.xcconfig" 7 | #include "Pods/Target Support Files/Pods-CarLensTests/Pods-CarLensTests.development.xcconfig" 8 | #include "Pods/Target Support Files/Pods-CarLensTests/Pods-CarLensTests.staging.xcconfig" 9 | #include "Pods/Target Support Files/Pods-CarLensTests/Pods-CarLensTests.release.xcconfig" 10 | #include "App-Common.xcconfig" 11 | 12 | __PRODUCT_BUNDLE_IDENTIFIER_SUFFIX = .test 13 | 14 | _ENVIRONMENTS = ENV_TESTS 15 | _BUNDLE_NAME = CarLens Tests 16 | _BUNDLE_INFOPLIST_PATH = $(SRCROOT)/CarLensTests/Supporting Files/Info.plist 17 | 18 | _CODESIGN_STYLE = 19 | _CODESIGN_DEVELOPMENT_TEAM = 20 | _CODESIGN_IDENTITY = 21 | _CODESIGN_PROFILE_SPECIFIER = 22 | 23 | TEST_HOST = $(BUILT_PRODUCTS_DIR)/CarLens.app/CarLens 24 | BUNDLE_LOADER = $(TEST_HOST) 25 | -------------------------------------------------------------------------------- /Configurations/TestUI-Common.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // TestUI-Common.xcconfig 3 | // CarLens 4 | // 5 | 6 | #include "../Carthage/Checkouts/xcconfigs/Targets/Tests.xcconfig" 7 | #include "App-Common.xcconfig" 8 | 9 | __PRODUCT_BUNDLE_IDENTIFIER_SUFFIX = .test 10 | 11 | _ENVIRONMENTS = ENV_TESTS 12 | _BUNDLE_NAME = CarLensUITests 13 | _BUNDLE_INFOPLIST_PATH = $(SRCROOT)/CarLensUITests/Info.plist 14 | 15 | _CODESIGN_STYLE = 16 | _CODESIGN_DEVELOPMENT_TEAM = 17 | _CODESIGN_IDENTITY = 18 | _CODESIGN_PROFILE_SPECIFIER = 19 | 20 | TEST_TARGET_NAME = CarLens 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Gem sources 2 | source 'https://rubygems.org' 3 | 4 | # Development tools 5 | gem 'cocoapods', '~> 1.5.3' 6 | gem 'cocoapods-keys', '~> 2.0.3' 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | RubyInline (3.12.4) 6 | ZenTest (~> 4.3) 7 | ZenTest (4.11.1) 8 | activesupport (4.2.10) 9 | i18n (~> 0.7) 10 | minitest (~> 5.1) 11 | thread_safe (~> 0.3, >= 0.3.4) 12 | tzinfo (~> 1.1) 13 | atomos (0.1.2) 14 | claide (1.0.2) 15 | cocoapods (1.5.3) 16 | activesupport (>= 4.0.2, < 5) 17 | claide (>= 1.0.2, < 2.0) 18 | cocoapods-core (= 1.5.3) 19 | cocoapods-deintegrate (>= 1.0.2, < 2.0) 20 | cocoapods-downloader (>= 1.2.0, < 2.0) 21 | cocoapods-plugins (>= 1.0.0, < 2.0) 22 | cocoapods-search (>= 1.0.0, < 2.0) 23 | cocoapods-stats (>= 1.0.0, < 2.0) 24 | cocoapods-trunk (>= 1.3.0, < 2.0) 25 | cocoapods-try (>= 1.1.0, < 2.0) 26 | colored2 (~> 3.1) 27 | escape (~> 0.0.4) 28 | fourflusher (~> 2.0.1) 29 | gh_inspector (~> 1.0) 30 | molinillo (~> 0.6.5) 31 | nap (~> 1.0) 32 | ruby-macho (~> 1.1) 33 | xcodeproj (>= 1.5.7, < 2.0) 34 | cocoapods-core (1.5.3) 35 | activesupport (>= 4.0.2, < 6) 36 | fuzzy_match (~> 2.0.4) 37 | nap (~> 1.0) 38 | cocoapods-deintegrate (1.0.2) 39 | cocoapods-downloader (1.2.1) 40 | cocoapods-keys (2.0.3) 41 | dotenv 42 | osx_keychain 43 | cocoapods-plugins (1.0.0) 44 | nap 45 | cocoapods-search (1.0.0) 46 | cocoapods-stats (1.0.0) 47 | cocoapods-trunk (1.3.0) 48 | nap (>= 0.8, < 2.0) 49 | netrc (~> 0.11) 50 | cocoapods-try (1.1.0) 51 | colored2 (3.1.2) 52 | concurrent-ruby (1.0.5) 53 | dotenv (2.4.0) 54 | escape (0.0.4) 55 | fourflusher (2.0.1) 56 | fuzzy_match (2.0.4) 57 | gh_inspector (1.1.3) 58 | i18n (0.9.5) 59 | concurrent-ruby (~> 1.0) 60 | minitest (5.11.3) 61 | molinillo (0.6.5) 62 | nanaimo (0.2.5) 63 | nap (1.1.0) 64 | netrc (0.11.0) 65 | osx_keychain (1.0.2) 66 | RubyInline (~> 3) 67 | ruby-macho (1.1.0) 68 | thread_safe (0.3.6) 69 | tzinfo (1.2.5) 70 | thread_safe (~> 0.1) 71 | xcodeproj (1.5.9) 72 | CFPropertyList (>= 2.3.3, < 4.0) 73 | atomos (~> 0.1.2) 74 | claide (>= 1.0.2, < 2.0) 75 | colored2 (~> 3.1) 76 | nanaimo (~> 0.2.5) 77 | 78 | PLATFORMS 79 | ruby 80 | 81 | DEPENDENCIES 82 | cocoapods (~> 1.5.3) 83 | cocoapods-keys (~> 2.0.3) 84 | 85 | BUNDLED WITH 86 | 1.16.1 87 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Ticket 2 | [CRAI-](https://netguru.atlassian.net/browse/CRAI-) 3 | 4 | 5 | ### Task Description 6 | 7 | 8 | 9 | ### Aditional Notes (optional) 10 | 11 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # 2 | # Podfile 3 | # 4 | 5 | platform :ios, '11.0' 6 | inhibit_all_warnings! 7 | use_frameworks! 8 | 9 | 10 | target 'CarLens' do 11 | pod 'SwiftLint', '~> 0.29.0' 12 | 13 | target 'CarLensTests' do 14 | inherit! :search_paths 15 | end 16 | end 17 | 18 | plugin 'cocoapods-keys', { 19 | project: 'CarLens', 20 | keys: [ 21 | 'HOCKEYAPP_APP_ID_STAGING', 22 | 'HOCKEYAPP_APP_ID_PRODUCTION' 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Keys (1.0.1) 3 | - SwiftLint (0.29.3) 4 | 5 | DEPENDENCIES: 6 | - Keys (from `Pods/CocoaPodsKeys`) 7 | - SwiftLint (~> 0.29.0) 8 | 9 | SPEC REPOS: 10 | https://github.com/cocoapods/specs.git: 11 | - SwiftLint 12 | 13 | EXTERNAL SOURCES: 14 | Keys: 15 | :path: Pods/CocoaPodsKeys 16 | 17 | SPEC CHECKSUMS: 18 | Keys: a576f4c9c1c641ca913a959a9c62ed3f215a8de9 19 | SwiftLint: bfa7ca7b4d170cfaf0d236ca3ffd969e88a2f002 20 | 21 | PODFILE CHECKSUM: b83d6631782a8ace4d307d21a29af7a5ddadcba5 22 | 23 | COCOAPODS: 1.5.3 24 | --------------------------------------------------------------------------------