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