diff --git a/Anyline Tire Demo.xcodeproj/project.pbxproj b/Anyline Tire Demo.xcodeproj/project.pbxproj index fc978ef..725e8f0 100644 --- a/Anyline Tire Demo.xcodeproj/project.pbxproj +++ b/Anyline Tire Demo.xcodeproj/project.pbxproj @@ -3,597 +3,609 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ - 802B90F6296D80EB0017919D /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 802B90F5296D80EB0017919D /* Alamofire */; }; - A0BEEBA62A6FFF7800E27276 /* AnylineTireTreadSdk in Frameworks */ = {isa = PBXBuildFile; productRef = A0BEEBA52A6FFF7800E27276 /* AnylineTireTreadSdk */; }; - A0D1F2412A6181BD00A6146B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E3FA4296573C400F1D384 /* SceneDelegate.swift */; }; - A0D1F2422A6181BD00A6146B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E3FA2296573C400F1D384 /* AppDelegate.swift */; }; - A0D1F2432A6181D600A6146B /* UserDefaultsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B9B46E299A46DE00C1CF81 /* UserDefaultsManager.swift */; }; - A0D1F2442A61821500A6146B /* LandingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E3FF02966E27900F1D384 /* LandingViewController.swift */; }; - A0D1F2452A61822200A6146B /* LandingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 802B9112297033500017919D /* LandingViewModel.swift */; }; - A0D1F2462A61823500A6146B /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4084D6529911ADA00A6FB6F /* KeychainManager.swift */; }; - A0D1F2472A61824700A6146B /* ATDTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44E4725299F88220077E2E2 /* ATDTopView.swift */; }; - A0D1F2482A61824E00A6146B /* LandingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B979A0297193500033E3EE /* LandingView.swift */; }; - A0D1F2492A61825A00A6146B /* FontStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E3FE329659A6F00F1D384 /* FontStruct.swift */; }; - A0D1F24A2A61826000A6146B /* ColorStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E3FE129659A4500F1D384 /* ColorStruct.swift */; }; - A0D1F24B2A61826B00A6146B /* ATDSideButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E3FDA2965983B00F1D384 /* ATDSideButton.swift */; }; - A0D1F24C2A61827700A6146B /* TextLandingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C316F029A54C1B00F836A1 /* TextLandingView.swift */; }; - A0D1F24D2A61829000A6146B /* LandingButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4438EF029704144002C3923 /* LandingButtonActionsDelegate.swift */; }; - A0D1F24E2A6182A400A6146B /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E40032966F1A600F1D384 /* String+Extension.swift */; }; - A0D1F24F2A6182B100A6146B /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E40012966EE8A00F1D384 /* SettingsViewController.swift */; }; - A0D1F2502A6182B600A6146B /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E3FFF2966EE7E00F1D384 /* ScanViewController.swift */; }; - A0D1F2512A6182C200A6146B /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40E345D299A43F800DA93E3 /* UIViewController+Extension.swift */; }; - A0D1F2522A6182D500A6146B /* VolumeButtonObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D400DD4529BA21F50021A291 /* VolumeButtonObserver.swift */; }; - A0D1F2532A6182E100A6146B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4438EEC29703963002C3923 /* SettingsView.swift */; }; - A0D1F2542A6182E900A6146B /* ButtonsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42AB9F229714E46005F2093 /* ButtonsSettingsView.swift */; }; - A0D1F2552A6182EF00A6146B /* LicenseSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42AB9F829716120005F2093 /* LicenseSettingsView.swift */; }; - A0D1F2562A6182F600A6146B /* ATDTitleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E3FDE2965998800F1D384 /* ATDTitleLabel.swift */; }; - A0D1F2572A61830600A6146B /* ImperialSystemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42AB9F629715E38005F2093 /* ImperialSystemView.swift */; }; - A0D1F2582A61830D00A6146B /* AccuracySpeedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42AB9FA297161D9005F2093 /* AccuracySpeedSettingsView.swift */; }; - A0D1F2592A61831400A6146B /* ATDTextLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43E3FDC296598E700F1D384 /* ATDTextLabel.swift */; }; - A0D1F25A2A61831B00A6146B /* InfoSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42AB9F429715B93005F2093 /* InfoSettingsView.swift */; }; - A0D1F25B2A61832200A6146B /* SystemInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43EA3002982993300872088 /* SystemInfo.swift */; }; - A0D1F25C2A61832A00A6146B /* ATDTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42AB9FC297169B3005F2093 /* ATDTextField.swift */; }; - A0D1F25D2A61833900A6146B /* SettingsButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4438EEA297038B7002C3923 /* SettingsButtonActionsDelegate.swift */; }; - A0D1F25E2A61834300A6146B /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4499019299CF5DF00BEEBD5 /* SettingsViewModel.swift */; }; - A0D1F25F2A61835C00A6146B /* ScanViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C8EC8329893F2600873781 /* ScanViewModel.swift */; }; - A0D1F2602A61836400A6146B /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C316F729A60E7A00F836A1 /* LoadingViewController.swift */; }; - A0D1F2612A61836D00A6146B /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C316F529A60E6F00F836A1 /* LoadingView.swift */; }; - A0D1F2622A61838100A6146B /* AnimationLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C316FB29A610B900F836A1 /* AnimationLoadingView.swift */; }; - A0D1F2632A61839000A6146B /* LoadingButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C316F929A60F0500F836A1 /* LoadingButtonActionsDelegate.swift */; }; - A0D1F2642A6183A900A6146B /* ErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42ABA00297540B2005F2093 /* ErrorViewController.swift */; }; - A0D1F2652A6183B000A6146B /* LoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C316FE29A6279400F836A1 /* LoadingViewModel.swift */; }; - A0D1F2662A6183B800A6146B /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42ABA032975410E005F2093 /* ErrorView.swift */; }; - A0D1F2672A6183C000A6146B /* ErrorButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42ABA072975470D005F2093 /* ErrorButtonActionsDelegate.swift */; }; - A0D1F2682A6183C800A6146B /* DescriptionErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42ABA0929754DDD005F2093 /* DescriptionErrorView.swift */; }; - A0D1F2692A6183D000A6146B /* ResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D434E11F298138B7001E1FFB /* ResultViewController.swift */; }; - A0D1F26A2A6183D800A6146B /* ResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D434E121298138C7001E1FFB /* ResultView.swift */; }; - A0D1F26B2A6183DE00A6146B /* ResultButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D434E123298138E0001E1FFB /* ResultButtonActionsDelegate.swift */; }; - A0D1F26C2A6183E600A6146B /* ButtonsResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D434E12529813A63001E1FFB /* ButtonsResultView.swift */; }; - A0D1F26D2A6183EC00A6146B /* TireTreadMeasurementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43EA2FD2982874C00872088 /* TireTreadMeasurementView.swift */; }; - A0D1F26E2A6183F600A6146B /* ResultDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D434E11529809341001E1FFB /* ResultDetailsViewController.swift */; }; - A0D1F26F2A6183FC00A6146B /* FeedbackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49F063A2976944B0031B752 /* FeedbackViewController.swift */; }; - A0D1F2702A61840400A6146B /* ResultDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D434E11A29809790001E1FFB /* ResultDetailsView.swift */; }; - A0D1F2712A61840A00A6146B /* ResultDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AAB8A32996D7F90045D527 /* ResultDetailsViewModel.swift */; }; - A0D1F2722A61841700A6146B /* MeasurementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D434E12729813FDD001E1FFB /* MeasurementView.swift */; }; - A0D1F2732A61841F00A6146B /* PDFReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D434E11329808E10001E1FFB /* PDFReaderView.swift */; }; - A0D1F2742A61842800A6146B /* FeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CCAC69297E959500CC78C6 /* FeedbackView.swift */; }; - A0D1F2752A61842C00A6146B /* FeedbackViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D421C097299BDD6400A9F5A6 /* FeedbackViewModel.swift */; }; - A0D1F2762A61843400A6146B /* ButtonsFeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49F063E2976997A0031B752 /* ButtonsFeedbackView.swift */; }; - A0D1F2772A61843900A6146B /* ATDSideTitleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42ABA0529754541005F2093 /* ATDSideTitleLabel.swift */; }; - A0D1F2782A61843E00A6146B /* TireDepthsFeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4CCAC6B297E99A900CC78C6 /* TireDepthsFeedbackView.swift */; }; - A0D1F2792A61844800A6146B /* FeedbackButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B5370529801C04009CAB53 /* FeedbackButtonActionsDelegate.swift */; }; - A0D1F27A2A61846500A6146B /* TireDepthFeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B53703298011B1009CAB53 /* TireDepthFeedbackView.swift */; }; - D410046029BB21F8007B476D /* tiretread_sound_high_beep.wav in Resources */ = {isa = PBXBuildFile; fileRef = D410045E29BB21F8007B476D /* tiretread_sound_high_beep.wav */; }; - D410046129BB21F8007B476D /* tiretread_sound_low_beep.wav in Resources */ = {isa = PBXBuildFile; fileRef = D410045F29BB21F8007B476D /* tiretread_sound_low_beep.wav */; }; - D43E3FAC296573C600F1D384 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D43E3FAB296573C600F1D384 /* Assets.xcassets */; }; - D43E3FAF296573C600F1D384 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D43E3FAD296573C600F1D384 /* LaunchScreen.storyboard */; }; - D43E3FE929659B8900F1D384 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D43E3FEB29659B8900F1D384 /* Localizable.strings */; }; - D43E3FFB2966E86200F1D384 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = D43E3FFA2966E86200F1D384 /* SnapKit */; }; - D43E3FFC2966EC1600F1D384 /* ProximaNova-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = D43E3FEE2966D18700F1D384 /* ProximaNova-Bold.otf */; }; - D43E3FFD2966EC1800F1D384 /* ProximaNova-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = D43E3FEF2966D18700F1D384 /* ProximaNova-Light.otf */; }; - D43E3FFE2966EC1A00F1D384 /* ProximaNova-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = D43E3FED2966D18700F1D384 /* ProximaNova-Regular.otf */; }; - D474EA2D29BB5A4000C1FABA /* tiretread_focuspoint_found.wav in Resources */ = {isa = PBXBuildFile; fileRef = D474EA2C29BB5A4000C1FABA /* tiretread_focuspoint_found.wav */; }; - D474EA2F29BB5D8F00C1FABA /* tiretread_sound_start.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = D474EA2E29BB5D8F00C1FABA /* tiretread_sound_start.mp3 */; }; - D474EA3129BB5D9D00C1FABA /* tiretread_sound_stop.wav in Resources */ = {isa = PBXBuildFile; fileRef = D474EA3029BB5D9D00C1FABA /* tiretread_sound_stop.wav */; }; - D4B53701298002C5009CAB53 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = D4B53700298002C5009CAB53 /* KeychainSwift */; }; + 00D7D9629931D5F9C34F144F /* FeedbackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A8744B0A87FC65A8B46D20 /* FeedbackViewController.swift */; }; + 015D0AF4D1678D4F78A46880 /* SystemInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F46F97960F579993C41507 /* SystemInfo.swift */; }; + 030CA17364D27E2C86B8D36F /* UserDefaultsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FDFBEF38034A3F4671F98F /* UserDefaultsManager.swift */; }; + 030D8C7E8F9F826524BA31A7 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = AE4098F783D33EDD00174621 /* SnapKit */; }; + 0A1A2396F1C55377451C6AF4 /* ResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF323846DAEE03F621DBF05 /* ResultViewController.swift */; }; + 0FA8883A556B2FA320CA2DD2 /* AccuracySpeedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94B3EE586C5AEFFD0946C9B5 /* AccuracySpeedSettingsView.swift */; }; + 1AC53E9E0FF2699C737155CF /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67840B9E99FD8F9FC54844DB /* String+Extension.swift */; }; + 1D17BE143196523D2031D216 /* DescriptionErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 684EA574D2CD2834C4B90FE7 /* DescriptionErrorView.swift */; }; + 1DB1D383AF7C342F7E94BE9C /* AnimationLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039C5E9CF9FCB168405C7A63 /* AnimationLoadingView.swift */; }; + 29ED2FF6F2DD3987E89BFA6A /* tiretread_sound_stop.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0279B6C33EC315BFB9202A88 /* tiretread_sound_stop.wav */; }; + 2E4E98C5AC372ECAECE7BCEA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F65BCB8B40B49FE3A81C57 /* SettingsView.swift */; }; + 35B3D619C9CB0D2485FB93C0 /* ResultDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 592EBCA3FF9BC3EC5068119D /* ResultDetailsViewController.swift */; }; + 38DB3D561F4C43FB5A8B9727 /* ErrorButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDD518668FD48C095561BE8 /* ErrorButtonActionsDelegate.swift */; }; + 3CAFFCCFEE3CA28949769BCE /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5A8E509B4C8C4AC6C6113DBD /* KeychainSwift */; }; + 3CDCBDE8BEBC15FDA1FB6264 /* tiretread_focuspoint_found.wav in Resources */ = {isa = PBXBuildFile; fileRef = 9305D03F418897C0AD10B90F /* tiretread_focuspoint_found.wav */; }; + 40E037705F4EA729263B4531 /* ATDTitleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2042E7AF3F98CE816AC74A87 /* ATDTitleLabel.swift */; }; + 4315EDFF1D1D482EB86C2AC6 /* ButtonsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 203FEF3A731C7A34F6109D47 /* ButtonsSettingsView.swift */; }; + 454F272008A986B435532AE0 /* MeasurementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E205BE38F7CF6F2E673D73B /* MeasurementView.swift */; }; + 4CEED1DF5615C73346CF62C8 /* AnylineTireTreadSdk in Frameworks */ = {isa = PBXBuildFile; productRef = 4FC06FD0D43DBAB59132C68A /* AnylineTireTreadSdk */; }; + 53E9BB7C9EFA688DF79A049A /* LoadingButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE123729BEA35B442A4649EC /* LoadingButtonActionsDelegate.swift */; }; + 59C58D5B136AB5E8E50E6CEF /* ATDSideTitleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AF252A096B6811DE5CBD11 /* ATDSideTitleLabel.swift */; }; + 5E7BB7B5DC99420C9F0C7503 /* ResultDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B64E5BEDEA3ABD3A59F22090 /* ResultDetailsView.swift */; }; + 60867B6448E0174B6C94DD34 /* PDFReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B672D5814257084C202F656D /* PDFReaderView.swift */; }; + 61F8BC3D17A20B47266573CC /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A1F359FD235E25B6A374F9 /* SettingsViewModel.swift */; }; + 62089BD82CA50593B8FA437A /* FeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9A3DA93C8AE03554D6124E /* FeedbackView.swift */; }; + 630302657E9563016A6A475F /* tiretread_sound_beep_too_close.wav in Resources */ = {isa = PBXBuildFile; fileRef = 898D068837A772FDC17C4F80 /* tiretread_sound_beep_too_close.wav */; }; + 658795794DFEF5319801E7C5 /* ButtonsFeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 628BA76297A2A7D92E46E45A /* ButtonsFeedbackView.swift */; }; + 66772AB4A13D6B2762CBAAD9 /* FeedbackViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7967A1D1C06BA2300E23BEA1 /* FeedbackViewModel.swift */; }; + 6A3DED6F32AF230CE411624C /* ATDSideButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FCB5E52078C5C5E8B5569B /* ATDSideButton.swift */; }; + 6A88F4119F37EF8E80A6C418 /* LandingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9341A53AAD476ED9898701C5 /* LandingViewModel.swift */; }; + 6E11D51C1B198681FBBBAF50 /* LoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5010D7616B934B0CEA331D02 /* LoadingViewModel.swift */; }; + 7369D989A1B4FE06EB66969A /* tiretread_sound_start.wav in Resources */ = {isa = PBXBuildFile; fileRef = 356C7E9537E33B81DD3E9DF8 /* tiretread_sound_start.wav */; }; + 752146CA082BED4AE6FA6C33 /* ResultButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 635177CA7A2D6A49FB41537F /* ResultButtonActionsDelegate.swift */; }; + 75DED790D67B3F8F6810A375 /* LandingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E863C814ADFA1AE90698DB /* LandingViewController.swift */; }; + 778185811BD1A1128E44BED5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F41F1B3D9D343C30C7129C4 /* Assets.xcassets */; }; + 7FAF812248111EAE17B51F8A /* SettingsButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F23279E1B27D785630EA5A47 /* SettingsButtonActionsDelegate.swift */; }; + 7FDA3B9A76C6BF12C38735C4 /* ButtonsResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31331DE50C6CDAD5AEA836F /* ButtonsResultView.swift */; }; + 8355FCC47FD62507211C2312 /* TireDepthFeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C8C38156D966C80C5CF40A /* TireDepthFeedbackView.swift */; }; + 84C49BAE86A8C61E2789AE51 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BE053E1CF96479297B839F59 /* LaunchScreen.storyboard */; }; + 84C5DAAA08E0024CCBC9F586 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91A81A696C0FE10BADEDFFF0 /* ErrorView.swift */; }; + 87DB8F58F9BAA1992550BE5D /* ATDTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBED146B22C6AF08693B5B3 /* ATDTopView.swift */; }; + 8A6F05DEA64AACAE756353C9 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79422290CE7EF2CD20C3D8EE /* SettingsViewController.swift */; }; + 8AF6D5CED47CF01761428943 /* ImperialSystemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63DA0E2038EF402A16AC3A68 /* ImperialSystemView.swift */; }; + 8BF4E284C06302D31E0935A4 /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14A8CECEA3C792A45FFCB5FE /* LoadingViewController.swift */; }; + 916BB27AE45273720C857859 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 36DF23772E8928D0BFDB3967 /* Alamofire */; }; + 922F01E6511122B7C47A513A /* LandingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FD477C87ED260D7F2D4B0B /* LandingView.swift */; }; + 927829229CB4F07B5590DDF8 /* TextLandingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CB99471900809461B83691 /* TextLandingView.swift */; }; + 97EA42AE051CF8B91895393B /* TireDepthsFeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD27ED133BF4D5F71179625C /* TireDepthsFeedbackView.swift */; }; + 9DC179F77F904D61DA624801 /* FontStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A56CB61EFAE890DA36421FE /* FontStruct.swift */; }; + A1A43E97D4186089AD2FF75B /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4839D8B8B3418C1F448C0C /* KeychainManager.swift */; }; + A5E50D4A5C0CC96AF74C8C8D /* LicenseSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1082146CA856FECC9AE1C392 /* LicenseSettingsView.swift */; }; + A657F5121313A04D7767CA25 /* ATDTextLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0D513A072F0FA35EEED2BE /* ATDTextLabel.swift */; }; + AB54A1FC59A94B0888996937 /* ResultDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743A1DCAF06FD21B53070DE9 /* ResultDetailsViewModel.swift */; }; + AC4D8CE67B84C89878A0FA2A /* RecorderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6D8CBBF0CEE70A674D10D60 /* RecorderViewController.swift */; }; + B2EBB1666A0CF9F779EF8931 /* TireTreadMeasurementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390C46701BB308E6F53EFB81 /* TireTreadMeasurementView.swift */; }; + B43E435CF5FD514D9D4D4245 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3318E4FA5B27EDB760F76DFF /* Localizable.strings */; }; + C547C5D4DB92F152AC74F0F8 /* ProximaNova-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = DB20B1D3E5DE027FAE19AD9F /* ProximaNova-Regular.otf */; }; + C6BF2CD1FD27D45201F44A83 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515C57BE1A6CB1D9486937B3 /* AppDelegate.swift */; }; + CABBA4922CFF9C7D20F87DF6 /* CaptureSpeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39FB6DAFECFCBA5FFF7D488D /* CaptureSpeedView.swift */; }; + CCA9F8DF9C1EDAD294141EA3 /* FeedbackButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0221683328A2A4F71635B509 /* FeedbackButtonActionsDelegate.swift */; }; + CE2BF07B703E436BCF4A75D0 /* ColorStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E627967E61F1B098CD294E /* ColorStruct.swift */; }; + CFBC8035DE3560D7ABB142FD /* ProximaNova-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8A35D4D3DBCB082A5C414DA4 /* ProximaNova-Bold.otf */; }; + D1C023881D18C8A35E33D669 /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5BA79536B6EEBE089971CDA /* ScanViewController.swift */; }; + D5CBEE1F3E3661B52DBEF1CC /* VolumeButtonObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBFD690B0F8E473AD1285E8 /* VolumeButtonObserver.swift */; }; + D5F2132E906282B4C27F6F83 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2193B4C3D407BACF67DB63B3 /* SceneDelegate.swift */; }; + D6919D00130DD2096F69F352 /* ResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44AFD25638D1C9513636C37 /* ResultView.swift */; }; + DA4553F0DFF86A25ACBF1197 /* LandingTextViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846F00A3CE42550CD5D87E5F /* LandingTextViewDelegate.swift */; }; + DAB4121C20787CFBD4E8BC3B /* ATDTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E20CCB2348A1D5779AD369D /* ATDTextField.swift */; }; + DEC9D5EECB42273A5F89B447 /* LandingButtonActionsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 696E6ED85B49AD9091DA35DE /* LandingButtonActionsDelegate.swift */; }; + E42046826C435BB3C8648356 /* InfoSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E054DE5275CFBA5F059FFD /* InfoSettingsView.swift */; }; + E867FA59DFE1158EF820BA09 /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE63BB8F9AD2406691FCCEAA /* UIViewController+Extension.swift */; }; + E9B33D39315858DA62E2C2BD /* ProximaNova-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7806B02B24D5CA9894554DDF /* ProximaNova-Light.otf */; }; + F526EDD2BB0625673BA4C11C /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8B10AB00E2C39D3A8E912B /* LoadingView.swift */; }; + F61AC54D164F3F1D35289943 /* QRCodeReaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 420A2185548C476939CD5037 /* QRCodeReaderViewController.swift */; }; + F81338C8435FEBA076801D69 /* ScanViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00CC8518403D55DB36CAFA0D /* ScanViewModel.swift */; }; + FA806DEF9791249E298CA64D /* tiretread_sound_beep_too_far.wav in Resources */ = {isa = PBXBuildFile; fileRef = B97D72540717494221B2D968 /* tiretread_sound_beep_too_far.wav */; }; + FE1F9B0BF324FAB9B95F0C81 /* ErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DC8214D152A69BF6BE2BFA /* ErrorViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 802B9112297033500017919D /* LandingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingViewModel.swift; sourceTree = ""; }; - 80B979A0297193500033E3EE /* LandingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LandingView.swift; sourceTree = ""; }; - 80C8EC8329893F2600873781 /* ScanViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewModel.swift; sourceTree = ""; }; - D400DD4529BA21F50021A291 /* VolumeButtonObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtonObserver.swift; sourceTree = ""; }; - D4084D6529911ADA00A6FB6F /* KeychainManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; - D40E345D299A43F800DA93E3 /* UIViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; }; - D410045E29BB21F8007B476D /* tiretread_sound_high_beep.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tiretread_sound_high_beep.wav; sourceTree = ""; }; - D410045F29BB21F8007B476D /* tiretread_sound_low_beep.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tiretread_sound_low_beep.wav; sourceTree = ""; }; - D421C097299BDD6400A9F5A6 /* FeedbackViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackViewModel.swift; sourceTree = ""; }; - D42AB9F229714E46005F2093 /* ButtonsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsSettingsView.swift; sourceTree = ""; }; - D42AB9F429715B93005F2093 /* InfoSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoSettingsView.swift; sourceTree = ""; }; - D42AB9F629715E38005F2093 /* ImperialSystemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImperialSystemView.swift; sourceTree = ""; }; - D42AB9F829716120005F2093 /* LicenseSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseSettingsView.swift; sourceTree = ""; }; - D42AB9FA297161D9005F2093 /* AccuracySpeedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccuracySpeedSettingsView.swift; sourceTree = ""; }; - D42AB9FC297169B3005F2093 /* ATDTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDTextField.swift; sourceTree = ""; }; - D42ABA00297540B2005F2093 /* ErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorViewController.swift; sourceTree = ""; }; - D42ABA032975410E005F2093 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; - D42ABA0529754541005F2093 /* ATDSideTitleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDSideTitleLabel.swift; sourceTree = ""; }; - D42ABA072975470D005F2093 /* ErrorButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorButtonActionsDelegate.swift; sourceTree = ""; }; - D42ABA0929754DDD005F2093 /* DescriptionErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptionErrorView.swift; sourceTree = ""; }; - D434E11329808E10001E1FFB /* PDFReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFReaderView.swift; sourceTree = ""; }; - D434E11529809341001E1FFB /* ResultDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultDetailsViewController.swift; sourceTree = ""; }; - D434E11A29809790001E1FFB /* ResultDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultDetailsView.swift; sourceTree = ""; }; - D434E11F298138B7001E1FFB /* ResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultViewController.swift; sourceTree = ""; }; - D434E121298138C7001E1FFB /* ResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultView.swift; sourceTree = ""; }; - D434E123298138E0001E1FFB /* ResultButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultButtonActionsDelegate.swift; sourceTree = ""; }; - D434E12529813A63001E1FFB /* ButtonsResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsResultView.swift; sourceTree = ""; }; - D434E12729813FDD001E1FFB /* MeasurementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasurementView.swift; sourceTree = ""; }; - D43E3F9F296573C400F1D384 /* Anyline Tire Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Anyline Tire Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - D43E3FA2296573C400F1D384 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - D43E3FA4296573C400F1D384 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - D43E3FAB296573C600F1D384 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - D43E3FAE296573C600F1D384 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - D43E3FB0296573C600F1D384 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D43E3FDA2965983B00F1D384 /* ATDSideButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDSideButton.swift; sourceTree = ""; }; - D43E3FDC296598E700F1D384 /* ATDTextLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDTextLabel.swift; sourceTree = ""; }; - D43E3FDE2965998800F1D384 /* ATDTitleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDTitleLabel.swift; sourceTree = ""; }; - D43E3FE129659A4500F1D384 /* ColorStruct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorStruct.swift; sourceTree = ""; }; - D43E3FE329659A6F00F1D384 /* FontStruct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FontStruct.swift; sourceTree = ""; }; - D43E3FEA29659B8900F1D384 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - D43E3FED2966D18700F1D384 /* ProximaNova-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ProximaNova-Regular.otf"; sourceTree = ""; }; - D43E3FEE2966D18700F1D384 /* ProximaNova-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ProximaNova-Bold.otf"; sourceTree = ""; }; - D43E3FEF2966D18700F1D384 /* ProximaNova-Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ProximaNova-Light.otf"; sourceTree = ""; }; - D43E3FF02966E27900F1D384 /* LandingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingViewController.swift; sourceTree = ""; }; - D43E3FFF2966EE7E00F1D384 /* ScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = ""; }; - D43E40012966EE8A00F1D384 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; - D43E40032966F1A600F1D384 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; - D43EA2FD2982874C00872088 /* TireTreadMeasurementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TireTreadMeasurementView.swift; sourceTree = ""; }; - D43EA3002982993300872088 /* SystemInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemInfo.swift; sourceTree = ""; }; - D4438EEA297038B7002C3923 /* SettingsButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButtonActionsDelegate.swift; sourceTree = ""; }; - D4438EEC29703963002C3923 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - D4438EF029704144002C3923 /* LandingButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingButtonActionsDelegate.swift; sourceTree = ""; }; - D4499019299CF5DF00BEEBD5 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; - D44E4725299F88220077E2E2 /* ATDTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDTopView.swift; sourceTree = ""; }; - D474EA2C29BB5A4000C1FABA /* tiretread_focuspoint_found.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tiretread_focuspoint_found.wav; sourceTree = ""; }; - D474EA2E29BB5D8F00C1FABA /* tiretread_sound_start.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = tiretread_sound_start.mp3; sourceTree = ""; }; - D474EA3029BB5D9D00C1FABA /* tiretread_sound_stop.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tiretread_sound_stop.wav; sourceTree = ""; }; - D49F063A2976944B0031B752 /* FeedbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackViewController.swift; sourceTree = ""; }; - D49F063E2976997A0031B752 /* ButtonsFeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsFeedbackView.swift; sourceTree = ""; }; - D4AAB8A32996D7F90045D527 /* ResultDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultDetailsViewModel.swift; sourceTree = ""; }; - D4B53703298011B1009CAB53 /* TireDepthFeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TireDepthFeedbackView.swift; sourceTree = ""; }; - D4B5370529801C04009CAB53 /* FeedbackButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackButtonActionsDelegate.swift; sourceTree = ""; }; - D4B9B46E299A46DE00C1CF81 /* UserDefaultsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsManager.swift; sourceTree = ""; }; - D4C316F029A54C1B00F836A1 /* TextLandingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLandingView.swift; sourceTree = ""; }; - D4C316F529A60E6F00F836A1 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; - D4C316F729A60E7A00F836A1 /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = ""; }; - D4C316F929A60F0500F836A1 /* LoadingButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingButtonActionsDelegate.swift; sourceTree = ""; }; - D4C316FB29A610B900F836A1 /* AnimationLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationLoadingView.swift; sourceTree = ""; }; - D4C316FE29A6279400F836A1 /* LoadingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewModel.swift; sourceTree = ""; }; - D4CCAC69297E959500CC78C6 /* FeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackView.swift; sourceTree = ""; }; - D4CCAC6B297E99A900CC78C6 /* TireDepthsFeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TireDepthsFeedbackView.swift; sourceTree = ""; }; + 00CC8518403D55DB36CAFA0D /* ScanViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewModel.swift; sourceTree = ""; }; + 0221683328A2A4F71635B509 /* FeedbackButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackButtonActionsDelegate.swift; sourceTree = ""; }; + 0279B6C33EC315BFB9202A88 /* tiretread_sound_stop.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tiretread_sound_stop.wav; sourceTree = ""; }; + 039C5E9CF9FCB168405C7A63 /* AnimationLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationLoadingView.swift; sourceTree = ""; }; + 1082146CA856FECC9AE1C392 /* LicenseSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseSettingsView.swift; sourceTree = ""; }; + 14A8CECEA3C792A45FFCB5FE /* LoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = ""; }; + 1DBED146B22C6AF08693B5B3 /* ATDTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDTopView.swift; sourceTree = ""; }; + 203FEF3A731C7A34F6109D47 /* ButtonsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsSettingsView.swift; sourceTree = ""; }; + 2042E7AF3F98CE816AC74A87 /* ATDTitleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDTitleLabel.swift; sourceTree = ""; }; + 2193B4C3D407BACF67DB63B3 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 2A56CB61EFAE890DA36421FE /* FontStruct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontStruct.swift; sourceTree = ""; }; + 3318E4FA5B27EDB760F76DFF /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 356C7E9537E33B81DD3E9DF8 /* tiretread_sound_start.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tiretread_sound_start.wav; sourceTree = ""; }; + 390C46701BB308E6F53EFB81 /* TireTreadMeasurementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TireTreadMeasurementView.swift; sourceTree = ""; }; + 39FB6DAFECFCBA5FFF7D488D /* CaptureSpeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaptureSpeedView.swift; sourceTree = ""; }; + 420A2185548C476939CD5037 /* QRCodeReaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeReaderViewController.swift; sourceTree = ""; }; + 43FD477C87ED260D7F2D4B0B /* LandingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingView.swift; sourceTree = ""; }; + 45AF252A096B6811DE5CBD11 /* ATDSideTitleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDSideTitleLabel.swift; sourceTree = ""; }; + 4EB97AF36FE6ADF80D5595B4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 4F41F1B3D9D343C30C7129C4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 5010D7616B934B0CEA331D02 /* LoadingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewModel.swift; sourceTree = ""; }; + 515C57BE1A6CB1D9486937B3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 53E863C814ADFA1AE90698DB /* LandingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingViewController.swift; sourceTree = ""; }; + 58DC8214D152A69BF6BE2BFA /* ErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorViewController.swift; sourceTree = ""; }; + 592EBCA3FF9BC3EC5068119D /* ResultDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultDetailsViewController.swift; sourceTree = ""; }; + 5C4839D8B8B3418C1F448C0C /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; + 5FA6E900770A1B48D530C0B0 /* Anyline Tire Demo.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "Anyline Tire Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5FDD518668FD48C095561BE8 /* ErrorButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorButtonActionsDelegate.swift; sourceTree = ""; }; + 628BA76297A2A7D92E46E45A /* ButtonsFeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsFeedbackView.swift; sourceTree = ""; }; + 635177CA7A2D6A49FB41537F /* ResultButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultButtonActionsDelegate.swift; sourceTree = ""; }; + 63B95ED79DF5781095E3CE27 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 63DA0E2038EF402A16AC3A68 /* ImperialSystemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImperialSystemView.swift; sourceTree = ""; }; + 67840B9E99FD8F9FC54844DB /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; + 684EA574D2CD2834C4B90FE7 /* DescriptionErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptionErrorView.swift; sourceTree = ""; }; + 696E6ED85B49AD9091DA35DE /* LandingButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingButtonActionsDelegate.swift; sourceTree = ""; }; + 6E20CCB2348A1D5779AD369D /* ATDTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDTextField.swift; sourceTree = ""; }; + 743A1DCAF06FD21B53070DE9 /* ResultDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultDetailsViewModel.swift; sourceTree = ""; }; + 7806B02B24D5CA9894554DDF /* ProximaNova-Light.otf */ = {isa = PBXFileReference; path = "ProximaNova-Light.otf"; sourceTree = ""; }; + 79422290CE7EF2CD20C3D8EE /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + 7967A1D1C06BA2300E23BEA1 /* FeedbackViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackViewModel.swift; sourceTree = ""; }; + 846F00A3CE42550CD5D87E5F /* LandingTextViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingTextViewDelegate.swift; sourceTree = ""; }; + 898D068837A772FDC17C4F80 /* tiretread_sound_beep_too_close.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tiretread_sound_beep_too_close.wav; sourceTree = ""; }; + 8A35D4D3DBCB082A5C414DA4 /* ProximaNova-Bold.otf */ = {isa = PBXFileReference; path = "ProximaNova-Bold.otf"; sourceTree = ""; }; + 91A81A696C0FE10BADEDFFF0 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; + 9305D03F418897C0AD10B90F /* tiretread_focuspoint_found.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tiretread_focuspoint_found.wav; sourceTree = ""; }; + 9341A53AAD476ED9898701C5 /* LandingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingViewModel.swift; sourceTree = ""; }; + 94A1F359FD235E25B6A374F9 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; + 94B3EE586C5AEFFD0946C9B5 /* AccuracySpeedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccuracySpeedSettingsView.swift; sourceTree = ""; }; + 98E054DE5275CFBA5F059FFD /* InfoSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoSettingsView.swift; sourceTree = ""; }; + 9E205BE38F7CF6F2E673D73B /* MeasurementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasurementView.swift; sourceTree = ""; }; + A5C8C38156D966C80C5CF40A /* TireDepthFeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TireDepthFeedbackView.swift; sourceTree = ""; }; + A9CB99471900809461B83691 /* TextLandingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLandingView.swift; sourceTree = ""; }; + AA0D513A072F0FA35EEED2BE /* ATDTextLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDTextLabel.swift; sourceTree = ""; }; + B31331DE50C6CDAD5AEA836F /* ButtonsResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsResultView.swift; sourceTree = ""; }; + B5F46F97960F579993C41507 /* SystemInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemInfo.swift; sourceTree = ""; }; + B64E5BEDEA3ABD3A59F22090 /* ResultDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultDetailsView.swift; sourceTree = ""; }; + B672D5814257084C202F656D /* PDFReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFReaderView.swift; sourceTree = ""; }; + B97D72540717494221B2D968 /* tiretread_sound_beep_too_far.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = tiretread_sound_beep_too_far.wav; sourceTree = ""; }; + C1FCB5E52078C5C5E8B5569B /* ATDSideButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATDSideButton.swift; sourceTree = ""; }; + C44AFD25638D1C9513636C37 /* ResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultView.swift; sourceTree = ""; }; + CBBFD690B0F8E473AD1285E8 /* VolumeButtonObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtonObserver.swift; sourceTree = ""; }; + D5BA79536B6EEBE089971CDA /* ScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = ""; }; + D6FDFBEF38034A3F4671F98F /* UserDefaultsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsManager.swift; sourceTree = ""; }; + DB20B1D3E5DE027FAE19AD9F /* ProximaNova-Regular.otf */ = {isa = PBXFileReference; path = "ProximaNova-Regular.otf"; sourceTree = ""; }; + DBF323846DAEE03F621DBF05 /* ResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultViewController.swift; sourceTree = ""; }; + DD27ED133BF4D5F71179625C /* TireDepthsFeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TireDepthsFeedbackView.swift; sourceTree = ""; }; + DD9A3DA93C8AE03554D6124E /* FeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackView.swift; sourceTree = ""; }; + DE123729BEA35B442A4649EC /* LoadingButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingButtonActionsDelegate.swift; sourceTree = ""; }; + E0E627967E61F1B098CD294E /* ColorStruct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorStruct.swift; sourceTree = ""; }; + F23279E1B27D785630EA5A47 /* SettingsButtonActionsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButtonActionsDelegate.swift; sourceTree = ""; }; + F4F65BCB8B40B49FE3A81C57 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + F6D8CBBF0CEE70A674D10D60 /* RecorderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecorderViewController.swift; sourceTree = ""; }; + F9A8744B0A87FC65A8B46D20 /* FeedbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackViewController.swift; sourceTree = ""; }; + FC8B10AB00E2C39D3A8E912B /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; + FE63BB8F9AD2406691FCCEAA /* UIViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - D43E3F9C296573C400F1D384 /* Frameworks */ = { + A7AA68BB7B6664B26C0758B7 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D4B53701298002C5009CAB53 /* KeychainSwift in Frameworks */, - A0BEEBA62A6FFF7800E27276 /* AnylineTireTreadSdk in Frameworks */, - D43E3FFB2966E86200F1D384 /* SnapKit in Frameworks */, - 802B90F6296D80EB0017919D /* Alamofire in Frameworks */, + 4CEED1DF5615C73346CF62C8 /* AnylineTireTreadSdk in Frameworks */, + 916BB27AE45273720C857859 /* Alamofire in Frameworks */, + 3CAFFCCFEE3CA28949769BCE /* KeychainSwift in Frameworks */, + 030D8C7E8F9F826524BA31A7 /* SnapKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 802B9109296EAFF40017919D /* Presentation */ = { + 07797286DB83D938F3E42B90 /* Landing */ = { isa = PBXGroup; children = ( - D43E3FBD29657C4F00F1D384 /* Components */, - D43E3FBE29657C8C00F1D384 /* Features */, + 696E6ED85B49AD9091DA35DE /* LandingButtonActionsDelegate.swift */, + 846F00A3CE42550CD5D87E5F /* LandingTextViewDelegate.swift */, + 4F0317CEF684E6F6D8E09AB4 /* Controller */, + C0665B9A93EEE921E82262E9 /* View */, + 4D6401116713DEBBA0697755 /* ViewModel */, ); - path = Presentation; + path = Landing; sourceTree = ""; }; - 802B9114297059A70017919D /* ViewModel */ = { + 082EB2EB5944D20E39DE8532 /* Loading */ = { isa = PBXGroup; children = ( - 802B9112297033500017919D /* LandingViewModel.swift */, + DE123729BEA35B442A4649EC /* LoadingButtonActionsDelegate.swift */, + 38E7F8F5E0215730E7AE8127 /* Controller */, + 6739CDD1F9FF2CE43F822A4C /* View */, + 4ACD01D0B098B61A12C94F6B /* ViewModel */, ); - path = ViewModel; + path = Loading; sourceTree = ""; }; - 80B9799F297193500033E3EE /* View */ = { + 0B640698AABBBA4ECC8390C5 /* Resources */ = { isa = PBXGroup; children = ( - 80B979A0297193500033E3EE /* LandingView.swift */, - D4C316F029A54C1B00F836A1 /* TextLandingView.swift */, + 19003AC71D8C98073C1BB5CA /* Audio */, + 26065F191C2A166B67E54837 /* Extensions */, + 9040E2F1A88E371D7DDCB6A7 /* Helpers */, + 930DC432CB7ADC12BB1C40F9 /* System */, ); - path = View; + path = Resources; sourceTree = ""; }; - 80C8EC8229893F0B00873781 /* ViewModel */ = { + 125A9CAF259E1BB12AEF96B7 /* Settings */ = { isa = PBXGroup; children = ( - 80C8EC8329893F2600873781 /* ScanViewModel.swift */, + F23279E1B27D785630EA5A47 /* SettingsButtonActionsDelegate.swift */, + 223561F4EAF60F7650B21435 /* Controller */, + A02EF026C8E68AE22F23D8ED /* View */, + 43B3AB73302BC67D222ACA1C /* ViewModel */, ); - path = ViewModel; + path = Settings; sourceTree = ""; }; - A0BEEBA42A6FFF7800E27276 /* Frameworks */ = { + 19003AC71D8C98073C1BB5CA /* Audio */ = { isa = PBXGroup; children = ( + 9305D03F418897C0AD10B90F /* tiretread_focuspoint_found.wav */, + 898D068837A772FDC17C4F80 /* tiretread_sound_beep_too_close.wav */, + B97D72540717494221B2D968 /* tiretread_sound_beep_too_far.wav */, + 356C7E9537E33B81DD3E9DF8 /* tiretread_sound_start.wav */, + 0279B6C33EC315BFB9202A88 /* tiretread_sound_stop.wav */, ); - name = Frameworks; + path = Audio; sourceTree = ""; }; - D4084D6429911ADA00A6FB6F /* DataStorage */ = { + 223561F4EAF60F7650B21435 /* Controller */ = { isa = PBXGroup; children = ( - D4084D6529911ADA00A6FB6F /* KeychainManager.swift */, - D4B9B46E299A46DE00C1CF81 /* UserDefaultsManager.swift */, + 79422290CE7EF2CD20C3D8EE /* SettingsViewController.swift */, ); - path = DataStorage; + path = Controller; sourceTree = ""; }; - D410045D29BB21B2007B476D /* Audio */ = { + 22B452DAF0CCDD6BA9E99063 /* Fonts */ = { isa = PBXGroup; children = ( - D474EA2C29BB5A4000C1FABA /* tiretread_focuspoint_found.wav */, - D410045E29BB21F8007B476D /* tiretread_sound_high_beep.wav */, - D410045F29BB21F8007B476D /* tiretread_sound_low_beep.wav */, - D474EA2E29BB5D8F00C1FABA /* tiretread_sound_start.mp3 */, - D474EA3029BB5D9D00C1FABA /* tiretread_sound_stop.wav */, + 8A35D4D3DBCB082A5C414DA4 /* ProximaNova-Bold.otf */, + 7806B02B24D5CA9894554DDF /* ProximaNova-Light.otf */, + DB20B1D3E5DE027FAE19AD9F /* ProximaNova-Regular.otf */, ); - path = Audio; + path = Fonts; sourceTree = ""; }; - D421C096299BDD4E00A9F5A6 /* ViewModel */ = { + 26065F191C2A166B67E54837 /* Extensions */ = { isa = PBXGroup; children = ( - D421C097299BDD6400A9F5A6 /* FeedbackViewModel.swift */, + 67840B9E99FD8F9FC54844DB /* String+Extension.swift */, + FE63BB8F9AD2406691FCCEAA /* UIViewController+Extension.swift */, ); - path = ViewModel; + path = Extensions; sourceTree = ""; }; - D42AB9FE29754044005F2093 /* Error */ = { + 38E7F8F5E0215730E7AE8127 /* Controller */ = { isa = PBXGroup; children = ( - D42AB9FF2975409E005F2093 /* Controller */, - D42ABA072975470D005F2093 /* ErrorButtonActionsDelegate.swift */, + 14A8CECEA3C792A45FFCB5FE /* LoadingViewController.swift */, ); - path = Error; + path = Controller; sourceTree = ""; }; - D42AB9FF2975409E005F2093 /* Controller */ = { + 39269ECB8E0AAF521DD42C1C /* Controller */ = { isa = PBXGroup; children = ( - D42ABA00297540B2005F2093 /* ErrorViewController.swift */, - D42ABA02297540B6005F2093 /* View */, + D5BA79536B6EEBE089971CDA /* ScanViewController.swift */, ); path = Controller; sourceTree = ""; }; - D42ABA02297540B6005F2093 /* View */ = { + 42B88D7021F7770EB8516B60 /* Controller */ = { isa = PBXGroup; children = ( - D42ABA032975410E005F2093 /* ErrorView.swift */, - D42ABA0929754DDD005F2093 /* DescriptionErrorView.swift */, + 58DC8214D152A69BF6BE2BFA /* ErrorViewController.swift */, + 8301B8486339AEE6DE4D3B30 /* View */, + ); + path = Controller; + sourceTree = ""; + }; + 433DD292F275DAE422928890 /* View */ = { + isa = PBXGroup; + children = ( + B31331DE50C6CDAD5AEA836F /* ButtonsResultView.swift */, + 9E205BE38F7CF6F2E673D73B /* MeasurementView.swift */, + C44AFD25638D1C9513636C37 /* ResultView.swift */, + 390C46701BB308E6F53EFB81 /* TireTreadMeasurementView.swift */, ); path = View; sourceTree = ""; }; - D434E11729809348001E1FFB /* Result details */ = { + 43B3AB73302BC67D222ACA1C /* ViewModel */ = { isa = PBXGroup; children = ( - D434E1192980935E001E1FFB /* View */, - D4AAB8A22996D7E30045D527 /* ViewModel */, - D434E11829809359001E1FFB /* Controller */, + 94A1F359FD235E25B6A374F9 /* SettingsViewModel.swift */, ); - path = "Result details"; + path = ViewModel; sourceTree = ""; }; - D434E11829809359001E1FFB /* Controller */ = { + 443C59D5612CFED4001C8AD1 /* Anyline Tire Demo */ = { isa = PBXGroup; children = ( - D434E11529809341001E1FFB /* ResultDetailsViewController.swift */, + 515C57BE1A6CB1D9486937B3 /* AppDelegate.swift */, + 4F41F1B3D9D343C30C7129C4 /* Assets.xcassets */, + 63B95ED79DF5781095E3CE27 /* Info.plist */, + BE053E1CF96479297B839F59 /* LaunchScreen.storyboard */, + 3318E4FA5B27EDB760F76DFF /* Localizable.strings */, + 2193B4C3D407BACF67DB63B3 /* SceneDelegate.swift */, + B2A029EAA4AF722D05D155BE /* DataStorage */, + 22B452DAF0CCDD6BA9E99063 /* Fonts */, + 4E462EF51CB251ECFF0B094A /* Presentation */, + 0B640698AABBBA4ECC8390C5 /* Resources */, ); - path = Controller; + path = "Anyline Tire Demo"; sourceTree = ""; }; - D434E1192980935E001E1FFB /* View */ = { + 4ACD01D0B098B61A12C94F6B /* ViewModel */ = { isa = PBXGroup; children = ( - D434E11A29809790001E1FFB /* ResultDetailsView.swift */, + 5010D7616B934B0CEA331D02 /* LoadingViewModel.swift */, ); - path = View; + path = ViewModel; sourceTree = ""; }; - D434E11C2981388B001E1FFB /* Result */ = { + 4D6401116713DEBBA0697755 /* ViewModel */ = { isa = PBXGroup; children = ( - D434E11E298138A5001E1FFB /* Controller */, - D434E11D298138A0001E1FFB /* View */, - D434E123298138E0001E1FFB /* ResultButtonActionsDelegate.swift */, + 9341A53AAD476ED9898701C5 /* LandingViewModel.swift */, ); - path = Result; + path = ViewModel; sourceTree = ""; }; - D434E11D298138A0001E1FFB /* View */ = { + 4E462EF51CB251ECFF0B094A /* Presentation */ = { isa = PBXGroup; children = ( - D434E12529813A63001E1FFB /* ButtonsResultView.swift */, - D434E12729813FDD001E1FFB /* MeasurementView.swift */, - D434E121298138C7001E1FFB /* ResultView.swift */, - D43EA2FD2982874C00872088 /* TireTreadMeasurementView.swift */, + E0C51FE8D426F614EDA5597B /* Components */, + 8DAAD10E567CFE5AD8404D5E /* Features */, ); - path = View; + path = Presentation; sourceTree = ""; }; - D434E11E298138A5001E1FFB /* Controller */ = { + 4F0317CEF684E6F6D8E09AB4 /* Controller */ = { isa = PBXGroup; children = ( - D434E11F298138B7001E1FFB /* ResultViewController.swift */, + 53E863C814ADFA1AE90698DB /* LandingViewController.swift */, + F6D8CBBF0CEE70A674D10D60 /* RecorderViewController.swift */, ); path = Controller; sourceTree = ""; }; - D43E3F96296573C400F1D384 = { + 5940DD433D86ACF87D593153 /* Scan */ = { isa = PBXGroup; children = ( - D43E3FA1296573C400F1D384 /* Anyline Tire Demo */, - D43E3FA0296573C400F1D384 /* Products */, - A0BEEBA42A6FFF7800E27276 /* Frameworks */, + 39269ECB8E0AAF521DD42C1C /* Controller */, + EC96517ED54B69E4FCD95A03 /* ViewModel */, ); + path = Scan; sourceTree = ""; }; - D43E3FA0296573C400F1D384 /* Products */ = { + 5FC9F09156E795359BF8FC75 /* Result details */ = { isa = PBXGroup; children = ( - D43E3F9F296573C400F1D384 /* Anyline Tire Demo.app */, + E6A570D49D23B181862D5DC6 /* Controller */, + AA5761B4C1D318401AFC51B8 /* View */, + F7BADD19F44B4EC4EBDFDB31 /* ViewModel */, ); - name = Products; + path = "Result details"; sourceTree = ""; }; - D43E3FA1296573C400F1D384 /* Anyline Tire Demo */ = { + 6739CDD1F9FF2CE43F822A4C /* View */ = { isa = PBXGroup; children = ( - D4084D6429911ADA00A6FB6F /* DataStorage */, - 802B9109296EAFF40017919D /* Presentation */, - D43E3FB929657B9E00F1D384 /* Resources */, - D43E3FEC2966D17300F1D384 /* Fonts */, - D43E3FA2296573C400F1D384 /* AppDelegate.swift */, - D43E3FA4296573C400F1D384 /* SceneDelegate.swift */, - D43E3FAB296573C600F1D384 /* Assets.xcassets */, - D43E3FAD296573C600F1D384 /* LaunchScreen.storyboard */, - D43E3FB0296573C600F1D384 /* Info.plist */, - D43E3FEB29659B8900F1D384 /* Localizable.strings */, + 039C5E9CF9FCB168405C7A63 /* AnimationLoadingView.swift */, + FC8B10AB00E2C39D3A8E912B /* LoadingView.swift */, ); - path = "Anyline Tire Demo"; + path = View; sourceTree = ""; }; - D43E3FB829657B6B00F1D384 /* Controller */ = { + 7045F4FAFB44E34CF1F56CB0 /* ViewModel */ = { isa = PBXGroup; children = ( - D43E3FF02966E27900F1D384 /* LandingViewController.swift */, + 7967A1D1C06BA2300E23BEA1 /* FeedbackViewModel.swift */, ); - path = Controller; + path = ViewModel; sourceTree = ""; }; - D43E3FB929657B9E00F1D384 /* Resources */ = { + 7D071E38AC4FE9AAD02D7B33 /* Error */ = { isa = PBXGroup; children = ( - D410045D29BB21B2007B476D /* Audio */, - D43EA2FF2982991F00872088 /* System */, - D43E3FE029659A2300F1D384 /* Helpers */, - D43E3FBC29657C4600F1D384 /* Extensions */, + 5FDD518668FD48C095561BE8 /* ErrorButtonActionsDelegate.swift */, + 42B88D7021F7770EB8516B60 /* Controller */, ); - path = Resources; + path = Error; sourceTree = ""; }; - D43E3FBA29657BAB00F1D384 /* Landing */ = { + 8301B8486339AEE6DE4D3B30 /* View */ = { isa = PBXGroup; children = ( - 80B9799F297193500033E3EE /* View */, - 802B9114297059A70017919D /* ViewModel */, - D43E3FB829657B6B00F1D384 /* Controller */, - D4438EF029704144002C3923 /* LandingButtonActionsDelegate.swift */, + 684EA574D2CD2834C4B90FE7 /* DescriptionErrorView.swift */, + 91A81A696C0FE10BADEDFFF0 /* ErrorView.swift */, ); - path = Landing; + path = View; sourceTree = ""; }; - D43E3FBC29657C4600F1D384 /* Extensions */ = { + 8D7F71E3A783FE4F8C5BC78F /* Result */ = { isa = PBXGroup; children = ( - D43E40032966F1A600F1D384 /* String+Extension.swift */, - D40E345D299A43F800DA93E3 /* UIViewController+Extension.swift */, + 635177CA7A2D6A49FB41537F /* ResultButtonActionsDelegate.swift */, + FF266DC6636B9C10AEC09666 /* Controller */, + 433DD292F275DAE422928890 /* View */, ); - path = Extensions; + path = Result; sourceTree = ""; }; - D43E3FBD29657C4F00F1D384 /* Components */ = { + 8D913555918E6AE6F6BA8BAB /* Controller */ = { isa = PBXGroup; children = ( - D43E3FDA2965983B00F1D384 /* ATDSideButton.swift */, - D42ABA0529754541005F2093 /* ATDSideTitleLabel.swift */, - D42AB9FC297169B3005F2093 /* ATDTextField.swift */, - D43E3FDC296598E700F1D384 /* ATDTextLabel.swift */, - D43E3FDE2965998800F1D384 /* ATDTitleLabel.swift */, - D44E4725299F88220077E2E2 /* ATDTopView.swift */, - D434E11329808E10001E1FFB /* PDFReaderView.swift */, + F9A8744B0A87FC65A8B46D20 /* FeedbackViewController.swift */, ); - path = Components; + path = Controller; sourceTree = ""; }; - D43E3FBE29657C8C00F1D384 /* Features */ = { + 8DAAD10E567CFE5AD8404D5E /* Features */ = { isa = PBXGroup; children = ( - D43E3FBA29657BAB00F1D384 /* Landing */, - D43E3FC729657D1600F1D384 /* Settings */, - D49F0637297693D60031B752 /* Feedback */, - D43E3FD329657D1600F1D384 /* Scan */, - D4C316F229A60E2B00F836A1 /* Loading */, - D434E11C2981388B001E1FFB /* Result */, - D42AB9FE29754044005F2093 /* Error */, - D434E11729809348001E1FFB /* Result details */, + 7D071E38AC4FE9AAD02D7B33 /* Error */, + F1716B231CD02A1A83A12434 /* Feedback */, + 07797286DB83D938F3E42B90 /* Landing */, + 082EB2EB5944D20E39DE8532 /* Loading */, + D1A636076B4B96ECF9BDB1BE /* QR */, + 8D7F71E3A783FE4F8C5BC78F /* Result */, + 5FC9F09156E795359BF8FC75 /* Result details */, + 5940DD433D86ACF87D593153 /* Scan */, + 125A9CAF259E1BB12AEF96B7 /* Settings */, ); path = Features; sourceTree = ""; }; - D43E3FC729657D1600F1D384 /* Settings */ = { + 9040E2F1A88E371D7DDCB6A7 /* Helpers */ = { isa = PBXGroup; children = ( - D4438EEA297038B7002C3923 /* SettingsButtonActionsDelegate.swift */, - D43E3FCA29657D1600F1D384 /* View */, - D4499018299CF5CC00BEEBD5 /* ViewModel */, - D43E3FC829657D1600F1D384 /* Controller */, + E0E627967E61F1B098CD294E /* ColorStruct.swift */, + 2A56CB61EFAE890DA36421FE /* FontStruct.swift */, + CBBFD690B0F8E473AD1285E8 /* VolumeButtonObserver.swift */, ); - path = Settings; + path = Helpers; sourceTree = ""; }; - D43E3FC829657D1600F1D384 /* Controller */ = { + 930DC432CB7ADC12BB1C40F9 /* System */ = { isa = PBXGroup; children = ( - D43E40012966EE8A00F1D384 /* SettingsViewController.swift */, + B5F46F97960F579993C41507 /* SystemInfo.swift */, ); - path = Controller; + path = System; sourceTree = ""; }; - D43E3FCA29657D1600F1D384 /* View */ = { + 9AB0F6C55B1BEBC49AE6BFA0 /* Products */ = { isa = PBXGroup; children = ( - D42AB9FA297161D9005F2093 /* AccuracySpeedSettingsView.swift */, - D42AB9F229714E46005F2093 /* ButtonsSettingsView.swift */, - D42AB9F629715E38005F2093 /* ImperialSystemView.swift */, - D42AB9F429715B93005F2093 /* InfoSettingsView.swift */, - D42AB9F829716120005F2093 /* LicenseSettingsView.swift */, - D4438EEC29703963002C3923 /* SettingsView.swift */, + 5FA6E900770A1B48D530C0B0 /* Anyline Tire Demo.app */, ); - path = View; + name = Products; sourceTree = ""; }; - D43E3FD329657D1600F1D384 /* Scan */ = { + A02EF026C8E68AE22F23D8ED /* View */ = { isa = PBXGroup; children = ( - 80C8EC8229893F0B00873781 /* ViewModel */, - D43E3FD429657D1600F1D384 /* Controller */, + 94B3EE586C5AEFFD0946C9B5 /* AccuracySpeedSettingsView.swift */, + 203FEF3A731C7A34F6109D47 /* ButtonsSettingsView.swift */, + 39FB6DAFECFCBA5FFF7D488D /* CaptureSpeedView.swift */, + 63DA0E2038EF402A16AC3A68 /* ImperialSystemView.swift */, + 98E054DE5275CFBA5F059FFD /* InfoSettingsView.swift */, + 1082146CA856FECC9AE1C392 /* LicenseSettingsView.swift */, + F4F65BCB8B40B49FE3A81C57 /* SettingsView.swift */, ); - path = Scan; + path = View; sourceTree = ""; }; - D43E3FD429657D1600F1D384 /* Controller */ = { + AA5761B4C1D318401AFC51B8 /* View */ = { isa = PBXGroup; children = ( - D43E3FFF2966EE7E00F1D384 /* ScanViewController.swift */, + B64E5BEDEA3ABD3A59F22090 /* ResultDetailsView.swift */, ); - path = Controller; + path = View; sourceTree = ""; }; - D43E3FE029659A2300F1D384 /* Helpers */ = { + B2A029EAA4AF722D05D155BE /* DataStorage */ = { isa = PBXGroup; children = ( - D43E3FE329659A6F00F1D384 /* FontStruct.swift */, - D43E3FE129659A4500F1D384 /* ColorStruct.swift */, - D400DD4529BA21F50021A291 /* VolumeButtonObserver.swift */, + 5C4839D8B8B3418C1F448C0C /* KeychainManager.swift */, + D6FDFBEF38034A3F4671F98F /* UserDefaultsManager.swift */, ); - path = Helpers; + path = DataStorage; sourceTree = ""; }; - D43E3FEC2966D17300F1D384 /* Fonts */ = { + C0665B9A93EEE921E82262E9 /* View */ = { isa = PBXGroup; children = ( - D43E3FEE2966D18700F1D384 /* ProximaNova-Bold.otf */, - D43E3FEF2966D18700F1D384 /* ProximaNova-Light.otf */, - D43E3FED2966D18700F1D384 /* ProximaNova-Regular.otf */, + 43FD477C87ED260D7F2D4B0B /* LandingView.swift */, + A9CB99471900809461B83691 /* TextLandingView.swift */, ); - path = Fonts; + path = View; sourceTree = ""; }; - D43EA2FF2982991F00872088 /* System */ = { + C1784AF3D57C3616670266F6 = { isa = PBXGroup; children = ( - D43EA3002982993300872088 /* SystemInfo.swift */, + 443C59D5612CFED4001C8AD1 /* Anyline Tire Demo */, + 9AB0F6C55B1BEBC49AE6BFA0 /* Products */, ); - path = System; sourceTree = ""; }; - D4499018299CF5CC00BEEBD5 /* ViewModel */ = { + D1A636076B4B96ECF9BDB1BE /* QR */ = { isa = PBXGroup; children = ( - D4499019299CF5DF00BEEBD5 /* SettingsViewModel.swift */, + 420A2185548C476939CD5037 /* QRCodeReaderViewController.swift */, ); - path = ViewModel; + path = QR; sourceTree = ""; }; - D49F0637297693D60031B752 /* Feedback */ = { + E0C51FE8D426F614EDA5597B /* Components */ = { isa = PBXGroup; children = ( - D49F0639297693EC0031B752 /* View */, - D421C096299BDD4E00A9F5A6 /* ViewModel */, - D49F0638297693E60031B752 /* Controller */, - D4B5370529801C04009CAB53 /* FeedbackButtonActionsDelegate.swift */, + C1FCB5E52078C5C5E8B5569B /* ATDSideButton.swift */, + 45AF252A096B6811DE5CBD11 /* ATDSideTitleLabel.swift */, + 6E20CCB2348A1D5779AD369D /* ATDTextField.swift */, + AA0D513A072F0FA35EEED2BE /* ATDTextLabel.swift */, + 2042E7AF3F98CE816AC74A87 /* ATDTitleLabel.swift */, + 1DBED146B22C6AF08693B5B3 /* ATDTopView.swift */, + B672D5814257084C202F656D /* PDFReaderView.swift */, ); - path = Feedback; + path = Components; sourceTree = ""; }; - D49F0638297693E60031B752 /* Controller */ = { + E6A570D49D23B181862D5DC6 /* Controller */ = { isa = PBXGroup; children = ( - D49F063A2976944B0031B752 /* FeedbackViewController.swift */, + 592EBCA3FF9BC3EC5068119D /* ResultDetailsViewController.swift */, ); path = Controller; sourceTree = ""; }; - D49F0639297693EC0031B752 /* View */ = { + E82518A0B5C6D055E1240A4B /* View */ = { isa = PBXGroup; children = ( - D49F063E2976997A0031B752 /* ButtonsFeedbackView.swift */, - D4CCAC69297E959500CC78C6 /* FeedbackView.swift */, - D4B53703298011B1009CAB53 /* TireDepthFeedbackView.swift */, - D4CCAC6B297E99A900CC78C6 /* TireDepthsFeedbackView.swift */, + 628BA76297A2A7D92E46E45A /* ButtonsFeedbackView.swift */, + DD9A3DA93C8AE03554D6124E /* FeedbackView.swift */, + A5C8C38156D966C80C5CF40A /* TireDepthFeedbackView.swift */, + DD27ED133BF4D5F71179625C /* TireDepthsFeedbackView.swift */, ); path = View; sourceTree = ""; }; - D4AAB8A22996D7E30045D527 /* ViewModel */ = { + EC96517ED54B69E4FCD95A03 /* ViewModel */ = { isa = PBXGroup; children = ( - D4AAB8A32996D7F90045D527 /* ResultDetailsViewModel.swift */, + 00CC8518403D55DB36CAFA0D /* ScanViewModel.swift */, ); path = ViewModel; sourceTree = ""; }; - D4C316F229A60E2B00F836A1 /* Loading */ = { + F1716B231CD02A1A83A12434 /* Feedback */ = { isa = PBXGroup; children = ( - D4C316F329A60E4600F836A1 /* View */, - D4C316FD29A6278200F836A1 /* ViewModel */, - D4C316F429A60E4B00F836A1 /* Controller */, - D4C316F929A60F0500F836A1 /* LoadingButtonActionsDelegate.swift */, + 0221683328A2A4F71635B509 /* FeedbackButtonActionsDelegate.swift */, + 8D913555918E6AE6F6BA8BAB /* Controller */, + E82518A0B5C6D055E1240A4B /* View */, + 7045F4FAFB44E34CF1F56CB0 /* ViewModel */, ); - path = Loading; + path = Feedback; sourceTree = ""; }; - D4C316F329A60E4600F836A1 /* View */ = { + F7BADD19F44B4EC4EBDFDB31 /* ViewModel */ = { isa = PBXGroup; children = ( - D4C316F529A60E6F00F836A1 /* LoadingView.swift */, - D4C316FB29A610B900F836A1 /* AnimationLoadingView.swift */, + 743A1DCAF06FD21B53070DE9 /* ResultDetailsViewModel.swift */, ); - path = View; + path = ViewModel; sourceTree = ""; }; - D4C316F429A60E4B00F836A1 /* Controller */ = { + FF266DC6636B9C10AEC09666 /* Controller */ = { isa = PBXGroup; children = ( - D4C316F729A60E7A00F836A1 /* LoadingViewController.swift */, + DBF323846DAEE03F621DBF05 /* ResultViewController.swift */, ); path = Controller; sourceTree = ""; }; - D4C316FD29A6278200F836A1 /* ViewModel */ = { - isa = PBXGroup; - children = ( - D4C316FE29A6279400F836A1 /* LoadingViewModel.swift */, - ); - path = ViewModel; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - D43E3F9E296573C400F1D384 /* Anyline Tire Demo */ = { + 4970B307CD8FFCB7C0762116 /* Anyline Tire Demo */ = { isa = PBXNativeTarget; - buildConfigurationList = D43E3FB3296573C600F1D384 /* Build configuration list for PBXNativeTarget "Anyline Tire Demo" */; + buildConfigurationList = DAAED4700B88574245FCE484 /* Build configuration list for PBXNativeTarget "Anyline Tire Demo" */; buildPhases = ( - D43E3F9B296573C400F1D384 /* Sources */, - D43E3F9C296573C400F1D384 /* Frameworks */, - D43E3F9D296573C400F1D384 /* Resources */, + 7A8B0CC15C0E90F484F91DD7 /* Sources */, + 6D8B5D5A9070181982C27C8B /* Resources */, + A7AA68BB7B6664B26C0758B7 /* Frameworks */, ); buildRules = ( ); @@ -601,170 +613,165 @@ ); name = "Anyline Tire Demo"; packageProductDependencies = ( - D43E3FFA2966E86200F1D384 /* SnapKit */, - 802B90F5296D80EB0017919D /* Alamofire */, - D4B53700298002C5009CAB53 /* KeychainSwift */, - A0BEEBA52A6FFF7800E27276 /* AnylineTireTreadSdk */, + 4FC06FD0D43DBAB59132C68A /* AnylineTireTreadSdk */, + 36DF23772E8928D0BFDB3967 /* Alamofire */, + 5A8E509B4C8C4AC6C6113DBD /* KeychainSwift */, + AE4098F783D33EDD00174621 /* SnapKit */, ); productName = "Anyline Tire Demo"; - productReference = D43E3F9F296573C400F1D384 /* Anyline Tire Demo.app */; + productReference = 5FA6E900770A1B48D530C0B0 /* Anyline Tire Demo.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - D43E3F97296573C400F1D384 /* Project object */ = { + E5CDB6798FCC7934BA0D0DE6 /* Project object */ = { isa = PBXProject; attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1420; + LastUpgradeCheck = 1200; TargetAttributes = { - D43E3F9E296573C400F1D384 = { - CreatedOnToolsVersion = 14.2; + 4970B307CD8FFCB7C0762116 = { + DevelopmentTeam = 35RHL53WRE; + ProvisioningStyle = Automatic; }; }; }; - buildConfigurationList = D43E3F9A296573C400F1D384 /* Build configuration list for PBXProject "Anyline Tire Demo" */; - compatibilityVersion = "Xcode 14.0"; + buildConfigurationList = 6AAA678835C7FA3FAECC4ABD /* Build configuration list for PBXProject "Anyline Tire Demo" */; + compatibilityVersion = "Xcode 11.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - en, Base, + en, ); - mainGroup = D43E3F96296573C400F1D384; + mainGroup = C1784AF3D57C3616670266F6; packageReferences = ( - D43E3FF92966E86200F1D384 /* XCRemoteSwiftPackageReference "SnapKit" */, - 802B90F4296D80EB0017919D /* XCRemoteSwiftPackageReference "Alamofire" */, - D4B536FF298002C5009CAB53 /* XCRemoteSwiftPackageReference "keychain-swift" */, - A07622DF2A696B5400250387 /* XCRemoteSwiftPackageReference "anyline-tiretread-spm-module" */, + 8BDBD2C18B241087A97EA599 /* XCRemoteSwiftPackageReference "Alamofire" */, + 2E17D9FE7E357A222A39DA30 /* XCRemoteSwiftPackageReference "anyline-tiretread-spm-module" */, + 8308C77CEAF862D126235E79 /* XCRemoteSwiftPackageReference "keychain-swift" */, + 4B5AEA5F9FCA6152FDAB044B /* XCRemoteSwiftPackageReference "SnapKit" */, ); - productRefGroup = D43E3FA0296573C400F1D384 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - D43E3F9E296573C400F1D384 /* Anyline Tire Demo */, + 4970B307CD8FFCB7C0762116 /* Anyline Tire Demo */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - D43E3F9D296573C400F1D384 /* Resources */ = { + 6D8B5D5A9070181982C27C8B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D410046129BB21F8007B476D /* tiretread_sound_low_beep.wav in Resources */, - D43E3FFD2966EC1800F1D384 /* ProximaNova-Light.otf in Resources */, - D474EA3129BB5D9D00C1FABA /* tiretread_sound_stop.wav in Resources */, - D474EA2F29BB5D8F00C1FABA /* tiretread_sound_start.mp3 in Resources */, - D43E3FAF296573C600F1D384 /* LaunchScreen.storyboard in Resources */, - D43E3FFC2966EC1600F1D384 /* ProximaNova-Bold.otf in Resources */, - D43E3FE929659B8900F1D384 /* Localizable.strings in Resources */, - D43E3FAC296573C600F1D384 /* Assets.xcassets in Resources */, - D410046029BB21F8007B476D /* tiretread_sound_high_beep.wav in Resources */, - D474EA2D29BB5A4000C1FABA /* tiretread_focuspoint_found.wav in Resources */, - D43E3FFE2966EC1A00F1D384 /* ProximaNova-Regular.otf in Resources */, + 778185811BD1A1128E44BED5 /* Assets.xcassets in Resources */, + 84C49BAE86A8C61E2789AE51 /* LaunchScreen.storyboard in Resources */, + B43E435CF5FD514D9D4D4245 /* Localizable.strings in Resources */, + CFBC8035DE3560D7ABB142FD /* ProximaNova-Bold.otf in Resources */, + E9B33D39315858DA62E2C2BD /* ProximaNova-Light.otf in Resources */, + C547C5D4DB92F152AC74F0F8 /* ProximaNova-Regular.otf in Resources */, + 3CDCBDE8BEBC15FDA1FB6264 /* tiretread_focuspoint_found.wav in Resources */, + 630302657E9563016A6A475F /* tiretread_sound_beep_too_close.wav in Resources */, + FA806DEF9791249E298CA64D /* tiretread_sound_beep_too_far.wav in Resources */, + 7369D989A1B4FE06EB66969A /* tiretread_sound_start.wav in Resources */, + 29ED2FF6F2DD3987E89BFA6A /* tiretread_sound_stop.wav in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - D43E3F9B296573C400F1D384 /* Sources */ = { + 7A8B0CC15C0E90F484F91DD7 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A0D1F24A2A61826000A6146B /* ColorStruct.swift in Sources */, - A0D1F25B2A61832200A6146B /* SystemInfo.swift in Sources */, - A0D1F2752A61842C00A6146B /* FeedbackViewModel.swift in Sources */, - A0D1F2692A6183D000A6146B /* ResultViewController.swift in Sources */, - A0D1F2722A61841700A6146B /* MeasurementView.swift in Sources */, - A0D1F2632A61839000A6146B /* LoadingButtonActionsDelegate.swift in Sources */, - A0D1F2562A6182F600A6146B /* ATDTitleLabel.swift in Sources */, - A0D1F26A2A6183D800A6146B /* ResultView.swift in Sources */, - A0D1F2572A61830600A6146B /* ImperialSystemView.swift in Sources */, - A0D1F2422A6181BD00A6146B /* AppDelegate.swift in Sources */, - A0D1F25E2A61834300A6146B /* SettingsViewModel.swift in Sources */, - A0D1F2782A61843E00A6146B /* TireDepthsFeedbackView.swift in Sources */, - A0D1F2592A61831400A6146B /* ATDTextLabel.swift in Sources */, - A0D1F27A2A61846500A6146B /* TireDepthFeedbackView.swift in Sources */, - A0D1F25A2A61831B00A6146B /* InfoSettingsView.swift in Sources */, - A0D1F25C2A61832A00A6146B /* ATDTextField.swift in Sources */, - A0D1F2462A61823500A6146B /* KeychainManager.swift in Sources */, - A0D1F2672A6183C000A6146B /* ErrorButtonActionsDelegate.swift in Sources */, - A0D1F26E2A6183F600A6146B /* ResultDetailsViewController.swift in Sources */, - A0D1F2732A61841F00A6146B /* PDFReaderView.swift in Sources */, - A0D1F24B2A61826B00A6146B /* ATDSideButton.swift in Sources */, - A0D1F2512A6182C200A6146B /* UIViewController+Extension.swift in Sources */, - A0D1F24D2A61829000A6146B /* LandingButtonActionsDelegate.swift in Sources */, - A0D1F2702A61840400A6146B /* ResultDetailsView.swift in Sources */, - A0D1F2452A61822200A6146B /* LandingViewModel.swift in Sources */, - A0D1F2772A61843900A6146B /* ATDSideTitleLabel.swift in Sources */, - A0D1F2612A61836D00A6146B /* LoadingView.swift in Sources */, - A0D1F2522A6182D500A6146B /* VolumeButtonObserver.swift in Sources */, - A0D1F2582A61830D00A6146B /* AccuracySpeedSettingsView.swift in Sources */, - A0D1F24C2A61827700A6146B /* TextLandingView.swift in Sources */, - A0D1F2502A6182B600A6146B /* ScanViewController.swift in Sources */, - A0D1F2412A6181BD00A6146B /* SceneDelegate.swift in Sources */, - A0D1F26B2A6183DE00A6146B /* ResultButtonActionsDelegate.swift in Sources */, - A0D1F2662A6183B800A6146B /* ErrorView.swift in Sources */, - A0D1F2602A61836400A6146B /* LoadingViewController.swift in Sources */, - A0D1F2792A61844800A6146B /* FeedbackButtonActionsDelegate.swift in Sources */, - A0D1F26F2A6183FC00A6146B /* FeedbackViewController.swift in Sources */, - A0D1F2552A6182EF00A6146B /* LicenseSettingsView.swift in Sources */, - A0D1F2542A6182E900A6146B /* ButtonsSettingsView.swift in Sources */, - A0D1F2762A61843400A6146B /* ButtonsFeedbackView.swift in Sources */, - A0D1F24F2A6182B100A6146B /* SettingsViewController.swift in Sources */, - A0D1F2642A6183A900A6146B /* ErrorViewController.swift in Sources */, - A0D1F26D2A6183EC00A6146B /* TireTreadMeasurementView.swift in Sources */, - A0D1F2742A61842800A6146B /* FeedbackView.swift in Sources */, - A0D1F26C2A6183E600A6146B /* ButtonsResultView.swift in Sources */, - A0D1F24E2A6182A400A6146B /* String+Extension.swift in Sources */, - A0D1F2482A61824E00A6146B /* LandingView.swift in Sources */, - A0D1F2682A6183C800A6146B /* DescriptionErrorView.swift in Sources */, - A0D1F2442A61821500A6146B /* LandingViewController.swift in Sources */, - A0D1F2432A6181D600A6146B /* UserDefaultsManager.swift in Sources */, - A0D1F25D2A61833900A6146B /* SettingsButtonActionsDelegate.swift in Sources */, - A0D1F25F2A61835C00A6146B /* ScanViewModel.swift in Sources */, - A0D1F2622A61838100A6146B /* AnimationLoadingView.swift in Sources */, - A0D1F2472A61824700A6146B /* ATDTopView.swift in Sources */, - A0D1F2712A61840A00A6146B /* ResultDetailsViewModel.swift in Sources */, - A0D1F2652A6183B000A6146B /* LoadingViewModel.swift in Sources */, - A0D1F2492A61825A00A6146B /* FontStruct.swift in Sources */, - A0D1F2532A6182E100A6146B /* SettingsView.swift in Sources */, + 6A3DED6F32AF230CE411624C /* ATDSideButton.swift in Sources */, + 59C58D5B136AB5E8E50E6CEF /* ATDSideTitleLabel.swift in Sources */, + DAB4121C20787CFBD4E8BC3B /* ATDTextField.swift in Sources */, + A657F5121313A04D7767CA25 /* ATDTextLabel.swift in Sources */, + 40E037705F4EA729263B4531 /* ATDTitleLabel.swift in Sources */, + 87DB8F58F9BAA1992550BE5D /* ATDTopView.swift in Sources */, + 0FA8883A556B2FA320CA2DD2 /* AccuracySpeedSettingsView.swift in Sources */, + 1DB1D383AF7C342F7E94BE9C /* AnimationLoadingView.swift in Sources */, + C6BF2CD1FD27D45201F44A83 /* AppDelegate.swift in Sources */, + 658795794DFEF5319801E7C5 /* ButtonsFeedbackView.swift in Sources */, + 7FDA3B9A76C6BF12C38735C4 /* ButtonsResultView.swift in Sources */, + 4315EDFF1D1D482EB86C2AC6 /* ButtonsSettingsView.swift in Sources */, + CABBA4922CFF9C7D20F87DF6 /* CaptureSpeedView.swift in Sources */, + CE2BF07B703E436BCF4A75D0 /* ColorStruct.swift in Sources */, + 1D17BE143196523D2031D216 /* DescriptionErrorView.swift in Sources */, + 38DB3D561F4C43FB5A8B9727 /* ErrorButtonActionsDelegate.swift in Sources */, + 84C5DAAA08E0024CCBC9F586 /* ErrorView.swift in Sources */, + FE1F9B0BF324FAB9B95F0C81 /* ErrorViewController.swift in Sources */, + CCA9F8DF9C1EDAD294141EA3 /* FeedbackButtonActionsDelegate.swift in Sources */, + 62089BD82CA50593B8FA437A /* FeedbackView.swift in Sources */, + 00D7D9629931D5F9C34F144F /* FeedbackViewController.swift in Sources */, + 66772AB4A13D6B2762CBAAD9 /* FeedbackViewModel.swift in Sources */, + 9DC179F77F904D61DA624801 /* FontStruct.swift in Sources */, + 8AF6D5CED47CF01761428943 /* ImperialSystemView.swift in Sources */, + E42046826C435BB3C8648356 /* InfoSettingsView.swift in Sources */, + A1A43E97D4186089AD2FF75B /* KeychainManager.swift in Sources */, + DEC9D5EECB42273A5F89B447 /* LandingButtonActionsDelegate.swift in Sources */, + DA4553F0DFF86A25ACBF1197 /* LandingTextViewDelegate.swift in Sources */, + 922F01E6511122B7C47A513A /* LandingView.swift in Sources */, + 75DED790D67B3F8F6810A375 /* LandingViewController.swift in Sources */, + 6A88F4119F37EF8E80A6C418 /* LandingViewModel.swift in Sources */, + A5E50D4A5C0CC96AF74C8C8D /* LicenseSettingsView.swift in Sources */, + 53E9BB7C9EFA688DF79A049A /* LoadingButtonActionsDelegate.swift in Sources */, + F526EDD2BB0625673BA4C11C /* LoadingView.swift in Sources */, + 8BF4E284C06302D31E0935A4 /* LoadingViewController.swift in Sources */, + 6E11D51C1B198681FBBBAF50 /* LoadingViewModel.swift in Sources */, + 454F272008A986B435532AE0 /* MeasurementView.swift in Sources */, + 60867B6448E0174B6C94DD34 /* PDFReaderView.swift in Sources */, + F61AC54D164F3F1D35289943 /* QRCodeReaderViewController.swift in Sources */, + AC4D8CE67B84C89878A0FA2A /* RecorderViewController.swift in Sources */, + 752146CA082BED4AE6FA6C33 /* ResultButtonActionsDelegate.swift in Sources */, + 5E7BB7B5DC99420C9F0C7503 /* ResultDetailsView.swift in Sources */, + 35B3D619C9CB0D2485FB93C0 /* ResultDetailsViewController.swift in Sources */, + AB54A1FC59A94B0888996937 /* ResultDetailsViewModel.swift in Sources */, + D6919D00130DD2096F69F352 /* ResultView.swift in Sources */, + 0A1A2396F1C55377451C6AF4 /* ResultViewController.swift in Sources */, + D1C023881D18C8A35E33D669 /* ScanViewController.swift in Sources */, + F81338C8435FEBA076801D69 /* ScanViewModel.swift in Sources */, + D5F2132E906282B4C27F6F83 /* SceneDelegate.swift in Sources */, + 7FAF812248111EAE17B51F8A /* SettingsButtonActionsDelegate.swift in Sources */, + 2E4E98C5AC372ECAECE7BCEA /* SettingsView.swift in Sources */, + 8A6F05DEA64AACAE756353C9 /* SettingsViewController.swift in Sources */, + 61F8BC3D17A20B47266573CC /* SettingsViewModel.swift in Sources */, + 1AC53E9E0FF2699C737155CF /* String+Extension.swift in Sources */, + 015D0AF4D1678D4F78A46880 /* SystemInfo.swift in Sources */, + 927829229CB4F07B5590DDF8 /* TextLandingView.swift in Sources */, + 8355FCC47FD62507211C2312 /* TireDepthFeedbackView.swift in Sources */, + 97EA42AE051CF8B91895393B /* TireDepthsFeedbackView.swift in Sources */, + B2EBB1666A0CF9F779EF8931 /* TireTreadMeasurementView.swift in Sources */, + E867FA59DFE1158EF820BA09 /* UIViewController+Extension.swift in Sources */, + 030CA17364D27E2C86B8D36F /* UserDefaultsManager.swift in Sources */, + D5CBEE1F3E3661B52DBEF1CC /* VolumeButtonObserver.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - D43E3FAD296573C600F1D384 /* LaunchScreen.storyboard */ = { + BE053E1CF96479297B839F59 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( - D43E3FAE296573C600F1D384 /* Base */, + 4EB97AF36FE6ADF80D5595B4 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; - D43E3FEB29659B8900F1D384 /* Localizable.strings */ = { - isa = PBXVariantGroup; - children = ( - D43E3FEA29659B8900F1D384 /* en */, - ); - name = Localizable.strings; - sourceTree = ""; - }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - D43E3FB1296573C600F1D384 /* Debug */ = { + 18C119D4D305BA2D99708F03 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -799,8 +806,8 @@ GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", "$(inherited)", + "DEBUG=1", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -808,23 +815,62 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 9BC34547D662BDC159D1D42F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 35RHL53WRE; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Anyline Tire Demo/Info.plist"; + INFOPLIST_KEY_NSCameraUsageDescription = "Camera permission for capturing video"; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIRequiresFullScreen = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = ( + UIInterfaceOrientationLandscapeLeft, + UIInterfaceOrientationLandscapeRight, + ); + IPHONEOS_DEPLOYMENT_TARGET = 16.4; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.8; + PRODUCT_BUNDLE_IDENTIFIER = com.anyline.showcase.tiretread; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - D43E3FB2296573C600F1D384 /* Release */ = { + D2397B9C3A9DEAAA948EC469 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -862,17 +908,19 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.2; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; name = Release; }; - D43E3FB4296573C600F1D384 /* Debug */ = { + D9FEFD91F8F16C184F8617E8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -881,63 +929,29 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 35RHL53WRE; - FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Anyline Tire Demo/Info.plist"; INFOPLIST_KEY_NSCameraUsageDescription = "Camera permission for capturing video"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIRequiresFullScreen = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", + INFOPLIST_KEY_UISupportedInterfaceOrientations = ( + UIInterfaceOrientationLandscapeLeft, + UIInterfaceOrientationLandscapeRight, ); - MARKETING_VERSION = 1.0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.anyline.showcase.tiretread; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - D43E3FB5296573C600F1D384 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 35RHL53WRE; - FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "Anyline Tire Demo/Info.plist"; - INFOPLIST_KEY_NSCameraUsageDescription = "Camera permission for capturing video"; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIRequiresFullScreen = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 16.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.0.8; PRODUCT_BUNDLE_IDENTIFIER = com.anyline.showcase.tiretread; - PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -945,44 +959,36 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - D43E3F9A296573C400F1D384 /* Build configuration list for PBXProject "Anyline Tire Demo" */ = { + 6AAA678835C7FA3FAECC4ABD /* Build configuration list for PBXProject "Anyline Tire Demo" */ = { isa = XCConfigurationList; buildConfigurations = ( - D43E3FB1296573C600F1D384 /* Debug */, - D43E3FB2296573C600F1D384 /* Release */, + 18C119D4D305BA2D99708F03 /* Debug */, + D2397B9C3A9DEAAA948EC469 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; - D43E3FB3296573C600F1D384 /* Build configuration list for PBXNativeTarget "Anyline Tire Demo" */ = { + DAAED4700B88574245FCE484 /* Build configuration list for PBXNativeTarget "Anyline Tire Demo" */ = { isa = XCConfigurationList; buildConfigurations = ( - D43E3FB4296573C600F1D384 /* Debug */, - D43E3FB5296573C600F1D384 /* Release */, + 9BC34547D662BDC159D1D42F /* Debug */, + D9FEFD91F8F16C184F8617E8 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 802B90F4296D80EB0017919D /* XCRemoteSwiftPackageReference "Alamofire" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/Alamofire/Alamofire"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 5.0.0; - }; - }; - A07622DF2A696B5400250387 /* XCRemoteSwiftPackageReference "anyline-tiretread-spm-module" */ = { + 2E17D9FE7E357A222A39DA30 /* XCRemoteSwiftPackageReference "anyline-tiretread-spm-module" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Anyline/anyline-tiretread-spm-module"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.0.0; + minimumVersion = 3.0.0; }; }; - D43E3FF92966E86200F1D384 /* XCRemoteSwiftPackageReference "SnapKit" */ = { + 4B5AEA5F9FCA6152FDAB044B /* XCRemoteSwiftPackageReference "SnapKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SnapKit/SnapKit.git"; requirement = { @@ -990,7 +996,7 @@ kind = branch; }; }; - D4B536FF298002C5009CAB53 /* XCRemoteSwiftPackageReference "keychain-swift" */ = { + 8308C77CEAF862D126235E79 /* XCRemoteSwiftPackageReference "keychain-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/evgenyneu/keychain-swift.git"; requirement = { @@ -998,30 +1004,38 @@ kind = branch; }; }; + 8BDBD2C18B241087A97EA599 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 802B90F5296D80EB0017919D /* Alamofire */ = { + 36DF23772E8928D0BFDB3967 /* Alamofire */ = { isa = XCSwiftPackageProductDependency; - package = 802B90F4296D80EB0017919D /* XCRemoteSwiftPackageReference "Alamofire" */; + package = 8BDBD2C18B241087A97EA599 /* XCRemoteSwiftPackageReference "Alamofire" */; productName = Alamofire; }; - A0BEEBA52A6FFF7800E27276 /* AnylineTireTreadSdk */ = { + 4FC06FD0D43DBAB59132C68A /* AnylineTireTreadSdk */ = { isa = XCSwiftPackageProductDependency; - package = A07622DF2A696B5400250387 /* XCRemoteSwiftPackageReference "anyline-tiretread-spm-module" */; + package = 2E17D9FE7E357A222A39DA30 /* XCRemoteSwiftPackageReference "anyline-tiretread-spm-module" */; productName = AnylineTireTreadSdk; }; - D43E3FFA2966E86200F1D384 /* SnapKit */ = { + 5A8E509B4C8C4AC6C6113DBD /* KeychainSwift */ = { isa = XCSwiftPackageProductDependency; - package = D43E3FF92966E86200F1D384 /* XCRemoteSwiftPackageReference "SnapKit" */; - productName = SnapKit; + package = 8308C77CEAF862D126235E79 /* XCRemoteSwiftPackageReference "keychain-swift" */; + productName = KeychainSwift; }; - D4B53700298002C5009CAB53 /* KeychainSwift */ = { + AE4098F783D33EDD00174621 /* SnapKit */ = { isa = XCSwiftPackageProductDependency; - package = D4B536FF298002C5009CAB53 /* XCRemoteSwiftPackageReference "keychain-swift" */; - productName = KeychainSwift; + package = 4B5AEA5F9FCA6152FDAB044B /* XCRemoteSwiftPackageReference "SnapKit" */; + productName = SnapKit; }; /* End XCSwiftPackageProductDependency section */ }; - rootObject = D43E3F97296573C400F1D384 /* Project object */; + rootObject = E5CDB6798FCC7934BA0D0DE6 /* Project object */; } diff --git a/Anyline Tire Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Anyline Tire Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 231f9dc..b77f90e 100644 --- a/Anyline Tire Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Anyline Tire Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,17 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Alamofire/Alamofire", "state" : { - "revision" : "bc268c28fb170f494de9e9927c371b8342979ece", - "version" : "5.7.1" - } - }, - { - "identity" : "anyline-tiretread-spm-module", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Anyline/anyline-tiretread-spm-module", - "state" : { - "revision" : "b805e64cc1cdc55c3d67d9450c457724741eee3d", - "version" : "2.1.0" + "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad", + "version" : "5.8.1" } }, { @@ -24,7 +15,7 @@ "location" : "https://github.com/evgenyneu/keychain-swift.git", "state" : { "branch" : "master", - "revision" : "c1fde55798b164cad44b5e23cfa2f0f1ebcd76af" + "revision" : "95219e66c94b6ba25e7d6ece7aecf1ea13134174" } }, { @@ -33,7 +24,7 @@ "location" : "https://github.com/SnapKit/SnapKit.git", "state" : { "branch" : "develop", - "revision" : "58320fe80522414bf3a7e24c88123581dc586752" + "revision" : "4d52cc3768ec5d345827b063dfa59e98a929965d" } } ], diff --git a/Anyline Tire Demo.xcodeproj/xcshareddata/xcschemes/Anyline Tire Demo.xcscheme b/Anyline Tire Demo.xcodeproj/xcshareddata/xcschemes/Anyline Tire Demo.xcscheme new file mode 100644 index 0000000..162eddd --- /dev/null +++ b/Anyline Tire Demo.xcodeproj/xcshareddata/xcschemes/Anyline Tire Demo.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Anyline Tire Demo/Assets.xcassets/qr_code_icon.imageset/Contents.json b/Anyline Tire Demo/Assets.xcassets/qr_code_icon.imageset/Contents.json new file mode 100644 index 0000000..204d3bc --- /dev/null +++ b/Anyline Tire Demo/Assets.xcassets/qr_code_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "QR Code Icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Anyline Tire Demo/Assets.xcassets/qr_code_icon.imageset/QR Code Icon.png b/Anyline Tire Demo/Assets.xcassets/qr_code_icon.imageset/QR Code Icon.png new file mode 100644 index 0000000..5ae8a93 Binary files /dev/null and b/Anyline Tire Demo/Assets.xcassets/qr_code_icon.imageset/QR Code Icon.png differ diff --git a/Anyline Tire Demo/DataStorage/UserDefaultsManager.swift b/Anyline Tire Demo/DataStorage/UserDefaultsManager.swift index 8ea07db..fbb6e16 100644 --- a/Anyline Tire Demo/DataStorage/UserDefaultsManager.swift +++ b/Anyline Tire Demo/DataStorage/UserDefaultsManager.swift @@ -1,4 +1,5 @@ import Foundation +import AnylineTireTreadSdk struct UserDefaultsManager { static var shared = UserDefaultsManager() @@ -39,4 +40,20 @@ struct UserDefaultsManager { UserDefaults.standard.set(newValue, forKey: "imperialSystem") } } + + var scanSpeed: ScanSpeed { + get { + switch Int32(UserDefaults.standard.integer(forKey: "scanSpeed")) { + case ScanSpeed.fast.ordinal: + return .fast + case ScanSpeed.slow.ordinal: + return .slow + default: + return .slow + } + } + set { + UserDefaults.standard.setValue(newValue.ordinal, forKey: "scanSpeed") + } + } } diff --git a/Anyline Tire Demo/Info.plist b/Anyline Tire Demo/Info.plist index d9a293d..7282393 100644 --- a/Anyline Tire Demo/Info.plist +++ b/Anyline Tire Demo/Info.plist @@ -2,13 +2,29 @@ + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 ITSAppUsesNonExemptEncryption UIAppFonts - ProximaNova-Bold.otf - ProximaNova-Light.otf ProximaNova-Regular.otf + ProximaNova-Light.otf + ProximaNova-Bold.otf UIApplicationSceneManifest diff --git a/Anyline Tire Demo/Presentation/Components/ATDSideButton.swift b/Anyline Tire Demo/Presentation/Components/ATDSideButton.swift index d654edd..4195993 100644 --- a/Anyline Tire Demo/Presentation/Components/ATDSideButton.swift +++ b/Anyline Tire Demo/Presentation/Components/ATDSideButton.swift @@ -1,19 +1,25 @@ import UIKit final class ATDSideButton: UIButton { - + // MARK: - Private Properties private let cornerRadius: CGFloat = 15 - + // MARK: - Init init(title: String) { super.init(frame: .zero) setup(title: title) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override var isEnabled: Bool { + didSet { + backgroundColor = isEnabled ? ColorStruct.anylineBlue : ColorStruct.skyGrey + } + } } // MARK: - Private func @@ -21,6 +27,7 @@ private extension ATDSideButton { func setup(title: String) { setTitle(title, for: .normal) setTitleColor(ColorStruct.snowWhite, for: .normal) + setTitleColor(ColorStruct.skyGrey, for: .highlighted) backgroundColor = ColorStruct.anylineBlue contentHorizontalAlignment = .center layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner] diff --git a/Anyline Tire Demo/Presentation/Components/ATDTopView.swift b/Anyline Tire Demo/Presentation/Components/ATDTopView.swift index 8f0604d..e1be0da 100644 --- a/Anyline Tire Demo/Presentation/Components/ATDTopView.swift +++ b/Anyline Tire Demo/Presentation/Components/ATDTopView.swift @@ -1,16 +1,16 @@ import UIKit class ATDTopView: UIView { - + // MARK: - UI Properties private lazy var logoImageView: UIImageView = { let imageView = UIImageView() - imageView.image = UIImage(named: "anyline_logo_black") + imageView.image = UIImage(named: "anyline_Logo_Black") imageView.contentMode = .scaleAspectFit imageView.tintColor = .white return imageView }() - + lazy var appNameLabel: UILabel = { let label = UILabel() let boldText = "TIRE TREAD" @@ -25,7 +25,7 @@ class ATDTopView: UIView { label.translatesAutoresizingMaskIntoConstraints = false return label }() - + // MARK: - Private Properties // MARK: - Init @@ -45,27 +45,27 @@ class ATDTopView: UIView { // MARK: - Private functions private extension ATDTopView { - + // MARK: - Setup UI func configureView() { backgroundColor = ColorStruct.stoneGrey } - + func addSubviews() { self.addSubview(logoImageView) self.addSubview(appNameLabel) } - + func setupLayout() { - + self.logoImageView.snp.makeConstraints { make in make.height.equalTo(35) make.width.equalTo(150) make.leading.equalTo(25) make.top.equalTo(9) - make.bottom.equalTo(-9) + make.bottom.greaterThanOrEqualTo(-9) } - + self.appNameLabel.snp.makeConstraints { make in make.width.equalTo(300) make.trailing.equalTo(16) diff --git a/Anyline Tire Demo/Presentation/Features/Feedback/Controller/FeedbackViewController.swift b/Anyline Tire Demo/Presentation/Features/Feedback/Controller/FeedbackViewController.swift index 2b23d45..7d4512c 100644 --- a/Anyline Tire Demo/Presentation/Features/Feedback/Controller/FeedbackViewController.swift +++ b/Anyline Tire Demo/Presentation/Features/Feedback/Controller/FeedbackViewController.swift @@ -131,9 +131,9 @@ extension FeedbackViewController: FeedbackButtonActionsDelegate { let safeMiddleValue = Double(middleValue), let safeRightValue = Double(rightValue) { - let leftTreadResultRegion = TreadResultRegion.companion.doInitMm(isAvailable: true, confidence: 100, value: safeLeftValue) - let middleTreadResultRegion = TreadResultRegion.companion.doInitMm(isAvailable: true, confidence: 100, value: safeMiddleValue) - let rightTreadResultRegion = TreadResultRegion.companion.doInitMm(isAvailable: true, confidence: 100, value: safeRightValue) + let leftTreadResultRegion = TreadResultRegion.companion.doInitMm(isAvailable: true, value: safeLeftValue) + let middleTreadResultRegion = TreadResultRegion.companion.doInitMm(isAvailable: true, value: safeMiddleValue) + let rightTreadResultRegion = TreadResultRegion.companion.doInitMm(isAvailable: true, value: safeRightValue) let treadResultRegions = [leftTreadResultRegion, middleTreadResultRegion, rightTreadResultRegion] diff --git a/Anyline Tire Demo/Presentation/Features/Landing/Controller/LandingViewController.swift b/Anyline Tire Demo/Presentation/Features/Landing/Controller/LandingViewController.swift index 88a6a9a..c4ba7fe 100644 --- a/Anyline Tire Demo/Presentation/Features/Landing/Controller/LandingViewController.swift +++ b/Anyline Tire Demo/Presentation/Features/Landing/Controller/LandingViewController.swift @@ -1,15 +1,34 @@ import UIKit import SnapKit +import AnylineTireTreadSdk + +enum LandingViewDisplayMode { + case `default` + case intro +} class LandingViewController: UIViewController { + enum Constants { + static let userDefaultsFlag_seenIntroVideo = "userDefaultsFlag_seenIntroVideo" + } + + private var displayMode: LandingViewDisplayMode? { + didSet { + if let displayMode = displayMode { + landingView.setDisplayMode(displayMode) + landingView.startButton.isEnabled = true + } + } + } + // MARK: - UI properties private var topView: ATDTopView = { let view = ATDTopView() return view }() - private var customView: LandingView = { + private var landingView: LandingView = { let view = LandingView() return view }() @@ -29,7 +48,20 @@ class LandingViewController: UIViewController { configureView() addSubviews() setupLayout() - customView.delegate = self + landingView.delegate = self + + landingView.startButton.isEnabled = false + landingViewModel.tryInitializeSdk(context: self) { [weak self] success, errorMsg in + self?.landingView.startButton.isEnabled = true + if !success { + self?.showError(error: errorMsg ?? "Unknown error") + } + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + landingView.startButton.isEnabled = true } } @@ -41,18 +73,17 @@ private extension LandingViewController { func addSubviews() { self.view.addSubview(topView) - self.view.addSubview(customView) + self.view.addSubview(landingView) } func setupLayout() { - topView.snp.makeConstraints { make in make.height.equalTo(52) make.width.equalToSuperview() make.top.leading.trailing.equalToSuperview() } - customView.snp.makeConstraints({ make in + landingView.snp.makeConstraints({ make in make.top.equalTo(topView.snp.bottom) make.leading.trailing.bottom.equalToSuperview() }) @@ -62,26 +93,63 @@ private extension LandingViewController { // MARK: - LandingButtonActionsDelegate extension LandingViewController: LandingButtonActionsDelegate { func startButtonTapped() { - customView.startButton.isEnabled = false - landingViewModel.tryInitializeSdk(context: self) + if !UserDefaults.standard.bool(forKey: Constants.userDefaultsFlag_seenIntroVideo) { + displayMode = .intro + UserDefaults.standard.set(true, forKey: Constants.userDefaultsFlag_seenIntroVideo) + } else { + displayMode = .default + landingView.startButton.isEnabled = false + landingViewModel.tryInitializeSdk(context: self) { [weak self] success, errorMsg in + self?.landingView.startButton.isEnabled = true + guard let self = self, success else { + self?.showError(error: errorMsg ?? "Unknown error") + return + } + self.landingViewModel.requestPermissionsAndProceed(context: self) { success, errorMsg in + self.startScanningScreen() + } + } + } } func settingsButtonTapped() { let settingsVC = SettingsViewController() self.navigationController?.pushViewController(settingsVC, animated: true) } + + func cancelButtonTapped() { + displayMode = .default + } + + func tutorialButtonTapped() { + displayMode = .intro + } + + func startScanningScreen() { + landingView.startButton.isEnabled = true + let scanVC = ScanViewController() + scanVC.delegate = self + self.navigationController?.pushViewController(scanVC, animated: true) + } } // MARK: - LandingViewModelDelegate extension LandingViewController: LandingViewModelDelegate { func authenticationSuccessfully() { - customView.startButton.isEnabled = true - let scanVC = ScanViewController() - self.navigationController?.pushViewController(scanVC, animated: true) + startScanningScreen() } func showError(error: String) { - customView.startButton.isEnabled = true + landingView.startButton.isEnabled = true self.displayAlert(title: "error.title".localized(), message: error) } } + +extension LandingViewController: ScanViewControllerDelegate { + + func startRecorderFlow(uuid: String) { + self.navigationController?.popToViewController(self, animated: false) + let vc = RecorderViewController(uuid: uuid) + self.navigationController?.pushViewController(vc, animated: false) + } +} diff --git a/Anyline Tire Demo/Presentation/Features/Landing/Controller/RecorderViewController.swift b/Anyline Tire Demo/Presentation/Features/Landing/Controller/RecorderViewController.swift new file mode 100644 index 0000000..d78f618 --- /dev/null +++ b/Anyline Tire Demo/Presentation/Features/Landing/Controller/RecorderViewController.swift @@ -0,0 +1,177 @@ +import UIKit +import AnylineTireTreadSdk + +class RecorderViewController: UIViewController { + + var uuid: String + + init(uuid: String) { + self.uuid = uuid + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var topView: ATDTopView = { + return ATDTopView() + }() + + private var textView: UITextView = { + let textView = UITextView() + + textView.backgroundColor = .white + textView.textColor = .black + textView.isScrollEnabled = false + textView.contentInset = .zero + textView.isEditable = false + textView.isSelectable = false + textView.translatesAutoresizingMaskIntoConstraints = false + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = 10 + + let tutorialText = "recorder.loading.message".localized() + var attributedString = NSMutableAttributedString(string: tutorialText) + + let string = attributedString.string as NSString + let range = NSRange(location: 0, length: string.length) + + // Set the font + attributedString.addAttribute(.font, value: FontStruct.proximaNovaRegular24!, range: range) + attributedString.addAttribute(.foregroundColor, value: UIColor.black, range: range) + + // line height + attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range) + + textView.attributedText = attributedString + return textView + }() + + override func viewDidLoad() { + super.viewDidLoad() + + addSubviews() + setupLayout() + + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.7) { [weak self] in + guard let self = self else { return } + self.startQRCodeScanForTireIDFeedback(uuid: self.uuid) + } + } + + private func addSubviews() { + self.view.addSubview(topView) + self.view.addSubview(textView) + } + + private func setupLayout() { + view.backgroundColor = ColorStruct.snowWhite + + topView.snp.makeConstraints { make in + make.height.equalTo(52) + make.width.equalToSuperview() + make.top.leading.trailing.equalToSuperview() + } + + textView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.centerY.equalToSuperview() + } + } + + private func startQRCodeScanForTireIDFeedback(uuid: String) { + + // scannerViewController?.view.isHidden = true + // show the QR Code + let message = "qrcodereader.guide_text.tire_id".localized() + QRCodeReaderViewController.showReader(over: self.navigationController!, + scanMode: .tireId, + msg: message, + animated: true) { [weak self] qrViewController, value in + guard let value = value else { + // User tapped Cancel + self?.navigationController?.popViewController(animated: false) + return + } + self?.showSendFeedbackConfirmation(over: qrViewController, uuid: uuid, tireId: value) + } + + textView.isHidden = true + } + + private func showSendFeedbackConfirmation(over qrViewController: QRCodeReaderViewController, uuid: String, tireId: String) { + + let message = String(format: "%@\n\n%@\n\n%@", + "recorder.msg.scanned_tire_id_1".localized(), + tireId, "recorder.msg.scanned_tire_id_2".localized()) + let alert = UIAlertController(title: "recorder.alert.title".localized(), + message: message, preferredStyle: .alert) + + // buttons shown in this order. + let buttonTitles = [ + "recorder.button.send", + "recorder.button.title.rescan", + "recorder.button.title.abort" + ] + + for buttonTitle in buttonTitles { + let title = buttonTitle.localized() + + alert.addAction(.init(title: title, style: .default) { [weak self] _ in + guard let self = self else { return } + switch buttonTitle { + case "recorder.button.title.rescan": + qrViewController.restart() + break + case "recorder.button.send": + self.sendTireIdFeedback(uuid: uuid, tireId: tireId, qrViewController: qrViewController) + break + case "recorder.button.title.abort": + qrViewController.dismiss(animated: false) + self.navigationController?.popToRootViewController(animated: true) + break + default: + break + } + }) + } + qrViewController.present(alert, animated: true) + } + + private func sendTireIdFeedback(uuid: String, tireId: String, qrViewController: QRCodeReaderViewController) { + var alertTitle = "recorder.alert.title".localized() + var alertMessage = "recorder.alert.message_success".localized() + var actions: [UIAlertAction] = [] + + AnylineTireTreadSdk.companion.sendTireIdFeedback(measurementUuid: uuid, tireId: tireId) { [weak self] in + let okButtonTitle = "recorder.button.title.ok".localized() + actions.append(.init(title: okButtonTitle, style: .default) { [weak self] _ in + qrViewController.dismiss(animated: false) + self?.navigationController?.popToRootViewController(animated: true) + }) + self?.presentAlert(over: qrViewController, title: alertTitle, message: alertMessage, actions: actions) + + } onFailure: { [weak self] error in + alertTitle = "recorder.alert.title".localized() + alertMessage = "recorder.alert.message_failure".localized() + actions.append(.init(title: "recorder.alert.btn_failure".localized(), style: .default) { [weak self] _ in + self?.sendTireIdFeedback(uuid: uuid, tireId: tireId, qrViewController: qrViewController) + }) + actions.append(.init(title: "recorder.button.title.abort".localized(), style: .default) { [weak self] _ in + qrViewController.dismiss(animated: false) + self?.navigationController?.popViewController(animated: true) + }) + self?.presentAlert(over: qrViewController, title: alertTitle, message: alertMessage, actions: actions) + } + } + + private func presentAlert(over qrViewController: QRCodeReaderViewController, title: String, message: String, actions: [UIAlertAction]) { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + for action in actions { + alert.addAction(action) + } + qrViewController.present(alert, animated: true) + } +} diff --git a/Anyline Tire Demo/Presentation/Features/Landing/LandingButtonActionsDelegate.swift b/Anyline Tire Demo/Presentation/Features/Landing/LandingButtonActionsDelegate.swift index 5cf1d32..1ff506e 100644 --- a/Anyline Tire Demo/Presentation/Features/Landing/LandingButtonActionsDelegate.swift +++ b/Anyline Tire Demo/Presentation/Features/Landing/LandingButtonActionsDelegate.swift @@ -3,4 +3,6 @@ import Foundation protocol LandingButtonActionsDelegate: AnyObject { func startButtonTapped() func settingsButtonTapped() + func cancelButtonTapped() + func tutorialButtonTapped() } diff --git a/Anyline Tire Demo/Presentation/Features/Landing/LandingTextViewDelegate.swift b/Anyline Tire Demo/Presentation/Features/Landing/LandingTextViewDelegate.swift new file mode 100644 index 0000000..6d0e657 --- /dev/null +++ b/Anyline Tire Demo/Presentation/Features/Landing/LandingTextViewDelegate.swift @@ -0,0 +1,6 @@ +import Foundation + +protocol LandingTextViewDelegate: AnyObject { + func startTapped() + func tutorialTapped() +} diff --git a/Anyline Tire Demo/Presentation/Features/Landing/View/LandingView.swift b/Anyline Tire Demo/Presentation/Features/Landing/View/LandingView.swift index 15dc48b..11ca77a 100644 --- a/Anyline Tire Demo/Presentation/Features/Landing/View/LandingView.swift +++ b/Anyline Tire Demo/Presentation/Features/Landing/View/LandingView.swift @@ -1,4 +1,5 @@ import UIKit +import WebKit class LandingView: UIView { @@ -15,8 +16,45 @@ class LandingView: UIView { return button }() + private lazy var cancelButton: ATDSideButton = { + let button = ATDSideButton(title: "landing.button.cancel".localized()) + button.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) + return button + }() + + private lazy var tutorialButton: ATDSideButton = { + let button = ATDSideButton(title: "landing.button.tutorial".localized()) + button.addTarget(self, action: #selector(tutorialButtonTapped), for: .touchUpInside) + return button + }() + private lazy var textView: TextLandingView = { let view = TextLandingView() + view.delegate = self + return view + }() + + // you need the PRO account to embed using video rather than an iframe: https://stackoverflow.com/a/36042672 + private lazy var webView: WKWebView = { + let view = WKWebView() + let vimeoVideoID = 774805672 + + // can't get autoplay or playsinline to work at least on iOS / Safari + // the docs say that for autoplay to work on Safari, muted should be 1. But that + // doesn't appear to be the case still + let vimeoVideoURL = "https://player.vimeo.com/video/\(vimeoVideoID)?background=0&autoplay=1&muted=0&playsinline=1" + + let htmlCode = """ + + +""" + let htmlString = """ + +\(htmlCode) +""" + + view.loadHTMLString(htmlString, baseURL: nil) + view.scrollView.isScrollEnabled = false return view }() @@ -33,10 +71,34 @@ class LandingView: UIView { setupLayout() } + func setDisplayMode(_ displayMode: LandingViewDisplayMode) { + switch displayMode { + case .default: + startButton.isHidden = false + textView.isHidden = false + webView.isHidden = true + settingsButton.isHidden = false + cancelButton.isHidden = true + tutorialButton.isHidden = false + + case .intro: + startButton.isHidden = true + webView.isHidden = false + textView.isHidden = true + settingsButton.isHidden = true + cancelButton.isHidden = false + tutorialButton.isHidden = true + } + + let cancelBtnTitle = displayMode == .intro ? "landing.button.back".localized() : + "landing.button.cancel".localized() + + cancelButton.setTitle(cancelBtnTitle, for: .normal) + } + required init?(coder: NSCoder) { super.init(coder: coder) } - } // MARK: - Private functions @@ -49,11 +111,15 @@ private extension LandingView { func addSubviews() { self.addSubview(startButton) + self.addSubview(cancelButton) self.addSubview(settingsButton) + self.addSubview(tutorialButton) + self.addSubview(webView) self.addSubview(textView) } func setupLayout() { + startButton.snp.makeConstraints({ make in make.top.equalTo(safeAreaLayoutGuide.snp.top).offset(20) make.trailing.equalTo(0) @@ -61,6 +127,15 @@ private extension LandingView { make.height.equalTo(80) }) + // slightly below start (but above settings by at least as much) + tutorialButton.snp.makeConstraints({ make in + make.top.equalTo(startButton.snp.bottom).offset(10) + make.bottom.lessThanOrEqualTo(settingsButton.snp.top).offset(-10) + make.trailing.equalTo(0) + make.width.equalTo(150) + make.height.equalTo(80) + }) + settingsButton.snp.makeConstraints({ make in make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).offset(-20) make.trailing.equalTo(0) @@ -68,6 +143,22 @@ private extension LandingView { make.height.equalTo(80) }) + // occupies same space as settings + cancelButton.snp.makeConstraints({ make in + make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).offset(-20) + make.trailing.equalTo(0) + make.width.equalTo(150) + make.height.equalTo(80) + }) + + webView.snp.makeConstraints { make in + // make.top.equalTo(self.safeAreaLayoutGuide.snp.top) + // make.bottom.equalTo(self.safeAreaLayoutGuide.snp.bottom) + make.top.bottom.equalTo(self.safeAreaLayoutGuide).offset(25) + make.leading.equalTo(40) + make.trailing.equalTo(startButton.snp.leading).offset(-40) + } + textView.snp.makeConstraints { make in make.top.bottom.equalTo(0) make.leading.equalTo(40) @@ -85,4 +176,24 @@ private extension LandingView { func settingsButtonTapped() { delegate?.settingsButtonTapped() } + + @objc + func cancelButtonTapped() { + delegate?.cancelButtonTapped() + } + + @objc + func tutorialButtonTapped() { + delegate?.tutorialButtonTapped() + } +} + +extension LandingView: LandingTextViewDelegate { + func startTapped() { + delegate?.startButtonTapped() + } + + func tutorialTapped() { + delegate?.tutorialButtonTapped() + } } diff --git a/Anyline Tire Demo/Presentation/Features/Landing/View/TextLandingView.swift b/Anyline Tire Demo/Presentation/Features/Landing/View/TextLandingView.swift index bde090a..9f86391 100644 --- a/Anyline Tire Demo/Presentation/Features/Landing/View/TextLandingView.swift +++ b/Anyline Tire Demo/Presentation/Features/Landing/View/TextLandingView.swift @@ -1,7 +1,12 @@ import UIKit -class TextLandingView: UIView { - +class TextLandingView: UIView, UIGestureRecognizerDelegate { + + enum Constants { + static let documentationUrlFull = "https://tiretreaddocu.anyline.com" + static let documentationUrlShort = "tiretreaddocu.anyline.com" + } + // MARK: - UI properties private lazy var welcomeLabel: UILabel = { let label = UILabel() @@ -13,34 +18,63 @@ class TextLandingView: UIView { return label }() - private lazy var startLabel: UILabel = { - let label = UILabel() - let string = "To start scanning, press the START button" - let attributedString = NSMutableAttributedString(string: string) + private lazy var startTextView: UITextView = { + let textView = UITextView() + + textView.backgroundColor = .white + textView.textColor = .black + textView.isScrollEnabled = false + textView.contentInset = .zero + textView.isEditable = false + textView.isSelectable = false + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = 10 - // Set the font to Helvetica - attributedString.addAttribute(.font, value: FontStruct.proximaNovaRegular24!, range: NSRange(location: 0, length: string.count)) - attributedString.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(location: 0, length: string.count)) + let tutorialText = "landing.text.tutorial".localized() + let startScanningText = "landing.text.start".localized() - // Set the color and font weight for the word "START" - let startRange = (string as NSString).range(of: "START") - attributedString.addAttribute(.foregroundColor, value: ColorStruct.anylineBlue, range: startRange) - attributedString.addAttribute(.font, value: FontStruct.proximaNovaBold24!, range: startRange) + var attributedString = NSMutableAttributedString(string: tutorialText) + attributedString.append(.init(string: "\n")) + attributedString.append(.init(.init(startScanningText))) + + let string = attributedString.string as NSString + let range = NSRange(location: 0, length: string.length) + + // Set the font + attributedString.addAttribute(.font, value: FontStruct.proximaNovaRegular24!, range: range) + attributedString.addAttribute(.foregroundColor, value: UIColor.black, range: range) + + // line height + attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range) + + // Bold the tutorial line + let tutorialRange = string.range(of: tutorialText) + attributedString.addAttribute(.font, value: FontStruct.proximaNovaBold24!, range: tutorialRange) + + // Set the attributes for the word "START" and "TUTORIAL" + var blueRange = string.range(of: "landing.button.start".localized()) + attributedString.addAttribute(.foregroundColor, value: ColorStruct.anylineBlue, range: blueRange) + attributedString.addAttribute(.font, value: FontStruct.proximaNovaBold24!, range: blueRange) + + blueRange = string.range(of: "landing.button.tutorial".localized()) + attributedString.addAttribute(.foregroundColor, value: ColorStruct.anylineBlue, range: blueRange) + attributedString.addAttribute(.font, value: FontStruct.proximaNovaBold24!, range: blueRange) // Create a label and set the attributed text - label.attributedText = attributedString - return label + textView.attributedText = attributedString + return textView }() private lazy var infoLabel: UILabel = { let label = UILabel() - let string = "For best scanning practices, please go to tiretreaddocu.anyline.com" + let string = "landing.text.info".localized() let attributedString = NSMutableAttributedString(string: string) attributedString.addAttribute(.font, value: FontStruct.proximaNovaRegular20!, range: NSRange(location: 0, length: string.count)) // Add an underline to the URL - let urlRange = (string as NSString).range(of: "tiretreaddocu.anyline.com") + let urlRange = (string as NSString).range(of: Constants.documentationUrlShort) attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: urlRange) // Add a tap gesture recognizer to handle clicks on the URL @@ -53,10 +87,10 @@ class TextLandingView: UIView { label.textColor = .black return label }() - - // MARK: - Private Properties - + + // MARK: - Public properties + var delegate: LandingTextViewDelegate? // MARK: - Init override init(frame: CGRect) { @@ -64,6 +98,11 @@ class TextLandingView: UIView { configureView() addSubviews() setupLayout() + + // add gesture recognizer for the text view + let tap = UITapGestureRecognizer(target: self, action: #selector(handleTextViewTap(_:))) + tap.delegate = self + startTextView.addGestureRecognizer(tap) } required init?(coder: NSCoder) { @@ -72,11 +111,34 @@ class TextLandingView: UIView { // MARK: - Actions @objc private func handleTap(_ gesture: UITapGestureRecognizer) { - if let url = URL(string: "https://tiretreaddocu.anyline.com") { + if let url = URL(string: Constants.documentationUrlFull) { UIApplication.shared.open(url, options: [:], completionHandler: nil) } } - + + @objc + func handleTextViewTap(_ sender: UITapGestureRecognizer) { + let myTextView = sender.view as! UITextView + let layoutManager = myTextView.layoutManager + + var location = sender.location(in: myTextView) + location.x -= myTextView.textContainerInset.left; + location.y -= myTextView.textContainerInset.top; + + let characterIndex = layoutManager.characterIndex(for: location, + in: myTextView.textContainer, + fractionOfDistanceBetweenInsertionPoints: nil) + + let string = myTextView.attributedText.string as NSString + let startRange = string.range(of: "landing.button.start".localized()) + let tutorialRange = string.range(of: "landing.button.tutorial".localized()) + + if characterIndex >= startRange.location && characterIndex < startRange.location + startRange.length { + delegate?.startTapped() + } else if characterIndex >= tutorialRange.location && characterIndex < tutorialRange.location + tutorialRange.length { + delegate?.tutorialTapped() + } + } } // MARK: - Private functions @@ -89,7 +151,7 @@ private extension TextLandingView { func addSubviews() { self.addSubview(welcomeLabel) - self.addSubview(startLabel) + self.addSubview(startTextView) self.addSubview(infoLabel) } @@ -98,7 +160,7 @@ private extension TextLandingView { make.leading.top.trailing.equalTo(20) }) - startLabel.snp.makeConstraints({ make in + startTextView.snp.makeConstraints({ make in make.top.equalTo(welcomeLabel.snp.bottom).offset(30) make.leading.trailing.equalTo(20) }) diff --git a/Anyline Tire Demo/Presentation/Features/Landing/ViewModel/LandingViewModel.swift b/Anyline Tire Demo/Presentation/Features/Landing/ViewModel/LandingViewModel.swift index 63d49af..f9d1e93 100644 --- a/Anyline Tire Demo/Presentation/Features/Landing/ViewModel/LandingViewModel.swift +++ b/Anyline Tire Demo/Presentation/Features/Landing/ViewModel/LandingViewModel.swift @@ -9,60 +9,62 @@ protocol LandingViewModelDelegate: AnyObject { } class LandingViewModel { - - // MARK: - Private Let's & Var's + + // MARK: - Private Properties private weak var landingViewModelDelegate: LandingViewModelDelegate? - // MARK: - Public Let's & Var's - // MARK: - Init init(delegate: LandingViewModelDelegate) { self.landingViewModelDelegate = delegate } + + var isInitialized: Bool { + AnylineTireTreadSdk.companion.isInitialized + } - func tryInitializeSdk(context: UIViewController) { + func tryInitializeSdk(context: UIViewController, completion: @escaping (Bool, String?) -> Void) { do { let keychainManager = KeychainManager() - guard let licenceID = keychainManager.getValue(forKey: KeychainKeys.licenseID) else { - landingViewModelDelegate?.showError(error: "error.license.missing_key".localized()) + guard let licenseString = keychainManager.getValue(forKey: KeychainKeys.licenseID) else { + completion(false, "error.license.missing_key".localized()) return } - try AnylineTireTreadSdk.companion.doInit(licenseKey: licenceID, context: context) + try AnylineTireTreadSdk.companion.doInit(licenseKey: licenseString, context: context) - if AnylineTireTreadSdk.companion.isInitialized { - requestPermissionsAndProceed(context: context) + if isInitialized { + completion(true, nil) } else { let errorMessage = "error.invalid_license".localized() - self.landingViewModelDelegate?.showError(error: errorMessage) + completion(false, errorMessage) } } catch { let errorMessage = "error.invalid_license".localized() + " (\(error.localizedDescription))" - self.landingViewModelDelegate?.showError(error: errorMessage) + completion(false, errorMessage) } } - func requestPermissionsAndProceed(context: UIViewController) { + func requestPermissionsAndProceed(context: UIViewController, completion: @escaping (Bool, String?) -> Void) { let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) switch cameraAuthorizationStatus { case .authorized: - self.landingViewModelDelegate?.authenticationSuccessfully() + completion(true, nil) case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { granted in DispatchQueue.main.async { if granted { - self.landingViewModelDelegate?.authenticationSuccessfully() + completion(true, nil) } else { let errorMessage = "error.camera_permission".localized() - self.landingViewModelDelegate?.showError(error: errorMessage) + completion(false, errorMessage) } } } default: let errorMessage = "error.camera_permission".localized() - self.landingViewModelDelegate?.showError(error: errorMessage) + completion(false, errorMessage) } } } diff --git a/Anyline Tire Demo/Presentation/Features/Loading/Controller/LoadingViewController.swift b/Anyline Tire Demo/Presentation/Features/Loading/Controller/LoadingViewController.swift index 9796ce8..dbe3b23 100644 --- a/Anyline Tire Demo/Presentation/Features/Loading/Controller/LoadingViewController.swift +++ b/Anyline Tire Demo/Presentation/Features/Loading/Controller/LoadingViewController.swift @@ -6,38 +6,38 @@ protocol LoadingViewControllerDelegate: AnyObject { } class LoadingViewController: UIViewController { - + // MARK: - UI properties private var topView: ATDTopView = { let view = ATDTopView() return view }() - + private var customView: LoadingView = { let view = LoadingView() return view }() - + // MARK: - Private properties private lazy var loadingViewModel: LoadingViewModel = { return LoadingViewModel(delegate: self, uuid: uuid) }() - + // MARK: - Public Properties weak var delegate: LoadingViewControllerDelegate? var uuid: String - + // MARK: - Init init(uuid: String) { self.uuid = uuid self.customView.customView.measurementUUIDLabel.text = "Scan ID: \(self.uuid)" super.init(nibName: nil, bundle: nil) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() @@ -77,49 +77,52 @@ private extension LoadingViewController { } } } - + func addSubviews() { self.view.addSubview(topView) self.view.addSubview(customView) } - + func setupLayout() { self.topView.snp.makeConstraints { make in make.height.equalTo(52) make.width.equalToSuperview() make.top.leading.trailing.equalToSuperview() } - + self.customView.snp.makeConstraints({ make in make.top.equalTo(topView.snp.bottom) make.leading.trailing.bottom.equalToSuperview() }) } - + func setDelegates() { self.customView.delegate = self } - + func startProcessing() { self.loadingViewModel.startDataProcessing() } + + private func returnToHome(animated: Bool = true) { + navigationController?.popToRootViewController(animated: animated) + } } // MARK: - LoadingButtonActionsDelegate extension LoadingViewController: LoadingButtonActionsDelegate { func abortButtonTapped() { - navigationController?.popToRootViewController(animated: true) + returnToHome() } } // MARK: - LoadingViewModelDelegate extension LoadingViewController: LoadingViewModelDelegate { - - func displayDepthResultView(uuid: String, treadDepthResult: TreadDepthResultDTO) { + func displayDepthResultView(uuid: String, treadDepthResult: TreadDepthResult) { let vc = ResultViewController(uuid: uuid, measurementResult: treadDepthResult) self.navigationController?.pushViewController(vc, animated: true) } - + func displayError() { DispatchQueue.main.async { [weak self] in let vc = ErrorViewController() @@ -131,12 +134,10 @@ extension LoadingViewController: LoadingViewModelDelegate { // MARK: - ErrorViewControllerDelegate extension LoadingViewController: ErrorViewControllerDelegate { - func didAbort() { self.delegate?.resetScan() - DispatchQueue.main.async { - self.navigationController?.popToRootViewController(animated: true) + DispatchQueue.main.async { [weak self] in + self?.returnToHome() } } - } diff --git a/Anyline Tire Demo/Presentation/Features/Loading/ViewModel/LoadingViewModel.swift b/Anyline Tire Demo/Presentation/Features/Loading/ViewModel/LoadingViewModel.swift index cf17d6f..62b0daa 100644 --- a/Anyline Tire Demo/Presentation/Features/Loading/ViewModel/LoadingViewModel.swift +++ b/Anyline Tire Demo/Presentation/Features/Loading/ViewModel/LoadingViewModel.swift @@ -3,7 +3,7 @@ import AnylineTireTreadSdk protocol LoadingViewModelDelegate: AnyObject { func displayError() - func displayDepthResultView(uuid: String, treadDepthResult: TreadDepthResultDTO) + func displayDepthResultView(uuid: String, treadDepthResult: TreadDepthResult) } class LoadingViewModel { @@ -11,7 +11,7 @@ class LoadingViewModel { // MARK: - Private properties private weak var loadingViewModelDelegate: LoadingViewModelDelegate? private var processingAttempts = 0 - + // MARK: - Public properties var uuid: String @@ -29,55 +29,23 @@ class LoadingViewModel { } private func fetchTreadDepthResult() { - AnylineTireTreadSdk.companion.getTreadDepthReportResult( - measurementUuid: self.uuid, - onGetTreadDepthReportResultSucceed: { [weak self] response in - response.body { resultDTO, error in + do { + try AnylineTireTreadSdk.companion.getTreadDepthReportResult( + measurementUuid: self.uuid, + onGetTreadDepthReportResultSucceeded: { [weak self] treadDepthResult in guard let self = self else { return } - guard let status = resultDTO?.measurement.status else { - self.loadingViewModelDelegate?.displayError() - return + DispatchQueue.main.async { + self.loadingViewModelDelegate?.displayDepthResultView(uuid: self.uuid, treadDepthResult: treadDepthResult) } - - self.handleTreadDepthResult(treadDepthResult: resultDTO?.result, status: status) - } - }, - onGetTreadDepthReportResultFailed: { [weak self] response, exception in - self?.loadingViewModelDelegate?.displayError() - } - ) - } -} - -// MARK: - Private Actions -private extension LoadingViewModel { - - func isValidTreadDepthResult(treadDepthResult: TreadDepthResultDTO) -> Bool { - return treadDepthResult.global.confidence > 0 - } - - private func handleTreadDepthResult(treadDepthResult: TreadDepthResultDTO?, status: MeasurementStatusDTO) { - switch status { - case .completed: - guard let treadDepthResult = treadDepthResult, isValidTreadDepthResult(treadDepthResult: treadDepthResult) else { - DispatchQueue.main.async { - self.loadingViewModelDelegate?.displayError() - } - return - } - DispatchQueue.main.async { - self.loadingViewModelDelegate?.displayDepthResultView(uuid: self.uuid, treadDepthResult: treadDepthResult) - } - case .failed, .unknown: - DispatchQueue.main.async { - self.loadingViewModelDelegate?.displayError() - } - case .processing, .waitingforimages: - DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { [weak self] in - self?.fetchTreadDepthResult() - } - default: - break + }, + onGetTreadDepthReportResultFailed: { [weak self] measurementError in + print("Error code: " + (measurementError.errorCode ?? "not available")) + print("Error message: " + measurementError.errorMessage) + self?.loadingViewModelDelegate?.displayError() + }, timeoutSeconds: 60 + ) + } catch { + self.loadingViewModelDelegate?.displayError() } } } diff --git a/Anyline Tire Demo/Presentation/Features/QR/QRCodeReaderViewController.swift b/Anyline Tire Demo/Presentation/Features/QR/QRCodeReaderViewController.swift new file mode 100644 index 0000000..0a4b67a --- /dev/null +++ b/Anyline Tire Demo/Presentation/Features/QR/QRCodeReaderViewController.swift @@ -0,0 +1,199 @@ +import UIKit +import AVFoundation + +class QRCodeReaderViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { + + enum ScanMode { + case `default` + case tireId + } + + private var scanMode: ScanMode = .default + + private var captureSession: AVCaptureSession! + + private var previewLayer: AVCaptureVideoPreviewLayer! + + private var completionBlock: ((QRCodeReaderViewController, String?) -> Void)? + + private var cancelButton: UIButton? + + private var instructionView: UIView? + + private var guideText: String? + + private var scannedString: String? { + didSet { + if let scannedString = scannedString { + completionBlock?(self, scannedString) + } + } + } + + private init() { + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + class func showReader(over presenter: UIViewController, + scanMode: ScanMode = .default, + msg: String? = nil, + animated: Bool = true, + completion: ((QRCodeReaderViewController, String?) -> Void)? = nil) { + let qrViewController = QRCodeReaderViewController() + qrViewController.completionBlock = completion + qrViewController.guideText = msg ?? "qrcodereader.guide_text.tire_id".localized() + qrViewController.scanMode = scanMode + qrViewController.modalPresentationStyle = .automatic + presenter.present(qrViewController, animated: animated) + } + + override func viewDidLoad() { + super.viewDidLoad() + setup() + } + + func restart() { + scannedString = nil + self.instructionView?.isHidden = false + self.cancelButton?.isHidden = false + self.previewLayer.isHidden = false + } + + private func addAccessoryViews() { + let cancelBtn = UIButton(type: .custom, + primaryAction: .init(title: "Cancel") { [weak self] _ in + guard let self = self else { return } + self.dismiss(animated: false) + self.completionBlock?(self, nil) + }) + view.addSubview(cancelBtn) + cancelBtn.translatesAutoresizingMaskIntoConstraints = false + cancelBtn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true + cancelBtn.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true + cancelBtn.titleLabel?.font = FontStruct.proximaNovaBold20 + cancelButton = cancelBtn + + let label = UILabel() + label.textColor = ColorStruct.snowWhite + label.font = UIFont(name: "ProximaNova-Bold", size: 16) + label.text = guideText + label.numberOfLines = 2 + label.translatesAutoresizingMaskIntoConstraints = false + + let holder = UIView() + holder.backgroundColor = ColorStruct.anylineBlue + holder.layer.cornerRadius = 15 + holder.layer.masksToBounds = true + holder.translatesAutoresizingMaskIntoConstraints = false + holder.addSubview(label) + view.addSubview(holder) + + self.instructionView = holder + + label.leadingAnchor.constraint(equalTo: holder.leadingAnchor, constant: 20).isActive = true + label.topAnchor.constraint(equalTo: holder.topAnchor, constant: 20.0).isActive = true + label.centerXAnchor.constraint(equalTo: holder.centerXAnchor).isActive = true + label.centerYAnchor.constraint(equalTo: holder.centerYAnchor).isActive = true + + holder.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -170.0).isActive = true + holder.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true + holder.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -40.0).isActive = true + holder.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true + + // fade them in + cancelBtn.alpha = 0 + holder.alpha = 0 + + UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseInOut, animations: { + cancelBtn.alpha = 1 + holder.alpha = 1 + }, completion: nil) + } + + func setup() { + captureSession = AVCaptureSession() + + guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { + return + } + + do { + let videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice) + captureSession.addInput(videoInput) + } catch { + print("Exception while setting up video input: \(error.localizedDescription)") + return + } + + let metadataOutput = AVCaptureMetadataOutput() + captureSession.addOutput(metadataOutput) + metadataOutput.setMetadataObjectsDelegate(self, queue: .main) + metadataOutput.metadataObjectTypes = [.qr] + + previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + + DispatchQueue.main.async { [weak self] in + guard let previewLayer = self?.previewLayer, let view = self?.view else { return } + previewLayer.frame = view.layer.bounds + view.layer.addSublayer(previewLayer) + previewLayer.videoGravity = .resizeAspectFill + + // Set the preview layer orientation based on the window scene orientation + let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene + let orientation = windowScene?.windows.first?.windowScene?.interfaceOrientation + var rotationAngle = CGFloat(0) + var videoOrientation: AVCaptureVideoOrientation = .landscapeRight + switch orientation { + case .landscapeLeft: + rotationAngle = 180 + videoOrientation = .landscapeLeft + default: + break + } + if #available(iOS 17.0, *) { + previewLayer.connection?.videoRotationAngle = rotationAngle + } else { + previewLayer.connection?.videoOrientation = videoOrientation + } + self?.addAccessoryViews() + } + + DispatchQueue.global(qos: .background).async { [weak self] in + self?.captureSession.startRunning() + } + } + + func metadataOutput(_ output: AVCaptureMetadataOutput, + didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { + guard scannedString == nil else { return } + if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject, + let code = metadataObject.stringValue { + if scanMode == .tireId { + // only if we can extract tire_id from a URL string + if let tireId = tireIdFromScannedQRCode(code) { + self.scannedString = tireId + self.hideUIElements() + } + } else { + self.scannedString = code + self.hideUIElements() + } + } + } + + private func tireIdFromScannedQRCode(_ urlString: String) -> String? { + let queryItems = URLComponents(string: urlString)?.queryItems + let tireIdParam = queryItems?.filter { $0.name == "tire_id" }.first + return tireIdParam?.value + } + + fileprivate func hideUIElements() { + self.instructionView?.isHidden = true + self.cancelButton?.isHidden = true + self.previewLayer.isHidden = true + } +} diff --git a/Anyline Tire Demo/Presentation/Features/Result details/ViewModel/ResultDetailsViewModel.swift b/Anyline Tire Demo/Presentation/Features/Result details/ViewModel/ResultDetailsViewModel.swift index 9917718..504ab7b 100644 --- a/Anyline Tire Demo/Presentation/Features/Result details/ViewModel/ResultDetailsViewModel.swift +++ b/Anyline Tire Demo/Presentation/Features/Result details/ViewModel/ResultDetailsViewModel.swift @@ -33,14 +33,14 @@ class ResultDetailsViewModel { } try anylineSDK.doInit(licenseKey: licenceID, context: context) - let kotlinByteArray = anylineSDK.getTreadDepthReportPdf(measurementUuid: self.uuid) { _ in + anylineSDK.getTreadDepthReportPdf(measurementUuid: self.uuid) { pdfByteArray in print("PDF fetched from SDK.") - } - - // Convert Kotlin ByteArray to Swift Data - let data = kotlinByteArray.toNSData() + + // Convert ByteArray to Swift Data + let data = pdfByteArray.toNSData() - self.resultDetailsViewModelDelegate?.showPDF(pdfData: data) + self.resultDetailsViewModelDelegate?.showPDF(pdfData: data) + } } catch { resultDetailsViewModelDelegate?.showError(error: "error.description".localized()) } diff --git a/Anyline Tire Demo/Presentation/Features/Result/Controller/ResultViewController.swift b/Anyline Tire Demo/Presentation/Features/Result/Controller/ResultViewController.swift index 8fdfc89..c1de266 100644 --- a/Anyline Tire Demo/Presentation/Features/Result/Controller/ResultViewController.swift +++ b/Anyline Tire Demo/Presentation/Features/Result/Controller/ResultViewController.swift @@ -21,11 +21,11 @@ class ResultViewController: UIViewController { private var rightTireTreadValue: String? // MARK: - Public Properties - var measurementResult: TreadDepthResultDTO + var measurementResult: TreadDepthResult var uuid: String // MARK: - Init - init(uuid: String, measurementResult: TreadDepthResultDTO) { + init(uuid: String, measurementResult: TreadDepthResult) { self.uuid = uuid self.measurementResult = measurementResult @@ -101,21 +101,21 @@ private extension ResultViewController { let useImperial = UserDefaultsManager.shared.imperialSystem - topTireTreadValue = String(useImperial ? measurementResult.global.valueInch : measurementResult.global.valueMm) + topTireTreadValue = String(useImperial ? Double(measurementResult.global.valueInch32nds) : measurementResult.global.valueMm) - if measurementResult.regions[0].available { + if measurementResult.regions[0].isAvailable { leftTireTreadValue = String(useImperial ? - measurementResult.regions[0].valueInch : measurementResult.regions[0].valueMm) + Double(measurementResult.regions[0].valueInch32nds) : measurementResult.regions[0].valueMm) } - if measurementResult.regions[1].available { + if measurementResult.regions[1].isAvailable { middleTireTreadValue = String(useImperial ? - measurementResult.regions[1].valueInch : measurementResult.regions[1].valueMm) + Double(measurementResult.regions[1].valueInch32nds) : measurementResult.regions[1].valueMm) } - if measurementResult.regions[2].available { + if measurementResult.regions[2].isAvailable { rightTireTreadValue = String(useImperial ? - measurementResult.regions[2].valueInch : measurementResult.regions[2].valueMm) + Double(measurementResult.regions[2].valueInch32nds) : measurementResult.regions[2].valueMm) } } diff --git a/Anyline Tire Demo/Presentation/Features/Result/View/MeasurementView.swift b/Anyline Tire Demo/Presentation/Features/Result/View/MeasurementView.swift index a43a18c..e3e3695 100644 --- a/Anyline Tire Demo/Presentation/Features/Result/View/MeasurementView.swift +++ b/Anyline Tire Demo/Presentation/Features/Result/View/MeasurementView.swift @@ -3,10 +3,19 @@ import AnylineTireTreadSdk class MeasurementView: UIView { + enum Location { + case global + case regional + } + + var location: Location = .global + + private var isRegional: Bool { location == .regional } + // MARK: - UI properties private lazy var tireTreadValueLabel: ATDTextLabel = { let label = ATDTextLabel(text: measurementValue ?? "") - label.font = FontStruct.proximaNovaBold23 + label.font = isRegional ? FontStruct.proximaNovaBold16 : FontStruct.proximaNovaBold23 label.textColor = ColorStruct.snowWhite label.textAlignment = .center return label @@ -14,12 +23,12 @@ class MeasurementView: UIView { private lazy var tireTreadMeasurementLabel: ATDTextLabel = { let label = ATDTextLabel(text: unitStr) - label.font = FontStruct.proximaNovaBold23 + label.font = isRegional ? FontStruct.proximaNovaBold16 : FontStruct.proximaNovaBold23 label.textColor = ColorStruct.snowWhite label.textAlignment = .center return label }() - + private var unitStr: String { UserDefaultsManager.shared.imperialSystem ? "32\"" : "mm" } @@ -29,7 +38,7 @@ class MeasurementView: UIView { view.backgroundColor = .white return view }() - + private lazy var emptyMeasurementLine: UIView = { let view = UIView() view.backgroundColor = .white @@ -58,33 +67,35 @@ class MeasurementView: UIView { contentVStackView.isHidden = true return } + let metricValue = Double(measurementValue) ?? 0.0 tireTreadMeasurementLabel.text = unitStr + if UserDefaultsManager.shared.imperialSystem { - _ = DistanceUnitConvertKt.inchToFractionString(inch: Double(measurementValue) ?? 0.0, improper: true, denominator: 32) - let fractionTriple = try? DecimalFractionConvertKt.toFractionTriple(Double(measurementValue) ?? 0.0, improper: false, denominator: 32) - let numerator = fractionTriple?.second?.intValue ?? 0 - tireTreadValueLabel.text = "\(numerator)" - self.backgroundColor = getColorFromInchFraction(numerator) + if let doubleValue = Double(measurementValue) { + let intValue = Int(doubleValue) + tireTreadValueLabel.text = "\(intValue)" + self.backgroundColor = getColorFromInchFraction(intValue) + } } else { - let metricValue = Double(measurementValue) ?? 0.0 tireTreadValueLabel.text = String(format: "%.1f", metricValue) self.backgroundColor = getColorForMetricValue(metricValue) } } } - + // MARK: - Init - override init(frame: CGRect) { - super.init(frame: frame) - configureView() - addSubviews() - setupLayout() - } - required init?(coder: NSCoder) { super.init(coder: coder) } + init(location: MeasurementView.Location = .global) { + super.init(frame: .zero) + self.location = location + + configureView() + addSubviews() + setupLayout() + } } // MARK: - Private functions @@ -93,7 +104,7 @@ private extension MeasurementView { // MARK: - Setup UI func configureView() { backgroundColor = ColorStruct.leafGreen - layer.cornerRadius = 5 + layer.cornerRadius = isRegional ? 4 : 5 } func addSubviews() { @@ -123,7 +134,7 @@ private extension MeasurementView { make.width.equalTo(20) } } - + self.emptyMeasurementLine.snp.makeConstraints { make in make.height.equalTo(2) make.width.equalTo(16) @@ -151,6 +162,6 @@ private extension MeasurementView { return ColorStruct.leafGreen } } - + } diff --git a/Anyline Tire Demo/Presentation/Features/Result/View/TireTreadMeasurementView.swift b/Anyline Tire Demo/Presentation/Features/Result/View/TireTreadMeasurementView.swift index a0bd2db..9c3f539 100644 --- a/Anyline Tire Demo/Presentation/Features/Result/View/TireTreadMeasurementView.swift +++ b/Anyline Tire Demo/Presentation/Features/Result/View/TireTreadMeasurementView.swift @@ -16,17 +16,17 @@ class TireTreadMeasurementView: UIView { }() lazy var leftMeasurementView: MeasurementView = { - var view = MeasurementView() + var view = MeasurementView(location: .regional) return view }() lazy var middleMeasurementView: MeasurementView = { - var view = MeasurementView() + var view = MeasurementView(location: .regional) return view }() lazy var rightMeasurementView: MeasurementView = { - var view = MeasurementView() + var view = MeasurementView(location: .regional) return view }() @@ -98,6 +98,21 @@ private extension TireTreadMeasurementView { make.width.equalTo(450) make.height.equalTo(150) } + + self.leftMeasurementView.snp.makeConstraints { make in + make.width.equalTo(self.topMeasurementView).multipliedBy(0.8) + make.height.equalTo(self.topMeasurementView).multipliedBy(0.8) + } + + self.rightMeasurementView.snp.makeConstraints { make in + make.width.equalTo(self.topMeasurementView).multipliedBy(0.8) + make.height.equalTo(self.topMeasurementView).multipliedBy(0.8) + } + + self.middleMeasurementView.snp.makeConstraints { make in + make.width.equalTo(self.topMeasurementView).multipliedBy(0.8) + make.height.equalTo(self.topMeasurementView).multipliedBy(0.8) + } } } diff --git a/Anyline Tire Demo/Presentation/Features/Scan/Controller/ScanViewController.swift b/Anyline Tire Demo/Presentation/Features/Scan/Controller/ScanViewController.swift index 1aa659f..2e7d107 100644 --- a/Anyline Tire Demo/Presentation/Features/Scan/Controller/ScanViewController.swift +++ b/Anyline Tire Demo/Presentation/Features/Scan/Controller/ScanViewController.swift @@ -4,87 +4,66 @@ import CoreHaptics import MediaPlayer import AnylineTireTreadSdk +protocol ScanViewControllerDelegate { + func startRecorderFlow(uuid: String) +} + class ScanViewController: UIViewController, ScannerViewControllerHolder { - + // MARK: - Private Var's & Let's - private lazy var progressView: UIProgressView = { - let progressView = UIProgressView(progressViewStyle: .default) - progressView.trackTintColor = ColorStruct.skyGrey - progressView.progressTintColor = ColorStruct.anylineBlue - progressView.progress = 0.0 - progressView.isHidden = true - return progressView - }() - - private lazy var distanceLabel: UILabel = { - let label = UILabel() - label.text = "\("scan.distance.ok".localized()): 20" - label.textColor = .clear - label.numberOfLines = 1 - label.textAlignment = .center - label.font = FontStruct.proximaNovaBold23 - return label - }() - - private var scanTimer: Timer? - private var progress: Float = 0 - private let totalTime = 10.0 // Total time for scanning - private let interval = 0.1 // Time interval to update progressView private var volumeButtonObserver: VolumeButtonObserver? - - // Audio properties - var highBeepAudioPlayer: AVAudioPlayer? - var lowBeepAudioPlayer: AVAudioPlayer? - var focusAudioPlayer: AVAudioPlayer? - var startAudioPlayer: AVAudioPlayer? - var stopAudioPlayer: AVAudioPlayer? - var hapticPlayer: CHHapticPatternPlayer? - var engine: CHHapticEngine? - var beepingTimer: Timer? - var previousDistance = 0 - + + var delegate: ScanViewControllerDelegate? + var scannerViewController: UIViewController? var dismissViewController: (() -> Void)? - + // MARK: - Init init() { super.init(nibName: nil, bundle: nil) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() setupTireTreadScanView() - setupSubviews() - setupAudioFiles() - setupHapticEngine() setupVolumeView() } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.setupVolumeButtonObserver() } - + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - self.stopBeeping() - self.stopAudioPlayers() + self.delegate = nil self.resetVolumeButtonObserver() + self.scannerViewController = nil + } } // MARK: - Private Actions private extension ScanViewController { - + private func setupTireTreadScanView() { - let config = TireTreadScanViewConfig(measurementSystem: UserDefaultsManager.shared.imperialSystem ? .imperial : .metric, useDefaultUi: true, useDefaultHaptic: true) + let userDefaults = UserDefaultsManager.shared + let measurementSystem: MeasurementSystem = userDefaults.imperialSystem ? .imperial : .metric + + let config = TireTreadScanViewConfig.Builder() + .withMeasurementSystem(measurementSystem: measurementSystem) + // The API ".withScanSpeed()" is experimental, may impact scan performance and be removed with any major SDK release. + // You are advised to ignore this configuration on your implementation. + .withScanSpeed(scanSpeed: userDefaults.scanSpeed) + .build() + // creates a TireTreadScannerViewController. You can later refer to it here // as self.scannerViewController. TireTreadScanViewKt.TireTreadScanView(context: UIViewController(), config: config, callback: self) { [weak self] error in @@ -97,7 +76,7 @@ private extension ScanViewController { } addScanViewControllerAsChild() } - + private func addScanViewControllerAsChild() { guard let scannerViewController = scannerViewController else { displayError() @@ -108,54 +87,11 @@ private extension ScanViewController { scannerViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() } scannerViewController.didMove(toParent: self) } - + func setupVolumeView() { let volumeView = MPVolumeView(frame: CGRect(x: -1000, y: -1000, width: 0, height: 0)) view.addSubview(volumeView) } - - func updateUI(status: DistanceStatus, distance: Int) { - let measurementUnit = UserDefaultsManager.shared.imperialSystem ? "in" : "cm" - let textAndColor = textAndColor(for: status) - - self.distanceLabel.text = "\(textAndColor.text): \(distance) \(measurementUnit)" - self.distanceLabel.textColor = textAndColor.color - } - - func textAndColor(for status: DistanceStatus) -> (text: String, color: UIColor) { - var text: String - var color: UIColor - - switch status { - case .close, .tooClose: - text = "scan.distance.increase".localized() - color = (status == .close) ? .yellow : .red - case .far, .tooFar: - text = "scan.distance.decrease".localized() - color = (status == .far) ? .yellow : .red - case .ok: - text = "scan.distance.ok".localized() - color = .green - default: - text = "scan.distance.ok".localized() - color = .green - } - - return (text, color) - } - - @objc func updateProgressView() { - // Update the progress - progress += Float(interval / totalTime) - progressView.progress = progress - - // Check if the scanning process is completed - if progress >= 1.0 { - // Stop the timer - scanTimer?.invalidate() - scanTimer = nil - } - } } // MARK: - AVAudioSession for Volume buttons @@ -170,7 +106,7 @@ private extension ScanViewController { func resetVolumeButtonObserver() { self.volumeButtonObserver = nil } - + private func handleVolumeButtonPressed() { if TireTreadScanner.companion.isInitialized { if TireTreadScanner.companion.instance.isScanning { @@ -183,265 +119,94 @@ private extension ScanViewController { } -// MARK: - Private setup methods -private extension ScanViewController { - func setupSubviews() { - setupBottomDistanceView() - setupProgressView() - } - - func setupProgressView() { - self.view.addSubview(progressView) - progressView.snp.makeConstraints { - $0.top.equalTo(20) - $0.leading.equalTo(200) - $0.trailing.equalTo(-200) - } - } - - func setupBottomDistanceView() { - self.view.addSubview(distanceLabel) - - distanceLabel.snp.makeConstraints { make in - make.leading.trailing.equalToSuperview() - make.bottom.equalTo(-50) - } - } -} - -// MARK: - AVAudioSession for Audio feedback -private extension ScanViewController { - - // Load audio files - func setupAudioFiles() { - - let focusBeepUrl = Bundle.main.url(forResource: "tiretread_focuspoint_found", withExtension: "wav")! - focusAudioPlayer = try? AVAudioPlayer(contentsOf: focusBeepUrl) - focusAudioPlayer?.prepareToPlay() - - let startBeepUrl = Bundle.main.url(forResource: "tiretread_sound_start", withExtension: "mp3")! - startAudioPlayer = try? AVAudioPlayer(contentsOf: startBeepUrl) - startAudioPlayer?.prepareToPlay() - - let stopBeepUrl = Bundle.main.url(forResource: "tiretread_sound_stop", withExtension: "wav")! - stopAudioPlayer = try? AVAudioPlayer(contentsOf: stopBeepUrl) - stopAudioPlayer?.prepareToPlay() - stopAudioPlayer?.delegate = self - - let highBeepUrl = Bundle.main.url(forResource: "tiretread_sound_high_beep", withExtension: "wav")! - highBeepAudioPlayer = try? AVAudioPlayer(contentsOf: highBeepUrl) - highBeepAudioPlayer?.prepareToPlay() - - let lowBeepUrl = Bundle.main.url(forResource: "tiretread_sound_low_beep", withExtension: "wav")! - lowBeepAudioPlayer = try? AVAudioPlayer(contentsOf: lowBeepUrl) - lowBeepAudioPlayer?.prepareToPlay() - - } - - func playStartSound() { - startAudioPlayer?.play() - } - - func playStopSound() { - stopAudioPlayer?.play() - } - - func playFocusSound() { - focusAudioPlayer?.play() - playHapticFeedback() - } - - // Function to start the beeping sound - func startBeeping(distanceInCm: Double, distanceStatus: DistanceStatus) { - // Cancel any existing timer - beepingTimer?.invalidate() - - // Calculate the interval for the timer based on the distance - var interval: Double - var playerToUse: AVAudioPlayer? - - switch distanceStatus { - case .close: - interval = 1000 - playerToUse = self.highBeepAudioPlayer - case .far: - interval = 1000 - playerToUse = self.lowBeepAudioPlayer - case .tooClose: - interval = 500 - playerToUse = self.highBeepAudioPlayer - case .tooFar: - interval = 500 - playerToUse = self.lowBeepAudioPlayer - default: return - } - - // another way of calculating interval (older method) -// let roundedDistance = Int(distanceInCm) -// if roundedDistance < 16 { -// interval = 500 * (distanceInCm / 16) -// playerToUse = self.highBeepAudioPlayer -// } else if roundedDistance > 22 { -// interval = max(500 + (distanceInCm - 22) * (0 - 500) / (40 - 22), 0) -// playerToUse = self.lowBeepAudioPlayer -// } else { -// // Distance is in the optimal range - no need to beep -// return -// } - - // Create a new timer - beepingTimer = Timer.scheduledTimer(withTimeInterval: interval / 1000, repeats: true) { _ in - DispatchQueue.global(qos: .userInitiated).async { - playerToUse?.play() - } - } - - // Ensure the timer is scheduled on the main thread - DispatchQueue.main.async { - self.beepingTimer?.fire() - } - } - - - func setupHapticEngine() { - do { - engine = try CHHapticEngine() - try engine?.start() - } catch let error { - print("Failed to create haptic engine: \(error.localizedDescription)") - } - } - - func playHapticFeedback() { - let sharpTap = CHHapticEvent(eventType: .hapticTransient, parameters: [CHHapticEventParameter(parameterID: .hapticIntensity, value: 1), CHHapticEventParameter(parameterID: .hapticSharpness, value: 1)], relativeTime: 0) - let vibration = CHHapticEvent(eventType: .hapticContinuous, parameters: [ - CHHapticEventParameter(parameterID: .hapticIntensity, value: 1), - CHHapticEventParameter(parameterID: .hapticSharpness, value: 1) - ], relativeTime: 0, duration: 0.1) - let pattern = try? CHHapticPattern(events: [sharpTap, vibration], parameters: []) - do { - hapticPlayer = try engine?.makePlayer(with: pattern!) - try hapticPlayer?.start(atTime: CHHapticTimeImmediate) - } catch { - // Handle the error - print("Failed to play haptic feedback: \(error.localizedDescription)") - } - } - - // Function to stop the beeping sound - func stopBeeping() { - // Cancel the timer - beepingTimer?.invalidate() - beepingTimer = nil - } - - func stopAudioPlayers() { - focusAudioPlayer?.stop() - startAudioPlayer?.stop() - highBeepAudioPlayer?.stop() - lowBeepAudioPlayer?.stop() - try? hapticPlayer?.stop(atTime: CHHapticTimeImmediate) - - focusAudioPlayer = nil - startAudioPlayer = nil - highBeepAudioPlayer = nil - lowBeepAudioPlayer = nil - beepingTimer = nil - hapticPlayer = nil - } -} - -// MARK: - AVAudioPlayerDelegate -extension ScanViewController: AVAudioPlayerDelegate { - - func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { - stopAudioPlayer?.stop() - stopAudioPlayer = nil - } - -} - // MARK: - ScanViewModelDelegate extension ScanViewController: ScanViewModelDelegate { - + func displayError() { let vc = ErrorViewController() vc.delegate = self self.navigationController?.pushViewController(vc, animated: true) } - + func displayLoading(uuid: String) { - self.scanTimer?.invalidate() - self.playStopSound() DispatchQueue.main.async { let vc = LoadingViewController(uuid: uuid) self.navigationController?.pushViewController(vc, animated: true) } } - + } // MARK: - LoadingViewControllerDelegate extension ScanViewController: LoadingViewControllerDelegate { func resetScan() { - + } } // MARK: - ErrorViewControllerDelegate extension ScanViewController: ErrorViewControllerDelegate { func didAbort() { - + } } extension ScanViewController: TireTreadScanViewCallback { + + // We're using the SDK's defaultUi, so we only really need to implement the behavior + // for the "onScanAbort", "onUploadCompleted", "onUploadAborted", and "onUploadFailed" callbacks func onScanAbort(uuid: String?) { print("scan aborted for uuid: \(uuid ?? "")") self.navigationController?.popViewController(animated: true) } - + func onUploadAborted(uuid: String?) { print("upload aborted for uuid: \(uuid ?? "")") self.navigationController?.popViewController(animated: true) } - func onFocusFound(uuid: String?) { - self.playFocusSound() + func onUploadFailed(uuid: String?, exception: KotlinException) { + self.displayError() } func onUploadCompleted(uuid: String?) { - // Implement this method - self.playStopSound() - if let safeUuid = uuid { - self.displayLoading(uuid: safeUuid) + // On upload complete, we should check for Results. + + // the "shouldRequestTireIdFeedback" is only intended for feedback and + // does not need to be implemented + if let uuid = uuid { + var shouldRequestForFeedback: Bool = false + do { + shouldRequestForFeedback = try AnylineTireTreadSdk.companion.shouldRequestTireIdFeedback() + } catch { + print("caught exception: \(error.localizedDescription)") + } + if shouldRequestForFeedback { + delegate?.startRecorderFlow(uuid: uuid) + } else { + // Your application should directly fetch Results + self.displayLoading(uuid: uuid) + } } else { self.displayError() } } - func onImageUploaded(uuid: String?, uploaded: Int32, total: Int32) { - print("Native iOS: Image uploaded (\(uploaded)/\(total)) for uuid: \(uuid ?? "unknown")") - } - - func onUploadFailed(uuid: String?, exception: KotlinException) { - self.displayError() + func onFocusFound(uuid: String?) { } func onScanStart(uuid: String?) { - self.playStartSound() - self.progressView.isHidden = false - self.scanTimer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(updateProgressView), userInfo: nil, repeats: true) } func onScanStop(uuid: String?) { - print("Native iOS: Scan stopped for uuid: \(uuid ?? "unknown")") - distanceLabel.isHidden = true - stopBeeping() - stopAudioPlayers() + print("Showcase iOS: Scan stopped for uuid: \(uuid ?? "unknown")") } + func onImageUploaded(uuid: String?, uploaded: Int32, total: Int32) { + print("Showcase iOS: Image uploaded (\(uploaded)/\(total)) for uuid: \(uuid ?? "unknown")") + } + /// Called when the distance has changed. /// /// - Parameters: @@ -453,12 +218,6 @@ extension ScanViewController: TireTreadScanViewCallback { /// /// Note: The distance values are provided in millimeters if the metric system is selected (`UserDefaultsManager.shared.imperialSystem = false`), and in inches if the imperial system is selected (`UserDefaultsManager.shared.imperialSystem = true`). func onDistanceChanged(uuid: String?, previousStatus: DistanceStatus, newStatus: DistanceStatus, previousDistance: Float, newDistance: Float) { - if Int(newDistance) != Int(previousDistance) { - let distanceInCentimeters = UserDefaultsManager.shared.imperialSystem ? (newDistance * 2.54) : (newDistance / 10.0) - startBeeping(distanceInCm: Double(distanceInCentimeters), distanceStatus: newStatus) - DispatchQueue.main.async { [weak self] in - self?.updateUI(status: newStatus, distance: Int(UserDefaultsManager.shared.imperialSystem ? newDistance : distanceInCentimeters)) - } - } + } } diff --git a/Anyline Tire Demo/Presentation/Features/Settings/Controller/SettingsViewController.swift b/Anyline Tire Demo/Presentation/Features/Settings/Controller/SettingsViewController.swift index a021d8d..f196d73 100644 --- a/Anyline Tire Demo/Presentation/Features/Settings/Controller/SettingsViewController.swift +++ b/Anyline Tire Demo/Presentation/Features/Settings/Controller/SettingsViewController.swift @@ -1,4 +1,5 @@ import UIKit +import AnylineTireTreadSdk class SettingsViewController: UIViewController { @@ -8,7 +9,7 @@ class SettingsViewController: UIViewController { return view }() - private var customView: SettingsView = { + private var settingsView: SettingsView = { let view = SettingsView() return view }() @@ -16,6 +17,8 @@ class SettingsViewController: UIViewController { // MARK: - Private Properties private var switchValueButton: Bool = UserDefaultsManager.shared.imageQualitySwitchValue private var imperialSystem: Bool = UserDefaultsManager.shared.imperialSystem + private var scanSpeed: ScanSpeed = UserDefaultsManager.shared.scanSpeed + private lazy var settingsViewModel: SettingsViewModel = { return SettingsViewModel(delegate: self) }() @@ -37,7 +40,7 @@ class SettingsViewController: UIViewController { configureView() addSubviews() setupLayout() - customView.delegate = self + settingsView.delegate = self } override func viewWillDisappear(_ animated: Bool) { @@ -70,7 +73,7 @@ class SettingsViewController: UIViewController { func saveLicenseID() { let keychainManager = KeychainManager() - let licenseIDText = customView.licenseView.licenseIdTextField.text + let licenseIDText = settingsView.licenseView.licenseIdTextField.text if let licenceID = keychainManager.getValue(forKey: KeychainKeys.licenseID) { if licenceID != licenseIDText { if let safeLicenseIDText = licenseIDText { @@ -99,7 +102,7 @@ private extension SettingsViewController { func addSubviews() { self.view.addSubview(topView) - self.view.addSubview(customView) + self.view.addSubview(settingsView) } func setupLayout() { @@ -110,7 +113,7 @@ private extension SettingsViewController { make.top.leading.trailing.equalToSuperview() } - customView.snp.makeConstraints { make in + settingsView.snp.makeConstraints { make in make.top.equalTo(topView.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } @@ -120,13 +123,35 @@ private extension SettingsViewController { // MARK: - Private Actions private extension SettingsViewController { @objc func keyboardWillShow(notification: NSNotification) { - if let userInfo = notification.userInfo, - let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect { - - let keyboardHeight = keyboardFrame.size.height - 100 - + + // the field that caused the software keyboard to display + // TODO: maybe use the main responder + let field = self.settingsView.licenseView.licenseIdTextField + + guard let userInfo = notification.userInfo else { return } + + // Get the keyboard’s frame at the end of its animation. + guard let screen = notification.object as? UIScreen, + let keyboardFrameEnd = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } + + let fromCoordinateSpace = screen.coordinateSpace + + let toCoordinateSpace: UICoordinateSpace = view + + // Convert the keyboard's frame from the screen's coordinate space to your view's coordinate space. + let convertedKeyboardFrameEnd = fromCoordinateSpace.convert(keyboardFrameEnd, to: toCoordinateSpace) + + let yMargin = 10.0 // min distance we allow between the field's bottom and the keyboard + + let fieldBottomY = (field.convert(field.frame.origin, to: screen.coordinateSpace).y + + field.bounds.height + + yMargin) + + var shift = 0.0 + if convertedKeyboardFrameEnd.minY < fieldBottomY { + shift = convertedKeyboardFrameEnd.minY - fieldBottomY UIView.animate(withDuration: 0.3) { - self.view.frame.origin.y = -keyboardHeight + self.view.frame.origin.y = shift } } } @@ -144,6 +169,57 @@ private extension SettingsViewController { // MARK: - SettingsButtonActionsDelegate extension SettingsViewController: SettingsButtonActionsDelegate { + + func scanQRCodeTapped() { + QRCodeReaderViewController.showReader(over: self.navigationController!, + msg: "qrcodereader.guide_text.license_key".localized(), + animated: true) { [weak self] qrViewController, decoded in + guard let decoded = decoded else { + return + } + if let licenseKeyString = self?.returnValidLicenseKey(decoded) { + self?.settingsView.licenseView.licenseIdTextField.text = licenseKeyString + qrViewController.dismiss(animated: true) + } else { + let msg = "qrcodereader.message.invalid_license_key".localized() + self?.showAlertQRCode(over: qrViewController, msg: msg) + } + } + } + + func showAlertQRCode(over qrViewController: QRCodeReaderViewController, msg: String) { + let alert = UIAlertController(title: "Scan License Key", message: msg, preferredStyle: .alert) + alert.addAction(.init(title: "OK", style: .default, handler: { _ in + qrViewController.restart() + })) + qrViewController.present(alert, animated: true) + } + + fileprivate func returnValidLicenseKey(_ licenseKeyString: String) -> String? { + return licenseKeyString + } + + func scanSpeedDialogRequested(sender: UIButton, options: [ScanSpeed], completion: (ScanSpeed?) -> Void) { + let alert = UIAlertController(title: "Select Capture Speed", message: nil, preferredStyle: .actionSheet) + + for speed in options { + let title = speed.name + alert.addAction(.init(title: title, style: .default, handler: { [weak self] action in + print("preset tapped: \(title) for preset \(speed.name)") + self?.settingsView.captureSpeedView.scanSpeed = speed + UserDefaultsManager.shared.scanSpeed = speed + })) + } + alert.addAction(.init(title: "Cancel", style: .cancel)) + + let popoverController = alert.popoverPresentationController + popoverController?.sourceView = sender + popoverController?.sourceRect = sender.bounds + popoverController?.permittedArrowDirections = .up + + self.navigationController?.present(alert, animated: true) + } + func okButtonTapped() { saveImperialSystem() saveLicenseID() @@ -152,7 +228,8 @@ extension SettingsViewController: SettingsButtonActionsDelegate { } func testUploadButtonTapped() { - self.settingsViewModel.testSetup(context: self) + let licenseKey = settingsView.licenseView.licenseIdTextField.text ?? "" + self.settingsViewModel.testLicenseKey(licenseKey, context: self) } func cancelButtonTapped() { diff --git a/Anyline Tire Demo/Presentation/Features/Settings/SettingsButtonActionsDelegate.swift b/Anyline Tire Demo/Presentation/Features/Settings/SettingsButtonActionsDelegate.swift index ed66e10..2b7d6a1 100644 --- a/Anyline Tire Demo/Presentation/Features/Settings/SettingsButtonActionsDelegate.swift +++ b/Anyline Tire Demo/Presentation/Features/Settings/SettingsButtonActionsDelegate.swift @@ -1,4 +1,5 @@ import UIKit +import AnylineTireTreadSdk protocol SettingsButtonActionsDelegate: AnyObject { func okButtonTapped() @@ -6,4 +7,11 @@ protocol SettingsButtonActionsDelegate: AnyObject { func cancelButtonTapped() func imageTapped(tapGestureRecognizer: UITapGestureRecognizer) func switchChanged(mySwitch: UISwitch) + + func scanQRCodeTapped() + func scanSpeedDialogRequested(sender: UIButton, options: [ScanSpeed], completion: (ScanSpeed?) -> Void) +} + +protocol CaptureSpeedViewDelegate: AnyObject { + func buttonTapped(sender: UIButton) } diff --git a/Anyline Tire Demo/Presentation/Features/Settings/View/ButtonsSettingsView.swift b/Anyline Tire Demo/Presentation/Features/Settings/View/ButtonsSettingsView.swift index d7def81..20b98d1 100644 --- a/Anyline Tire Demo/Presentation/Features/Settings/View/ButtonsSettingsView.swift +++ b/Anyline Tire Demo/Presentation/Features/Settings/View/ButtonsSettingsView.swift @@ -69,14 +69,15 @@ private extension ButtonsSettingsView { } testUploadButton.snp.makeConstraints { make in - make.top.equalTo(okButton.snp.bottom).offset(30) + make.top.equalTo(okButton.snp.bottom).offset(10) + make.bottom.lessThanOrEqualTo(cancelButton.snp.top).offset(-10) make.trailing.equalTo(0) make.width.equalTo(150) make.height.equalTo(80) } cancelButton.snp.makeConstraints { make in - make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).offset(-10) + make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).offset(-20) make.trailing.equalTo(0) make.width.equalTo(150) make.height.equalTo(80) diff --git a/Anyline Tire Demo/Presentation/Features/Settings/View/CaptureSpeedView.swift b/Anyline Tire Demo/Presentation/Features/Settings/View/CaptureSpeedView.swift new file mode 100644 index 0000000..b01d0b5 --- /dev/null +++ b/Anyline Tire Demo/Presentation/Features/Settings/View/CaptureSpeedView.swift @@ -0,0 +1,84 @@ +import UIKit +import AnylineTireTreadSdk + +class CaptureSpeedView: UIView { + + var delegate: CaptureSpeedViewDelegate? + + var scanSpeed: ScanSpeed = UserDefaultsManager.shared.scanSpeed { + didSet { + let attrStr = attributedString(scanSpeed: scanSpeed) + buttonText.setAttributedTitle(attrStr, for: .normal) + } + } + + private func attributedString(scanSpeed: ScanSpeed) -> NSAttributedString { + let presetValue = scanSpeed.name + let fullText = "\(presetValue) (Tap to change)" + let attributedString = NSMutableAttributedString(string: fullText) + let range = (fullText as NSString).range(of: presetValue) + let boldAttributes: [NSAttributedString.Key: Any] = [ + .font: FontStruct.proximaNovaBold16! + ] + attributedString.addAttributes(boldAttributes, range: range) + return attributedString + } + + private lazy var headerLabel: ATDTextLabel = { + let label = ATDTextLabel(text: "settings.label.capture_speed".localized()) + return label + }() + + private lazy var buttonText: UIButton = { + let button = UIButton(type: .custom) + button.setTitleColor(ColorStruct.anylineBlue, for: .normal) + button.titleLabel?.font = FontStruct.proximaNovaRegular14 + button.addTarget(self, action: #selector(didPressButton), for: .touchUpInside) + return button + }() + + @objc func didPressButton(btn: UIButton) { + delegate?.buttonTapped(sender: btn) + } + + // MARK: - Init + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = ColorStruct.snowWhite + configureView() + addSubviews() + setupLayout() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + +} + +private extension CaptureSpeedView { + + // MARK: - Setup UI + func configureView() { + let attrStr = attributedString(scanSpeed: scanSpeed) + buttonText.setAttributedTitle(attrStr, for: .normal) + } + + func addSubviews() { + self.addSubview(headerLabel) + self.addSubview(buttonText) + } + + func setupLayout() { + headerLabel.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.leading.equalToSuperview() + } + + buttonText.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.leading.equalTo(headerLabel.snp.trailing).offset(5) + make.trailing.lessThanOrEqualToSuperview() + } + } +} diff --git a/Anyline Tire Demo/Presentation/Features/Settings/View/InfoSettingsView.swift b/Anyline Tire Demo/Presentation/Features/Settings/View/InfoSettingsView.swift index 3b76ae9..a77958f 100644 --- a/Anyline Tire Demo/Presentation/Features/Settings/View/InfoSettingsView.swift +++ b/Anyline Tire Demo/Presentation/Features/Settings/View/InfoSettingsView.swift @@ -4,12 +4,13 @@ class InfoSettingsView: UIView { // MARK: - UI properties private lazy var appVersionLabel: ATDTextLabel = { - let label = ATDTextLabel(text: "settings.label.app_version".localized() + " \(SystemInfo.getAppVersion())") + let label = ATDTextLabel(text: "settings.label.app_sdk_version".localized() + " App: \(SystemInfo.getAppVersion()) - SDK: \(SystemInfo.getSDKVersion())") return label }() private lazy var deviceNameLabel: ATDTextLabel = { - let label = ATDTextLabel(text: "settings.label.device_name".localized() + " \(SystemInfo.getDeviceName())") + let deviceName = SystemInfo.getDeviceName() + let label = ATDTextLabel(text: "settings.label.device_name".localized() + " \(deviceName)") return label }() @@ -23,6 +24,7 @@ class InfoSettingsView: UIView { let stackView = UIStackView() stackView.axis = .vertical stackView.distribution = .fillEqually + stackView.spacing = 5 return stackView }() @@ -62,7 +64,6 @@ private extension InfoSettingsView { func setupLayout() { contentVStackView.snp.makeConstraints { make in make.edges.equalToSuperview() - make.height.equalTo(100) } } } diff --git a/Anyline Tire Demo/Presentation/Features/Settings/View/LicenseSettingsView.swift b/Anyline Tire Demo/Presentation/Features/Settings/View/LicenseSettingsView.swift index 528a563..1a20d50 100644 --- a/Anyline Tire Demo/Presentation/Features/Settings/View/LicenseSettingsView.swift +++ b/Anyline Tire Demo/Presentation/Features/Settings/View/LicenseSettingsView.swift @@ -1,22 +1,34 @@ import UIKit class LicenseSettingsView: UIView { - + + enum Constants { + static let emailAddress = "presales@anyline.com" + } + // MARK: - UI properties private lazy var licenseIdLabel: ATDTextLabel = { let label = ATDTextLabel(text: "settings.label.license_id".localized()) return label }() - + lazy var licenseIdTextField: ATDTextField = { let textField = ATDTextField(backgroundColor: ColorStruct.snowWhite) textField.placeholder = "settings.label.license_id".localized() if let licenceID = KeychainManager().getValue(forKey: KeychainKeys.licenseID) { textField.text = licenceID } + textField.returnKeyType = .done return textField }() - + + // button tap is handled by owner + lazy var scanQRCodeButton: UIButton = { + let button = UIButton() + button.setImage(.init(named: "qr_code_icon"), for: .normal) + return button + }() + private lazy var licenseHStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .horizontal @@ -24,9 +36,9 @@ class LicenseSettingsView: UIView { stackView.spacing = 20 return stackView }() - + private lazy var supportLabel: ATDTextLabel = { - let emailAddress = "presales@anyline.com" + let emailAddress = Constants.emailAddress let label = ATDTextLabel(text: "") // Create a string with the email address @@ -38,6 +50,10 @@ class LicenseSettingsView: UIView { let url = URL(string: "mailto:\(emailAddress)")! attributedString.addAttribute(.link, value: url, range: range) + // NOTE: you cannot set a foreground color at the same range which you + // also set as a .link, unless you change the underlying type, currently + // label, to UITextView. + // Set the attributed string for the label label.attributedText = attributedString label.font = FontStruct.proximaNovaBold14 @@ -49,7 +65,7 @@ class LicenseSettingsView: UIView { label.addGestureRecognizer(tapGesture) return label }() - + private lazy var contentVStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical @@ -58,11 +74,11 @@ class LicenseSettingsView: UIView { stackView.spacing = 10 return stackView }() - + // MARK: - Private Properties - + // MARK: - Public properties - + // MARK: - Init override init(frame: CGRect) { super.init(frame: frame) @@ -71,72 +87,69 @@ class LicenseSettingsView: UIView { setupLayout() licenseIdTextField.delegate = self } - + required init?(coder: NSCoder) { super.init(coder: coder) } - + } // MARK: - Private functions private extension LicenseSettingsView { - + // MARK: - Setup UI func configureView() { backgroundColor = ColorStruct.snowWhite - + } - + func addSubviews() { self.addSubview(contentVStackView) self.contentVStackView.addArrangedSubview(licenseHStackView) self.licenseHStackView.addArrangedSubview(licenseIdLabel) self.licenseHStackView.addArrangedSubview(licenseIdTextField) + self.licenseHStackView.addArrangedSubview(scanQRCodeButton) self.contentVStackView.addArrangedSubview(supportLabel) } - + func setupLayout() { - + contentVStackView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + + licenseIdLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) licenseIdLabel.snp.makeConstraints { make in make.centerY.equalTo(licenseIdTextField.snp.centerY) } - + licenseIdTextField.snp.makeConstraints { make in make.height.equalTo(40) - make.width.equalTo(450) + make.width.lessThanOrEqualTo(280) + make.trailing.equalTo(scanQRCodeButton.snp.leading).offset(-10) + } + + scanQRCodeButton.snp.makeConstraints { make in + make.trailing.equalTo(supportLabel) + make.centerY.equalTo(licenseIdTextField) } - } - + // MARK: - Actions @objc func handleLinkTap(_ gesture: UITapGestureRecognizer) { guard let label = gesture.view as? UILabel else { return } guard let attributedString = label.attributedText else { return } - - let location = gesture.location(in: label) - let textStorage = NSTextStorage(attributedString: attributedString) - let layoutManager = NSLayoutManager() - textStorage.addLayoutManager(layoutManager) - let textContainer = NSTextContainer(size: label.bounds.size) - textContainer.lineFragmentPadding = 0 - textContainer.maximumNumberOfLines = label.numberOfLines - textContainer.lineBreakMode = label.lineBreakMode - layoutManager.addTextContainer(textContainer) - - let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - if let url = attributedString.attribute(.link, at: characterIndex, effectiveRange: nil) as? URL { - UIApplication.shared.open(url) + let range = (attributedString.string as NSString).range(of: Constants.emailAddress) + if range.location != NSNotFound, + let emailURL = attributedString.attribute(.link, at: range.location, effectiveRange: nil) as? URL { + UIApplication.shared.open(emailURL, options: [:], completionHandler: nil) } } } // MARK: - UITextFieldDelegate extension LicenseSettingsView: UITextFieldDelegate { - + func textFieldShouldReturn(_ textField: UITextField) -> Bool { self.licenseIdTextField.resignFirstResponder() return true diff --git a/Anyline Tire Demo/Presentation/Features/Settings/View/SettingsView.swift b/Anyline Tire Demo/Presentation/Features/Settings/View/SettingsView.swift index 85484f9..be16a96 100644 --- a/Anyline Tire Demo/Presentation/Features/Settings/View/SettingsView.swift +++ b/Anyline Tire Demo/Presentation/Features/Settings/View/SettingsView.swift @@ -16,7 +16,12 @@ class SettingsView: UIView { stackView.spacing = spacing return stackView }() - + + private lazy var contentScrollView: UIScrollView = { + let scrollView = UIScrollView() + return scrollView + }() + private lazy var settingsLabel: ATDTitleLabel = { let label = ATDTitleLabel(textColor: ColorStruct.stoneGrey, text: "settings.label.settings".localized()) label.textAlignment = .center @@ -37,13 +42,23 @@ class SettingsView: UIView { let view = InfoSettingsView() return view }() - + + lazy var captureSpeedView: CaptureSpeedView = { + let view = CaptureSpeedView() + view.delegate = self + return view + }() + + @objc func didTapQRCode(sender: UIButton) { + delegate?.scanQRCodeTapped() + } + // MARK: - Private Properties - private let spacing: CGFloat = 20 + private let spacing: CGFloat = 15 // MARK: - Public properties weak var delegate: SettingsButtonActionsDelegate? - + // MARK: - Init override init(frame: CGRect) { super.init(frame: frame) @@ -56,7 +71,6 @@ class SettingsView: UIView { required init?(coder: NSCoder) { super.init(coder: coder) } - } // MARK: - Private functions @@ -65,30 +79,41 @@ private extension SettingsView { // MARK: - Setup UI func configureView() { backgroundColor = ColorStruct.snowWhite + licenseView.scanQRCodeButton.addTarget(self, + action: #selector(didTapQRCode), + for: .touchUpInside) } func addSubviews() { self.addSubview(buttonsView) self.addSubview(contentVStackView) - self.contentVStackView.addArrangedSubview(settingsLabel) - self.contentVStackView.addArrangedSubview(imperialSystemView) - self.contentVStackView.addArrangedSubview(licenseView) - self.contentVStackView.addArrangedSubview(infoView) + + contentVStackView.addArrangedSubview(settingsLabel) + contentVStackView.addArrangedSubview(imperialSystemView) + contentVStackView.addArrangedSubview(licenseView) + contentVStackView.addArrangedSubview(captureSpeedView) + contentVStackView.addArrangedSubview(infoView) } func setupLayout() { buttonsView.snp.makeConstraints { make in make.trailing.bottom.top.equalToSuperview() - make.width.equalTo(200) + make.width.equalTo(200).priority(.low) } - + contentVStackView.snp.makeConstraints { make in make.leading.equalTo(80) - make.trailing.equalTo(buttonsView.safeAreaLayoutGuide.snp.leading) + make.width.equalTo(500) make.top.equalTo(20) make.bottom.equalTo(0) } - + + captureSpeedView.snp.makeConstraints { make in + make.height.equalTo(35) + make.leading.equalToSuperview() + make.trailing.equalToSuperview() + } + settingsLabel.snp.makeConstraints { make in make.top.equalTo(self.safeAreaInsets.top) make.centerX.equalTo(self) @@ -124,3 +149,13 @@ extension SettingsView: ImperialSystemSettingsViewDelegate { delegate?.imageTapped(tapGestureRecognizer: tapGestureRecognizer) } } + +extension SettingsView: CaptureSpeedViewDelegate { + func buttonTapped(sender: UIButton) { + delegate?.scanSpeedDialogRequested(sender: sender, options: [.fast, .slow], completion: { scanSpeed in + if let scanSpeed = scanSpeed { + self.captureSpeedView.scanSpeed = scanSpeed + } + }) + } +} diff --git a/Anyline Tire Demo/Presentation/Features/Settings/ViewModel/SettingsViewModel.swift b/Anyline Tire Demo/Presentation/Features/Settings/ViewModel/SettingsViewModel.swift index 3f2414f..bd87bf5 100644 --- a/Anyline Tire Demo/Presentation/Features/Settings/ViewModel/SettingsViewModel.swift +++ b/Anyline Tire Demo/Presentation/Features/Settings/ViewModel/SettingsViewModel.swift @@ -20,23 +20,10 @@ class SettingsViewModel { self.settingsViewModelDelegate = delegate } - func testSetup(context: UIViewController) { + func testLicenseKey(_ licenseKey: String, context: UIViewController) { do { - let keychainManager = KeychainManager() - - guard let licenceID = keychainManager.getValue(forKey: KeychainKeys.licenseID) else { - settingsViewModelDelegate?.showError(error: "error.invalid_license".localized()) - return - } - - try AnylineTireTreadSdk.companion.doInit(licenseKey: licenceID, context: context) - - if AnylineTireTreadSdk.companion.isInitialized { - requestPermissionsAndProceed(context: context) - } else { - let errorMessage = "error.invalid_license".localized() - self.settingsViewModelDelegate?.showError(error: errorMessage) - } + try AnylineTireTreadSdk.companion.doInit(licenseKey: licenseKey, context: context) + requestPermissionsAndProceed(context: context) } catch { let errorMessage = "error.invalid_license".localized() + " (\(error.localizedDescription))" self.settingsViewModelDelegate?.showError(error: errorMessage) diff --git a/Anyline Tire Demo/Resources/Audio/tiretread_sound_low_beep.wav b/Anyline Tire Demo/Resources/Audio/tiretread_sound_beep_too_close.wav similarity index 100% rename from Anyline Tire Demo/Resources/Audio/tiretread_sound_low_beep.wav rename to Anyline Tire Demo/Resources/Audio/tiretread_sound_beep_too_close.wav diff --git a/Anyline Tire Demo/Resources/Audio/tiretread_sound_high_beep.wav b/Anyline Tire Demo/Resources/Audio/tiretread_sound_beep_too_far.wav similarity index 100% rename from Anyline Tire Demo/Resources/Audio/tiretread_sound_high_beep.wav rename to Anyline Tire Demo/Resources/Audio/tiretread_sound_beep_too_far.wav diff --git a/Anyline Tire Demo/Resources/Audio/tiretread_sound_start.mp3 b/Anyline Tire Demo/Resources/Audio/tiretread_sound_start.mp3 deleted file mode 100644 index 32337ba..0000000 Binary files a/Anyline Tire Demo/Resources/Audio/tiretread_sound_start.mp3 and /dev/null differ diff --git a/Anyline Tire Demo/Resources/Audio/tiretread_sound_start.wav b/Anyline Tire Demo/Resources/Audio/tiretread_sound_start.wav new file mode 100644 index 0000000..5033b62 Binary files /dev/null and b/Anyline Tire Demo/Resources/Audio/tiretread_sound_start.wav differ diff --git a/Anyline Tire Demo/Resources/Helpers/VolumeButtonObserver.swift b/Anyline Tire Demo/Resources/Helpers/VolumeButtonObserver.swift index d545417..86733ef 100644 --- a/Anyline Tire Demo/Resources/Helpers/VolumeButtonObserver.swift +++ b/Anyline Tire Demo/Resources/Helpers/VolumeButtonObserver.swift @@ -2,35 +2,25 @@ import AVFoundation class VolumeButtonObserver { - private let audioSession = AVAudioSession.sharedInstance() - private var observer: NSKeyValueObservation? - private var previousVolume: Float - var onVolumeButtonPressed: (() -> Void)? - + + deinit { + NotificationCenter.default.removeObserver(self) + } + init() { - do { - try audioSession.setActive(true) - previousVolume = audioSession.outputVolume - observer = audioSession.observe(\.outputVolume) { [weak self] _, _ in - self?.handleVolumeButtonPressed() - } - } catch { - print("Error configuring audio session: \(error)") - previousVolume = 0.0 - } + // This class' deinit has a call to deregister itself as the + // observer for this event. + NotificationCenter.default + .addObserver(self, + selector: #selector(volChanged(_:)), + name: NSNotification.Name(rawValue: "SystemVolumeDidChange"), + object: nil) } - - private func handleVolumeButtonPressed() { - let currentVolume = audioSession.outputVolume - if currentVolume < previousVolume { + + @objc func volChanged(_ notif: Notification) { + if notif.userInfo?["Reason"] as? String == "ExplicitVolumeChange" { onVolumeButtonPressed?() } - previousVolume = currentVolume - } - - deinit { - observer?.invalidate() - try? audioSession.setActive(false) } } diff --git a/Anyline Tire Demo/Resources/System/SystemInfo.swift b/Anyline Tire Demo/Resources/System/SystemInfo.swift index efceeae..5716387 100644 --- a/Anyline Tire Demo/Resources/System/SystemInfo.swift +++ b/Anyline Tire Demo/Resources/System/SystemInfo.swift @@ -1,3 +1,4 @@ +import AnylineTireTreadSdk import UIKit class SystemInfo { @@ -6,11 +7,346 @@ class SystemInfo { let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String return appVersion ?? "" } + + static func getSDKVersion() -> String { + return AnylineTireTreadSdk.companion.sdkVersion + } static func getDeviceName() -> String { - let deviceModel = UIDevice.current.model + let deviceModel = UIDevice().type.rawValue let deviceOSVersion = UIDevice.current.systemVersion - let versionString = "\(deviceModel) (\(deviceOSVersion))" + let versionString = "\(deviceModel) (iOS \(deviceOSVersion))" return versionString } } + +// Based on https://stackoverflow.com/a/46234519 +// This has to be updated with each new device release +public enum DeviceModel: String { + + // Simulator + case simulator = "simulator/sandbox", + + // iPod + iPod1 = "iPod 1", + iPod2 = "iPod 2", + iPod3 = "iPod 3", + iPod4 = "iPod 4", + iPod5 = "iPod 5", + iPod6 = "iPod 6", + iPod7 = "iPod 7", + + // iPad + iPad2 = "iPad 2", + iPad3 = "iPad 3", + iPad4 = "iPad 4", + iPadAir = "iPad Air ", + iPadAir2 = "iPad Air 2", + iPadAir3 = "iPad Air 3", + iPadAir4 = "iPad Air 4", + iPadAir5 = "iPad Air 5", + iPad5 = "iPad 5", //iPad 2017 + iPad6 = "iPad 6", //iPad 2018 + iPad7 = "iPad 7", //iPad 2019 + iPad8 = "iPad 8", //iPad 2020 + iPad9 = "iPad 9", //iPad 2021 + iPad10 = "iPad 10", //iPad 2022 + + // iPad Mini + iPadMini = "iPad Mini", + iPadMini2 = "iPad Mini 2", + iPadMini3 = "iPad Mini 3", + iPadMini4 = "iPad Mini 4", + iPadMini5 = "iPad Mini 5", + iPadMini6 = "iPad Mini 6", + + // iPad Pro + iPadPro9_7 = "iPad Pro 9.7\"", + iPadPro10_5 = "iPad Pro 10.5\"", + iPadPro11 = "iPad Pro 11\"", + iPadPro2_11 = "iPad Pro 11\" 2nd gen", + iPadPro3_11 = "iPad Pro 11\" 3rd gen", + iPadPro12_9 = "iPad Pro 12.9\"", + iPadPro2_12_9 = "iPad Pro 2 12.9\"", + iPadPro3_12_9 = "iPad Pro 3 12.9\"", + iPadPro4_12_9 = "iPad Pro 4 12.9\"", + iPadPro5_12_9 = "iPad Pro 5 12.9\"", + + // iPhone + iPhone4 = "iPhone 4", + iPhone4S = "iPhone 4S", + iPhone5 = "iPhone 5", + iPhone5S = "iPhone 5S", + iPhone5C = "iPhone 5C", + iPhone6 = "iPhone 6", + iPhone6Plus = "iPhone 6 Plus", + iPhone6S = "iPhone 6S", + iPhone6SPlus = "iPhone 6S Plus", + iPhoneSE = "iPhone SE", + iPhone7 = "iPhone 7", + iPhone7Plus = "iPhone 7 Plus", + iPhone8 = "iPhone 8", + iPhone8Plus = "iPhone 8 Plus", + iPhoneX = "iPhone X", + iPhoneXS = "iPhone XS", + iPhoneXSMax = "iPhone XS Max", + iPhoneXR = "iPhone XR", + iPhone11 = "iPhone 11", + iPhone11Pro = "iPhone 11 Pro", + iPhone11ProMax = "iPhone 11 Pro Max", + iPhoneSE2 = "iPhone SE 2nd gen", + iPhone12Mini = "iPhone 12 Mini", + iPhone12 = "iPhone 12", + iPhone12Pro = "iPhone 12 Pro", + iPhone12ProMax = "iPhone 12 Pro Max", + iPhone13Mini = "iPhone 13 Mini", + iPhone13 = "iPhone 13", + iPhone13Pro = "iPhone 13 Pro", + iPhone13ProMax = "iPhone 13 Pro Max", + iPhoneSE3 = "iPhone SE 3nd gen", + iPhone14 = "iPhone 14", + iPhone14Plus = "iPhone 14 Plus", + iPhone14Pro = "iPhone 14 Pro", + iPhone14ProMax = "iPhone 14 Pro Max", + iPhone15 = "iPhone 15", + iPhone15Plus = "iPhone 15 Plus", + iPhone15Pro = "iPhone 15 Pro", + iPhone15ProMax = "iPhone 15 Pro Max", + + // Apple Watch + AppleWatch1 = "Apple Watch 1gen", + AppleWatchS1 = "Apple Watch Series 1", + AppleWatchS2 = "Apple Watch Series 2", + AppleWatchS3 = "Apple Watch Series 3", + AppleWatchS4 = "Apple Watch Series 4", + AppleWatchS5 = "Apple Watch Series 5", + AppleWatchSE = "Apple Watch Special Edition", + AppleWatchS6 = "Apple Watch Series 6", + AppleWatchS7 = "Apple Watch Series 7", + + // Apple TV + AppleTV1 = "Apple TV 1gen", + AppleTV2 = "Apple TV 2gen", + AppleTV3 = "Apple TV 3gen", + AppleTV4 = "Apple TV 4gen", + AppleTV_4K = "Apple TV 4K", + AppleTV2_4K = "Apple TV 4K 2gen", + + unrecognized = "?unrecognized?" +} + +public extension UIDevice { + + var type: DeviceModel { + var systemInfo = utsname() + uname(&systemInfo) + let modelCode = withUnsafePointer(to: &systemInfo.machine) { + $0.withMemoryRebound(to: CChar.self, capacity: 1) { + ptr in String.init(validatingUTF8: ptr) + } + } + + let modelMap : [String: DeviceModel] = [ + + // Simulator + "i386" : .simulator, + "x86_64" : .simulator, + + // iPod + "iPod1,1" : .iPod1, + "iPod2,1" : .iPod2, + "iPod3,1" : .iPod3, + "iPod4,1" : .iPod4, + "iPod5,1" : .iPod5, + "iPod7,1" : .iPod6, + "iPod9,1" : .iPod7, + + // iPad + "iPad2,1" : .iPad2, + "iPad2,2" : .iPad2, + "iPad2,3" : .iPad2, + "iPad2,4" : .iPad2, + "iPad3,1" : .iPad3, + "iPad3,2" : .iPad3, + "iPad3,3" : .iPad3, + "iPad3,4" : .iPad4, + "iPad3,5" : .iPad4, + "iPad3,6" : .iPad4, + "iPad6,11" : .iPad5, //iPad 2017 + "iPad6,12" : .iPad5, + "iPad7,5" : .iPad6, //iPad 2018 + "iPad7,6" : .iPad6, + "iPad7,11" : .iPad7, //iPad 2019 + "iPad7,12" : .iPad7, + "iPad11,6" : .iPad8, //iPad 2020 + "iPad11,7" : .iPad8, + "iPad12,1" : .iPad9, //iPad 2021 + "iPad12,2" : .iPad9, + "iPad13,18" : .iPad10, + "iPad13,19" : .iPad10, + + // iPad Mini + "iPad2,5" : .iPadMini, + "iPad2,6" : .iPadMini, + "iPad2,7" : .iPadMini, + "iPad4,4" : .iPadMini2, + "iPad4,5" : .iPadMini2, + "iPad4,6" : .iPadMini2, + "iPad4,7" : .iPadMini3, + "iPad4,8" : .iPadMini3, + "iPad4,9" : .iPadMini3, + "iPad5,1" : .iPadMini4, + "iPad5,2" : .iPadMini4, + "iPad11,1" : .iPadMini5, + "iPad11,2" : .iPadMini5, + "iPad14,1" : .iPadMini6, + "iPad14,2" : .iPadMini6, + + // iPad Pro + "iPad6,3" : .iPadPro9_7, + "iPad6,4" : .iPadPro9_7, + "iPad7,3" : .iPadPro10_5, + "iPad7,4" : .iPadPro10_5, + "iPad6,7" : .iPadPro12_9, + "iPad6,8" : .iPadPro12_9, + "iPad7,1" : .iPadPro2_12_9, + "iPad7,2" : .iPadPro2_12_9, + "iPad8,1" : .iPadPro11, + "iPad8,2" : .iPadPro11, + "iPad8,3" : .iPadPro11, + "iPad8,4" : .iPadPro11, + "iPad8,9" : .iPadPro2_11, + "iPad8,10" : .iPadPro2_11, + "iPad13,4" : .iPadPro3_11, + "iPad13,5" : .iPadPro3_11, + "iPad13,6" : .iPadPro3_11, + "iPad13,7" : .iPadPro3_11, + "iPad8,5" : .iPadPro3_12_9, + "iPad8,6" : .iPadPro3_12_9, + "iPad8,7" : .iPadPro3_12_9, + "iPad8,8" : .iPadPro3_12_9, + "iPad8,11" : .iPadPro4_12_9, + "iPad8,12" : .iPadPro4_12_9, + "iPad13,8" : .iPadPro5_12_9, + "iPad13,9" : .iPadPro5_12_9, + "iPad13,10" : .iPadPro5_12_9, + "iPad13,11" : .iPadPro5_12_9, + + // iPad Air + "iPad4,1" : .iPadAir, + "iPad4,2" : .iPadAir, + "iPad4,3" : .iPadAir, + "iPad5,3" : .iPadAir2, + "iPad5,4" : .iPadAir2, + "iPad11,3" : .iPadAir3, + "iPad11,4" : .iPadAir3, + "iPad13,1" : .iPadAir4, + "iPad13,2" : .iPadAir4, + "iPad13,16" : .iPadAir5, + "iPad13,17" : .iPadAir5, + + // iPhone + "iPhone3,1" : .iPhone4, + "iPhone3,2" : .iPhone4, + "iPhone3,3" : .iPhone4, + "iPhone4,1" : .iPhone4S, + "iPhone5,1" : .iPhone5, + "iPhone5,2" : .iPhone5, + "iPhone5,3" : .iPhone5C, + "iPhone5,4" : .iPhone5C, + "iPhone6,1" : .iPhone5S, + "iPhone6,2" : .iPhone5S, + "iPhone7,1" : .iPhone6Plus, + "iPhone7,2" : .iPhone6, + "iPhone8,1" : .iPhone6S, + "iPhone8,2" : .iPhone6SPlus, + "iPhone8,4" : .iPhoneSE, + "iPhone9,1" : .iPhone7, + "iPhone9,3" : .iPhone7, + "iPhone9,2" : .iPhone7Plus, + "iPhone9,4" : .iPhone7Plus, + "iPhone10,1" : .iPhone8, + "iPhone10,4" : .iPhone8, + "iPhone10,2" : .iPhone8Plus, + "iPhone10,5" : .iPhone8Plus, + "iPhone10,3" : .iPhoneX, + "iPhone10,6" : .iPhoneX, + "iPhone11,2" : .iPhoneXS, + "iPhone11,4" : .iPhoneXSMax, + "iPhone11,6" : .iPhoneXSMax, + "iPhone11,8" : .iPhoneXR, + "iPhone12,1" : .iPhone11, + "iPhone12,3" : .iPhone11Pro, + "iPhone12,5" : .iPhone11ProMax, + "iPhone12,8" : .iPhoneSE2, + "iPhone13,1" : .iPhone12Mini, + "iPhone13,2" : .iPhone12, + "iPhone13,3" : .iPhone12Pro, + "iPhone13,4" : .iPhone12ProMax, + "iPhone14,4" : .iPhone13Mini, + "iPhone14,5" : .iPhone13, + "iPhone14,2" : .iPhone13Pro, + "iPhone14,3" : .iPhone13ProMax, + "iPhone14,6" : .iPhoneSE3, + "iPhone14,7" : .iPhone14, + "iPhone14,8" : .iPhone14Plus, + "iPhone15,2" : .iPhone14Pro, + "iPhone15,3" : .iPhone14ProMax, + "iPhone15,4" : .iPhone15, + "iPhone15,5" : .iPhone15Plus, + "iPhone16,1" : .iPhone15Pro, + "iPhone16,2" : .iPhone15ProMax, + + // Apple Watch + "Watch1,1" : .AppleWatch1, + "Watch1,2" : .AppleWatch1, + "Watch2,6" : .AppleWatchS1, + "Watch2,7" : .AppleWatchS1, + "Watch2,3" : .AppleWatchS2, + "Watch2,4" : .AppleWatchS2, + "Watch3,1" : .AppleWatchS3, + "Watch3,2" : .AppleWatchS3, + "Watch3,3" : .AppleWatchS3, + "Watch3,4" : .AppleWatchS3, + "Watch4,1" : .AppleWatchS4, + "Watch4,2" : .AppleWatchS4, + "Watch4,3" : .AppleWatchS4, + "Watch4,4" : .AppleWatchS4, + "Watch5,1" : .AppleWatchS5, + "Watch5,2" : .AppleWatchS5, + "Watch5,3" : .AppleWatchS5, + "Watch5,4" : .AppleWatchS5, + "Watch5,9" : .AppleWatchSE, + "Watch5,10" : .AppleWatchSE, + "Watch5,11" : .AppleWatchSE, + "Watch5,12" : .AppleWatchSE, + "Watch6,1" : .AppleWatchS6, + "Watch6,2" : .AppleWatchS6, + "Watch6,3" : .AppleWatchS6, + "Watch6,4" : .AppleWatchS6, + "Watch6,6" : .AppleWatchS7, + "Watch6,7" : .AppleWatchS7, + "Watch6,8" : .AppleWatchS7, + "Watch6,9" : .AppleWatchS7, + + // Apple TV + "AppleTV1,1" : .AppleTV1, + "AppleTV2,1" : .AppleTV2, + "AppleTV3,1" : .AppleTV3, + "AppleTV3,2" : .AppleTV3, + "AppleTV5,3" : .AppleTV4, + "AppleTV6,2" : .AppleTV_4K, + "AppleTV11,1" : .AppleTV2_4K + ] + + guard let map = modelCode, let model = modelMap[map] else { return DeviceModel.unrecognized } + if model == .simulator { + if let simModelCode = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] { + if let simModel = modelMap[simModelCode] { + return simModel + } + } + } + return model + } +} diff --git a/Anyline Tire Demo/en.lproj/Localizable.strings b/Anyline Tire Demo/en.lproj/Localizable.strings index 610c41d..aca8d86 100644 --- a/Anyline Tire Demo/en.lproj/Localizable.strings +++ b/Anyline Tire Demo/en.lproj/Localizable.strings @@ -1,7 +1,13 @@ // Landing View -"landing.button.start" = "START"; +"landing.button.start" = "OPEN SCANNER"; "landing.button.settings" = "SETTINGS"; +"landing.button.tutorial" = "TUTORIAL"; "landing.label.welcome" = "Welcome to the Anyline\nTire Tread Scanner!"; +"landing.button.cancel" = "CANCEL"; +"landing.button.back" = "BACK"; +"landing.text.start" = "To start scanning, press the OPEN SCANNER button."; +"landing.text.tutorial" = "Watch the TUTORIAL to learn how to scan."; +"landing.text.info" = "For best scanning practices, please go to tiretreaddocu.anyline.com"; // Settings "settings.button.ok" = "OK"; @@ -9,14 +15,19 @@ "settings.button.test_setup" = "TEST SETUP"; "settings.button.cancel" = "CANCEL"; "settings.label.settings" = "Settings"; + "settings.label.use_imperial_system" = "Use Imperial System:"; -"settings.label.license_id" = "License ID:"; +"settings.label.license_id" = "License Key:"; "settings.label.accuraccy_speed" = "Accuracy / Speed: High Speed"; "settings.label.high_accuracy" = "High Accuracy"; -"settings.label.app_version" = "App Version:"; +"settings.label.app_sdk_version" = "App and SDK Version:"; "settings.label.device_name" = "Device Name:"; "settings.label.upload" = "Upload kbps"; "settings.label.support" = "If you need a license please reach out to"; +"settings.label.capture_speed" = "Capture Speed:"; +"settings.label.capture_speed_fast" = "Fast"; +"settings.label.capture_speed_slow" = "Slow"; +"settings.label.capture_speed.tip" = "(Tap to change)"; "success.title" = "Success!"; "success.description" = "The SDK has been initialized successfully!"; @@ -36,11 +47,24 @@ "feedback.button.submit" = "Submit"; "feedback.button.cancel" = "Cancel"; +// Recorder +"recorder.loading.message" = "Processing..."; +"recorder.msg.scanned_tire_id_1" = "This is the scanned Tire ID:"; +"recorder.msg.scanned_tire_id_2" = "What would you like to do?"; +"recorder.alert.title" = "Data Collection"; +"recorder.alert.message_success" = "Your data recording has been successfully submitted."; +"recorder.alert.message_failure" = "Your data recording was not sent successfully. Retry?"; +"recorder.alert.btn_failure" = "Retry"; +"recorder.button.send" = "Submit"; +"recorder.button.title.rescan" = "Rescan Barcode"; +"recorder.button.title.abort" = "Abort"; +"recorder.button.title.ok" = "OK"; + // Error "error.title" = "Error"; "error.description" = "Something went wrong!, Please try again later"; "error.invalid_license" = "Your license key is invalid or the server is currently unavailable."; -"error.key.description" = "Unable to authenticate! Please check if you entered the correct License ID."; +"error.key.description" = "Unable to authenticate! Please check if you entered the correct license key."; "error.scan.description" = "The scan was not successful, please try again.\n\nMake sure you do not move the device too fast and keep the right distance."; "error.scan.dot.description" = "Unable to set a focus point. Please adjust the distance and the focus on the tire and try again."; "error.pdf.description" = "The PDF data is unavailable! Please try again later."; @@ -60,3 +84,8 @@ // Details "details.button.download" = "DOWNLOAD"; + +// QR Code Reader +"qrcodereader.guide_text.tire_id" = "Scan the QR code of the tire you have just scanned."; +"qrcodereader.guide_text.license_key" = "Scan the QR code of your license key."; +"qrcodereader.message.invalid_license_key" = "No valid license found."; diff --git a/README.md b/README.md index 301310a..563390a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Anyline Tire Tread SDK Showcase +# Anyline TireTread Example for iOS This application runs on the **Anyline Tire Tread SDK** that helps users easily and accurately assess the health of their vehicle's tires. The application code is included @@ -7,241 +7,18 @@ to help developers build the SDK into their own iOS applications. For more information and additional guidance, please check the [Tire Tread SDK iOS Documentation](https://documentation.anyline.com/tiretreadsdk-component/latest/ios/overview.html). -## Requirements -- Xcode version: 14.3 or later -- iOS version: 15.0 or later -- Dependency manager: Swift Package Manager (Cocoapods also supported) - -## How to Run The Showcase App - -1. Clone the repository -2. Open the `Anyline Tire Demo.xcodeproj` -3. Install dependencies -4. Build and run the project on a simulator or a device - -## Integrate the Anyline Tire Tread SDK into a Native iOS App - -The following guide will walk you through the process of integrating `AnylineTireTreadSdk` into a native iOS application. - -### 1. Import the `AnylineTireTreadSdk` Framework - -#### With Swift Package Manager - -- In Xcode, go to `File -> Swift Packages -> Add Package Dependency` and enter the Swift Package repository of -`AnylineTireTreadSdk`. - -After cloning the `AnylineTireTreadSdk` repository, you may import the framework into your native iOS -application code: - -``` -import AnylineTireTreadSdk -``` - -#### With CocoaPods - -- Add the following line to your `Podfile`: - - ``` - pod 'AnylineTireTreadSdk', '~>2' - ``` - -- Run `pod install`. - -### 2. Licensing - -In order to use the `AnylineTireTreadSdk`, a valid license key is required. Please contact presales@anyline.com to -obtain a license key if needed. - -Before making any calls with `AnylineTireTreadSdk`, initialize it with a license key as follows: - -``` -func tryInitializeSdk(context: UIViewController) { - do { - let licenseKey = "" - - try AnylineTireTreadSdk.companion.doInit(licenseKey: licenseKey, context: context) - - // Continue setup after successful initialization - } catch { - // Handle initialization error - } -} -``` - -In the code above, replace `` with your actual license key string. This function attempts to -initialize `AnylineTireTreadSdk` with the license key provided. If the SDK fails to initialize, you should handle -this error appropriately. - -It is recommended to store the license key securely (such as in the system keychain). - -### 3. Check Camera Permissions - -Before starting the scan process, it is crucial to check and request camera permissions, as they are required -in order for the SDK to function correctly. - -First, in your application's Info.plist file, add an `NSCameraUsageDescription` string value which explains -to your users why the app would be requiring camera permission access from them. - -Then, right before requesting for a scan view from the SDK, call the following: - -``` -func requestPermissionsAndProceed(context: UIViewController) { - let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) - - switch cameraAuthorizationStatus { - case .authorized: - // Continue with setup - case .notDetermined: - AVCaptureDevice.requestAccess(for: .video) { granted in - DispatchQueue.main.async { - if granted { - // Continue with setup - } else { - // Handle denied access - } - } - } - default: - // Handle other cases - } -} -``` - -### 4. Implement the `ScannerViewControllerHolder` Interface - -In order to set up the scan view correctly, make your UIViewController subclass implement the -`ScannerViewControllerHolder` interface. - -This interface gives your view controller two properties: - -1. `scannerViewController`: This property will hold an instance of `TireTreadScannerViewController`, which -takes over the scanning process. Once it becomes available, you should add its main view into your -UIViewController's view hierarchy. -2. `dismissViewController`: This is a function property that will handle the event of the scan view being -dismissed. Assign it with a block that removes the scan view from your UIViewController's view hierarchy. - -Here is an example of how to implement the ScannerViewControllerHolder interface: -``` -class YourViewController: UIViewController, ScannerViewControllerHolder { - - // Implement the properties defined by the ScannerViewControllerHolder interface - var scannerViewController: UIViewController? - var dismissViewController: (() -> Void)? - - // The rest of your class implementation -} -``` - -Remember to replace `YourViewController` with the actual name of your view controller. - -### 5. Set up the Scan View Controller - -Once the SDK has been initialized and the necessary permissions have been granted, you can setup the -scan view controller. - -``` -private func setupTireTreadScanView() { - let config = TireTreadScanViewConfig(measurementSystem: .metric, useDefaultUi: true, useDefaultHaptic: false) - TireTreadScanViewKt.TireTreadScanView(context: self, config: config) { error in - // Handle initialization error - } -} - -private func addScanViewControllerAsChild() { - guard let scannerViewController = scannerViewController else { - // Handle error - return - } - addChild(scannerViewController) - view.addSubview(scannerViewController.view) - scannerViewController.didMove(toParent: self) -} -``` - -Once added, the Tire Tread scan view will be displayed, and the user will be guided by on-screen -prompts to scan a tire correctly and submit the results online for analysis. - -### 6. Implementing the `TireTreadScanViewCallback` - -Your UIViewController should also implement `TireTreadScanViewCallback` to handle scan events. -For example: - -``` -extension YourViewController: TireTreadScanViewCallback { - func onUploadCompleted(uuid: String?) { - // Handle upload completion - } - - // Implement the rest of the callback methods... -} -``` - -`onUploadCompleted` is called when the scanner has taken enough tire image frames and have -successfully uploaded them to the cloud for processing. A UUID string associated with the tire -scan session is provided in the callback in order for you to make the subsequent request for -the scan results. - -### 7. Fetch the Scan Results - -Once the scan has been successfully completed, use the scan's UUID to retrieve the measurement -values from `AnylineTireTreadSdk`: - -``` -private func fetchTreadDepthResult() { - AnylineTireTreadSdk.companion.getTreadDepthReportResult( - measurementUuid: uuid, - onGetTreadDepthReportResultSucceed: { [weak self] response in - response.body { resultDTO, error in - guard let self = self else { return } - guard let status = resultDTO?.measurement.status else { - // handle error - return - } - - // obtain the scan results - self.handleTreadDepthResult(treadDepthResult: resultDTO?.result, status: status) - } - }, - onGetTreadDepthReportResultFailed: { [weak self] response, exception in - // handle error - } - ) -} -``` - -We recommend implementing this call with a polling logic (e.g. running every 3 seconds or more), -until the final results become available. - - -## Dependencies - -List of third-party libraries and frameworks used in the project: -- Alamofire - Networking library -- SnapKit - Auto Layout DSL -- KeychainSwift - Keychain helper library - -## Project Structure - -- DataStorage: Contains the modules responsible for managing the app's local data storage -- Domain: Responsible for handling network requests and other domain-specific logic -- Presentation: Contains the UIKit components -- Resources: Contains the supplementary assets and helper classes -- Fonts - ---- - -## Get Help (Support) +## Get Help (Support) ## We don't actively monitor the GitHub Issues, please raise a support request using the [Anyline Helpdesk](https://support.anyline.com/). When raising a support request based on this GitHub Issue, please fill out and include the following information: - ``` -Support request concerning Anyline GitHub Repository: anyline-tiretread-showcase-app-ios +Support request concerning Anyline GitHub Repository: anyline-tiretread-ios-example ``` Thank you! -## License + +## License ## See LICENSE file. \ No newline at end of file