From 1752d89b78652a95de2f59cbcbe1a8f3cbf4241b Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Sat, 5 Mar 2022 17:20:15 +0300 Subject: [PATCH] Latest changes --- AutoCat2.xcodeproj/project.pbxproj | 130 ++++++++++++------ .../xcschemes/xcschememanagement.plist | 18 +++ AutoCat2Tests/ApiTests.swift | 2 +- AutoCat2Tests/SettingsTests.swift | 2 +- Shared/Assets.xcassets/Colors/Contents.json | 6 + .../PlateBackground.colorset/Contents.json | 38 +++++ .../Contents.json | 38 +++++ .../PlateForeground.colorset/Contents.json | 38 +++++ .../Shared.xcdatamodel/contents | 14 +- Shared/AutoCat2App.swift | 4 +- Shared/Fonts/RoadNumbers.otf | Bin 0 -> 9520 bytes Shared/Fonts/RoadNumbers2.0.otf | Bin 0 -> 5944 bytes Shared/Models/PlateNumber.swift | 25 ++++ Shared/Models/Settings.swift | 15 +- Shared/Models/VBrand.swift | 22 +++ Shared/Models/VName.swift | 19 +++ Shared/Models/Vehicle.swift | 35 ++++- Shared/Services/StorageService.swift | 56 ++++++++ Shared/Services/VehicleService.swift | 29 ++++ .../GenericJSON/Initialization.swift | 124 ----------------- Shared/ThirdParty/GenericJSON/JSON.swift | 82 ----------- Shared/ThirdParty/GenericJSON/Merging.swift | 41 ------ Shared/ThirdParty/GenericJSON/Querying.swift | 107 -------------- Shared/Utils/Api.swift | 35 ++++- Shared/Utils/Constants.swift | 8 +- Shared/ViewModels/AuthVM.swift | 12 +- Shared/Views/AuthView.swift | 1 + Shared/Views/CheckView.swift | 2 +- Shared/Views/PlateNumberView.swift | 67 +++++++++ Shared/Views/macOS/CheckNumberPopup.swift | 56 ++++++++ Shared/Views/{ => macOS}/MainViewBig.swift | 34 +++-- 31 files changed, 631 insertions(+), 429 deletions(-) create mode 100644 Shared/Assets.xcassets/Colors/Contents.json create mode 100644 Shared/Assets.xcassets/Colors/PlateBackground.colorset/Contents.json create mode 100644 Shared/Assets.xcassets/Colors/PlateBackgroundError.colorset/Contents.json create mode 100644 Shared/Assets.xcassets/Colors/PlateForeground.colorset/Contents.json create mode 100644 Shared/Fonts/RoadNumbers.otf create mode 100644 Shared/Fonts/RoadNumbers2.0.otf create mode 100644 Shared/Models/PlateNumber.swift create mode 100644 Shared/Models/VBrand.swift create mode 100644 Shared/Models/VName.swift create mode 100644 Shared/Services/StorageService.swift create mode 100644 Shared/Services/VehicleService.swift delete mode 100644 Shared/ThirdParty/GenericJSON/Initialization.swift delete mode 100644 Shared/ThirdParty/GenericJSON/JSON.swift delete mode 100644 Shared/ThirdParty/GenericJSON/Merging.swift delete mode 100644 Shared/ThirdParty/GenericJSON/Querying.swift create mode 100644 Shared/Views/PlateNumberView.swift create mode 100644 Shared/Views/macOS/CheckNumberPopup.swift rename Shared/Views/{ => macOS}/MainViewBig.swift (56%) diff --git a/AutoCat2.xcodeproj/project.pbxproj b/AutoCat2.xcodeproj/project.pbxproj index 92287c7..bf88cd4 100644 --- a/AutoCat2.xcodeproj/project.pbxproj +++ b/AutoCat2.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 7A2329B926AF122400264CFA /* MainViewSmall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2329B826AF122400264CFA /* MainViewSmall.swift */; }; 7A2329BA26AF122400264CFA /* MainViewSmall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2329B826AF122400264CFA /* MainViewSmall.swift */; }; - 7A2329BC26AF123300264CFA /* MainViewBig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2329BB26AF123200264CFA /* MainViewBig.swift */; }; 7A2329BD26AF123300264CFA /* MainViewBig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2329BB26AF123200264CFA /* MainViewBig.swift */; }; 7A2329BF26AF140F00264CFA /* CheckView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2329BE26AF140F00264CFA /* CheckView.swift */; }; 7A2329C026AF140F00264CFA /* CheckView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2329BE26AF140F00264CFA /* CheckView.swift */; }; @@ -57,25 +56,34 @@ 7A971F0A26AD74FD007E527B /* ApiMethodMockProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F0926AD74FD007E527B /* ApiMethodMockProtocol.swift */; }; 7A971F0D26AD7D4C007E527B /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F0C26AD7D4C007E527B /* AnyEncodable.swift */; }; 7A971F0E26AD7D4C007E527B /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F0C26AD7D4C007E527B /* AnyEncodable.swift */; }; - 7A971F1526AD8AEB007E527B /* Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1026AD8AEB007E527B /* Initialization.swift */; }; - 7A971F1626AD8AEB007E527B /* Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1026AD8AEB007E527B /* Initialization.swift */; }; - 7A971F1726AD8AEB007E527B /* Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1126AD8AEB007E527B /* Querying.swift */; }; - 7A971F1826AD8AEB007E527B /* Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1126AD8AEB007E527B /* Querying.swift */; }; - 7A971F1926AD8AEB007E527B /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1226AD8AEB007E527B /* JSON.swift */; }; - 7A971F1A26AD8AEB007E527B /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1226AD8AEB007E527B /* JSON.swift */; }; - 7A971F1D26AD8AEB007E527B /* Merging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1426AD8AEB007E527B /* Merging.swift */; }; - 7A971F1E26AD8AEB007E527B /* Merging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1426AD8AEB007E527B /* Merging.swift */; }; 7A971F2026ADC351007E527B /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1F26ADC351007E527B /* ApiError.swift */; }; 7A971F2126ADC351007E527B /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F1F26ADC351007E527B /* ApiError.swift */; }; 7A971F2326ADF74B007E527B /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F2226ADF74B007E527B /* MainView.swift */; }; 7A971F2426ADF74B007E527B /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A971F2226ADF74B007E527B /* MainView.swift */; }; + 7AAEBD35274A47C500230C28 /* PlateNumberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAEBD34274A47C500230C28 /* PlateNumberView.swift */; }; + 7AAEBD36274A47C600230C28 /* PlateNumberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAEBD34274A47C500230C28 /* PlateNumberView.swift */; }; 7ABC05A826B05AF9004BB5A7 /* Vehicle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABC05A726B05AF9004BB5A7 /* Vehicle.swift */; }; 7ABC05A926B05AF9004BB5A7 /* Vehicle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABC05A726B05AF9004BB5A7 /* Vehicle.swift */; }; 7ACD05D72695C08A00557667 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD05D62695C08A00557667 /* Constants.swift */; }; 7ACD05D82695C08A00557667 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD05D62695C08A00557667 /* Constants.swift */; }; + 7ADE63712735E1FF00A152FE /* VName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE63702735E1FF00A152FE /* VName.swift */; }; + 7ADE63732735E27200A152FE /* VBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE63722735E27200A152FE /* VBrand.swift */; }; + 7ADE63742735E27200A152FE /* VBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE63722735E27200A152FE /* VBrand.swift */; }; + 7ADE63752735E27D00A152FE /* VName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE63702735E1FF00A152FE /* VName.swift */; }; + 7ADE637827368C4600A152FE /* VehicleService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE637727368C4600A152FE /* VehicleService.swift */; }; + 7ADE637927368C4600A152FE /* VehicleService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE637727368C4600A152FE /* VehicleService.swift */; }; + 7ADE637B273692F300A152FE /* StorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE637A273692F300A152FE /* StorageService.swift */; }; + 7ADE637C273692F300A152FE /* StorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE637A273692F300A152FE /* StorageService.swift */; }; + 7ADE637F2738143000A152FE /* CheckNumberPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADE637E2738143000A152FE /* CheckNumberPopup.swift */; }; 7AEFAEED26985A3400ED2C85 /* ACProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFAEEC26985A3400ED2C85 /* ACProgressView.swift */; }; 7AEFAEEE26985A3400ED2C85 /* ACProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFAEEC26985A3400ED2C85 /* ACProgressView.swift */; }; 7AF552D92696E5C100578083 /* ApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF552D82696E5C100578083 /* ApiTests.swift */; }; + 7AFADDA7274A49DB00D53EFC /* PlateNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFADDA6274A49DB00D53EFC /* PlateNumber.swift */; }; + 7AFADDA8274A49DB00D53EFC /* PlateNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFADDA6274A49DB00D53EFC /* PlateNumber.swift */; }; + 7AFADDAF274A64DD00D53EFC /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7AFADDAD274A64DD00D53EFC /* RoadNumbers.otf */; }; + 7AFADDB0274A64DD00D53EFC /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7AFADDAD274A64DD00D53EFC /* RoadNumbers.otf */; }; + 7AFADDB1274A64DD00D53EFC /* RoadNumbers2.0.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7AFADDAE274A64DD00D53EFC /* RoadNumbers2.0.otf */; }; + 7AFADDB2274A64DD00D53EFC /* RoadNumbers2.0.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7AFADDAE274A64DD00D53EFC /* RoadNumbers2.0.otf */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -160,17 +168,24 @@ 7A971F0726AD7084007E527B /* LoginMethodMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMethodMock.swift; sourceTree = ""; }; 7A971F0926AD74FD007E527B /* ApiMethodMockProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiMethodMockProtocol.swift; sourceTree = ""; }; 7A971F0C26AD7D4C007E527B /* AnyEncodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = ""; }; - 7A971F1026AD8AEB007E527B /* Initialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Initialization.swift; sourceTree = ""; }; - 7A971F1126AD8AEB007E527B /* Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Querying.swift; sourceTree = ""; }; - 7A971F1226AD8AEB007E527B /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; - 7A971F1426AD8AEB007E527B /* Merging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Merging.swift; sourceTree = ""; }; 7A971F1F26ADC351007E527B /* ApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiError.swift; sourceTree = ""; }; 7A971F2226ADF74B007E527B /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 7A971F2526ADFD35007E527B /* AutoCat2 (macOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "AutoCat2 (macOS).entitlements"; sourceTree = ""; }; + 7AAEBD34274A47C500230C28 /* PlateNumberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlateNumberView.swift; sourceTree = ""; }; 7ABC05A726B05AF9004BB5A7 /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = ""; }; 7ACD05D62695C08A00557667 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 7ADE63702735E1FF00A152FE /* VName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VName.swift; sourceTree = ""; }; + 7ADE63722735E27200A152FE /* VBrand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VBrand.swift; sourceTree = ""; }; + 7ADE637727368C4600A152FE /* VehicleService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleService.swift; sourceTree = ""; }; + 7ADE637A273692F300A152FE /* StorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageService.swift; sourceTree = ""; }; + 7ADE637E2738143000A152FE /* CheckNumberPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckNumberPopup.swift; sourceTree = ""; }; 7AEFAEEC26985A3400ED2C85 /* ACProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACProgressView.swift; sourceTree = ""; }; 7AF552D82696E5C100578083 /* ApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiTests.swift; sourceTree = ""; }; + 7AFADDA6274A49DB00D53EFC /* PlateNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlateNumber.swift; sourceTree = ""; }; + 7AFADDAD274A64DD00D53EFC /* RoadNumbers.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = RoadNumbers.otf; sourceTree = ""; }; + 7AFADDAE274A64DD00D53EFC /* RoadNumbers2.0.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = RoadNumbers2.0.otf; sourceTree = ""; }; + 7AFADDB3274A65BB00D53EFC /* AutoCat2--macOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "AutoCat2--macOS--Info.plist"; sourceTree = ""; }; + 7AFADDB4274A678D00D53EFC /* AutoCat2--iOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "AutoCat2--iOS--Info.plist"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -215,19 +230,22 @@ 7A40D5732691C6D6009B0BC4 = { isa = PBXGroup; children = ( + 7AFADDB4274A678D00D53EFC /* AutoCat2--iOS--Info.plist */, + 7AFADDB3274A65BB00D53EFC /* AutoCat2--macOS--Info.plist */, 7A971F2526ADFD35007E527B /* AutoCat2 (macOS).entitlements */, 7A40D5782691C6D7009B0BC4 /* Shared */, 7A40D5912691C6D8009B0BC4 /* Tests iOS */, 7A40D59B2691C6D8009B0BC4 /* Tests macOS */, 7A40D5F42693A63A009B0BC4 /* AutoCat2Tests */, 7A40D5842691C6D8009B0BC4 /* Products */, - 7A40D5D92691C7EB009B0BC4 /* Frameworks */, ); sourceTree = ""; }; 7A40D5782691C6D7009B0BC4 /* Shared */ = { isa = PBXGroup; children = ( + 7AFADDAC274A64DD00D53EFC /* Fonts */, + 7ADE637627368C2A00A152FE /* Services */, 7A971F0B26AD7D27007E527B /* ThirdParty */, 7A40D6002694FF4C009B0BC4 /* Utils */, 7A40D5FC2693A90F009B0BC4 /* Extensions */, @@ -271,13 +289,6 @@ path = "Tests macOS"; sourceTree = ""; }; - 7A40D5D92691C7EB009B0BC4 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; 7A40D5DF26924ADE009B0BC4 /* Models */ = { isa = PBXGroup; children = ( @@ -285,6 +296,9 @@ 7A40D5E226924B09009B0BC4 /* Settings.swift */, 7A683998269612EA00B2188A /* Response.swift */, 7ABC05A726B05AF9004BB5A7 /* Vehicle.swift */, + 7ADE63702735E1FF00A152FE /* VName.swift */, + 7ADE63722735E27200A152FE /* VBrand.swift */, + 7AFADDA6274A49DB00D53EFC /* PlateNumber.swift */, ); path = Models; sourceTree = ""; @@ -299,17 +313,18 @@ 7A40D5E726938BC8009B0BC4 /* Views */ = { isa = PBXGroup; children = ( + 7ADE637D2738139D00A152FE /* macOS */, 7AEFAEEC26985A3400ED2C85 /* ACProgressView.swift */, + 7A2329BE26AF140F00264CFA /* CheckView.swift */, 7A40D57C2691C6D7009B0BC4 /* ContentView.swift */, 7A40D5E826938BEC009B0BC4 /* AuthView.swift */, 7A971F2226ADF74B007E527B /* MainView.swift */, 7A2329B826AF122400264CFA /* MainViewSmall.swift */, - 7A2329BB26AF123200264CFA /* MainViewBig.swift */, - 7A2329BE26AF140F00264CFA /* CheckView.swift */, 7A2329C126AF141E00264CFA /* SearchView.swift */, 7A2329C426AF142900264CFA /* SettingsView.swift */, 7A2329C726AF143F00264CFA /* RecordsView.swift */, 7A2329CA26AF15AC00264CFA /* ReportView.swift */, + 7AAEBD34274A47C500230C28 /* PlateNumberView.swift */, ); path = Views; sourceTree = ""; @@ -390,21 +405,36 @@ 7A971F0B26AD7D27007E527B /* ThirdParty */ = { isa = PBXGroup; children = ( - 7A971F0F26AD8AEB007E527B /* GenericJSON */, 7A971F0C26AD7D4C007E527B /* AnyEncodable.swift */, ); path = ThirdParty; sourceTree = ""; }; - 7A971F0F26AD8AEB007E527B /* GenericJSON */ = { + 7ADE637627368C2A00A152FE /* Services */ = { isa = PBXGroup; children = ( - 7A971F1026AD8AEB007E527B /* Initialization.swift */, - 7A971F1126AD8AEB007E527B /* Querying.swift */, - 7A971F1226AD8AEB007E527B /* JSON.swift */, - 7A971F1426AD8AEB007E527B /* Merging.swift */, + 7ADE637727368C4600A152FE /* VehicleService.swift */, + 7ADE637A273692F300A152FE /* StorageService.swift */, ); - path = GenericJSON; + path = Services; + sourceTree = ""; + }; + 7ADE637D2738139D00A152FE /* macOS */ = { + isa = PBXGroup; + children = ( + 7A2329BB26AF123200264CFA /* MainViewBig.swift */, + 7ADE637E2738143000A152FE /* CheckNumberPopup.swift */, + ); + path = macOS; + sourceTree = ""; + }; + 7AFADDAC274A64DD00D53EFC /* Fonts */ = { + isa = PBXGroup; + children = ( + 7AFADDAD274A64DD00D53EFC /* RoadNumbers.otf */, + 7AFADDAE274A64DD00D53EFC /* RoadNumbers2.0.otf */, + ); + path = Fonts; sourceTree = ""; }; /* End PBXGroup section */ @@ -424,6 +454,8 @@ dependencies = ( ); name = "AutoCat2 (iOS)"; + packageProductDependencies = ( + ); productName = "AutoCat2 (iOS)"; productReference = 7A40D5832691C6D8009B0BC4 /* AutoCat2.app */; productType = "com.apple.product-type.application"; @@ -442,6 +474,8 @@ dependencies = ( ); name = "AutoCat2 (macOS)"; + packageProductDependencies = ( + ); productName = "AutoCat2 (macOS)"; productReference = 7A40D5892691C6D8009B0BC4 /* AutoCat2.app */; productType = "com.apple.product-type.application"; @@ -508,7 +542,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1300; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1310; TargetAttributes = { 7A40D5822691C6D8009B0BC4 = { CreatedOnToolsVersion = 13.0; @@ -539,6 +573,8 @@ Base, ); mainGroup = 7A40D5732691C6D6009B0BC4; + packageReferences = ( + ); productRefGroup = 7A40D5842691C6D8009B0BC4 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -557,7 +593,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7AFADDAF274A64DD00D53EFC /* RoadNumbers.otf in Resources */, 7A40D5A62691C6D8009B0BC4 /* Assets.xcassets in Resources */, + 7AFADDB1274A64DD00D53EFC /* RoadNumbers2.0.otf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -565,7 +603,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7AFADDB0274A64DD00D53EFC /* RoadNumbers.otf in Resources */, 7A40D5A72691C6D8009B0BC4 /* Assets.xcassets in Resources */, + 7AFADDB2274A64DD00D53EFC /* RoadNumbers2.0.otf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -600,25 +640,26 @@ files = ( 7A40D5E926938BEC009B0BC4 /* AuthView.swift in Sources */, 7A971F2326ADF74B007E527B /* MainView.swift in Sources */, + 7ADE63732735E27200A152FE /* VBrand.swift in Sources */, 7A971F0D26AD7D4C007E527B /* AnyEncodable.swift in Sources */, 7ACD05D72695C08A00557667 /* Constants.swift in Sources */, - 7A971F1926AD8AEB007E527B /* JSON.swift in Sources */, - 7A2329BC26AF123300264CFA /* MainViewBig.swift in Sources */, - 7A971F1D26AD8AEB007E527B /* Merging.swift in Sources */, 7A40D5E326924B09009B0BC4 /* Settings.swift in Sources */, + 7ADE637827368C4600A152FE /* VehicleService.swift in Sources */, 7AEFAEED26985A3400ED2C85 /* ACProgressView.swift in Sources */, 7A40D5A02691C6D8009B0BC4 /* AutoCat2App.swift in Sources */, 7A683999269612EA00B2188A /* Response.swift in Sources */, 7A2329BF26AF140F00264CFA /* CheckView.swift in Sources */, - 7A971F1526AD8AEB007E527B /* Initialization.swift in Sources */, - 7A971F1726AD8AEB007E527B /* Querying.swift in Sources */, 7A40D60826998DCF009B0BC4 /* Alert.swift in Sources */, 7A2329C226AF141E00264CFA /* SearchView.swift in Sources */, + 7ADE637B273692F300A152FE /* StorageService.swift in Sources */, + 7ADE63712735E1FF00A152FE /* VName.swift in Sources */, 7A40D5A42691C6D8009B0BC4 /* Persistence.swift in Sources */, 7A971F2026ADC351007E527B /* ApiError.swift in Sources */, 7A2329CB26AF15AC00264CFA /* ReportView.swift in Sources */, + 7AAEBD35274A47C500230C28 /* PlateNumberView.swift in Sources */, 7A40D5ED2693A1EA009B0BC4 /* AuthVM.swift in Sources */, 7A40D59E2691C6D8009B0BC4 /* AutoCat2.xcdatamodeld in Sources */, + 7AFADDA7274A49DB00D53EFC /* PlateNumber.swift in Sources */, 7ABC05A826B05AF9004BB5A7 /* Vehicle.swift in Sources */, 7A2329B926AF122400264CFA /* MainViewSmall.swift in Sources */, 7A2329C826AF143F00264CFA /* RecordsView.swift in Sources */, @@ -634,31 +675,34 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7AAEBD36274A47C600230C28 /* PlateNumberView.swift in Sources */, + 7ADE637927368C4600A152FE /* VehicleService.swift in Sources */, 7A40D5EA26938BEC009B0BC4 /* AuthView.swift in Sources */, 7A971F2426ADF74B007E527B /* MainView.swift in Sources */, + 7ADE63742735E27200A152FE /* VBrand.swift in Sources */, 7A971F0E26AD7D4C007E527B /* AnyEncodable.swift in Sources */, 7ACD05D82695C08A00557667 /* Constants.swift in Sources */, - 7A971F1A26AD8AEB007E527B /* JSON.swift in Sources */, 7A2329BD26AF123300264CFA /* MainViewBig.swift in Sources */, - 7A971F1E26AD8AEB007E527B /* Merging.swift in Sources */, 7A40D5A12691C6D8009B0BC4 /* AutoCat2App.swift in Sources */, 7AEFAEEE26985A3400ED2C85 /* ACProgressView.swift in Sources */, 7A40D5A52691C6D8009B0BC4 /* Persistence.swift in Sources */, + 7AFADDA8274A49DB00D53EFC /* PlateNumber.swift in Sources */, 7A68399A269612EA00B2188A /* Response.swift in Sources */, 7A2329C026AF140F00264CFA /* CheckView.swift in Sources */, - 7A971F1626AD8AEB007E527B /* Initialization.swift in Sources */, - 7A971F1826AD8AEB007E527B /* Querying.swift in Sources */, 7A40D60926998DCF009B0BC4 /* Alert.swift in Sources */, 7A2329C326AF141E00264CFA /* SearchView.swift in Sources */, 7A40D5E526924B0C009B0BC4 /* User.swift in Sources */, 7A971F2126ADC351007E527B /* ApiError.swift in Sources */, 7A2329CC26AF15AC00264CFA /* ReportView.swift in Sources */, + 7ADE637C273692F300A152FE /* StorageService.swift in Sources */, + 7ADE637F2738143000A152FE /* CheckNumberPopup.swift in Sources */, 7A40D5EE2693A1EA009B0BC4 /* AuthVM.swift in Sources */, 7A40D59F2691C6D8009B0BC4 /* AutoCat2.xcdatamodeld in Sources */, 7ABC05A926B05AF9004BB5A7 /* Vehicle.swift in Sources */, 7A2329BA26AF122400264CFA /* MainViewSmall.swift in Sources */, 7A2329C926AF143F00264CFA /* RecordsView.swift in Sources */, 7A40D5A32691C6D8009B0BC4 /* ContentView.swift in Sources */, + 7ADE63752735E27D00A152FE /* VName.swift in Sources */, 7A40D5FF2693A91F009B0BC4 /* CocoaError.swift in Sources */, 7A40D6032694FF5D009B0BC4 /* Api.swift in Sources */, 7A40D5E426924B09009B0BC4 /* Settings.swift in Sources */, @@ -841,6 +885,7 @@ DEVELOPMENT_TEAM = 46DTTB8X4S; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "AutoCat2--iOS--Info.plist"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -871,6 +916,7 @@ DEVELOPMENT_TEAM = 46DTTB8X4S; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "AutoCat2--iOS--Info.plist"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -898,6 +944,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "AutoCat2 (macOS).entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; @@ -907,6 +954,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "AutoCat2--macOS--Info.plist"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -928,6 +976,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "AutoCat2 (macOS).entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; @@ -937,6 +986,7 @@ ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "AutoCat2--macOS--Info.plist"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist b/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist index 6d7bf61..a2751d7 100644 --- a/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist @@ -20,5 +20,23 @@ 2 + SuppressBuildableAutocreation + + 7A40D5822691C6D8009B0BC4 + + primary + + + 7A40D58D2691C6D8009B0BC4 + + primary + + + 7A40D5F22693A63A009B0BC4 + + primary + + + diff --git a/AutoCat2Tests/ApiTests.swift b/AutoCat2Tests/ApiTests.swift index 3192706..df060d4 100644 --- a/AutoCat2Tests/ApiTests.swift +++ b/AutoCat2Tests/ApiTests.swift @@ -3,7 +3,7 @@ import AutoCat2 class ApiTests: XCTestCase { - private var api: Api! + private var api: ApiProtocol! private let testLogin = "test@gmail.com" private let testPassword = "12345" diff --git a/AutoCat2Tests/SettingsTests.swift b/AutoCat2Tests/SettingsTests.swift index 0d0fa0e..b290da7 100644 --- a/AutoCat2Tests/SettingsTests.swift +++ b/AutoCat2Tests/SettingsTests.swift @@ -3,7 +3,7 @@ import AutoCat2 class SettingsTests: XCTestCase { - private var settings: Settings! + private var settings: SettingsProtocol! override func setUpWithError() throws { guard let userDefaults = UserDefaults(suiteName: #file) else { diff --git a/Shared/Assets.xcassets/Colors/Contents.json b/Shared/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Shared/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Colors/PlateBackground.colorset/Contents.json b/Shared/Assets.xcassets/Colors/PlateBackground.colorset/Contents.json new file mode 100644 index 0000000..2257e90 --- /dev/null +++ b/Shared/Assets.xcassets/Colors/PlateBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.152", + "green" : "0.152", + "red" : "0.152" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Colors/PlateBackgroundError.colorset/Contents.json b/Shared/Assets.xcassets/Colors/PlateBackgroundError.colorset/Contents.json new file mode 100644 index 0000000..dcf9455 --- /dev/null +++ b/Shared/Assets.xcassets/Colors/PlateBackgroundError.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.182", + "green" : "0.225", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/Assets.xcassets/Colors/PlateForeground.colorset/Contents.json b/Shared/Assets.xcassets/Colors/PlateForeground.colorset/Contents.json new file mode 100644 index 0000000..945ab6a --- /dev/null +++ b/Shared/Assets.xcassets/Colors/PlateForeground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.866", + "green" : "0.866", + "red" : "0.866" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Shared/AutoCat2.xcdatamodeld/Shared.xcdatamodel/contents b/Shared/AutoCat2.xcdatamodeld/Shared.xcdatamodel/contents index 5a90e77..ef04a39 100644 --- a/Shared/AutoCat2.xcdatamodeld/Shared.xcdatamodel/contents +++ b/Shared/AutoCat2.xcdatamodeld/Shared.xcdatamodel/contents @@ -1,23 +1,23 @@ - - + + - + - + - - - + + + \ No newline at end of file diff --git a/Shared/AutoCat2App.swift b/Shared/AutoCat2App.swift index e02f4f2..af0a06e 100644 --- a/Shared/AutoCat2App.swift +++ b/Shared/AutoCat2App.swift @@ -9,12 +9,12 @@ import SwiftUI @main struct AutoCat2App: App { - let persistenceController = PersistenceController.shared + let storageService = StorageService.shared var body: some Scene { WindowGroup { ContentView() - .environment(\.managedObjectContext, persistenceController.container.viewContext) + .environment(\.managedObjectContext, storageService.context) } } } diff --git a/Shared/Fonts/RoadNumbers.otf b/Shared/Fonts/RoadNumbers.otf new file mode 100644 index 0000000000000000000000000000000000000000..7f40a38651f2cd257411960af245d38c7696d130 GIT binary patch literal 9520 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 W@vrp#`9e%p|B8JQR$$LS!+!!$iEZ`( literal 0 HcmV?d00001 diff --git a/Shared/Models/PlateNumber.swift b/Shared/Models/PlateNumber.swift new file mode 100644 index 0000000..1eb436d --- /dev/null +++ b/Shared/Models/PlateNumber.swift @@ -0,0 +1,25 @@ +import Foundation + +public class PlateNumber { + private var number: String + private var numberEnglish: String + + public init(_ string: String) { + self.number = string + self.numberEnglish = String(self.number.map { Constants.pnLettersMap[$0] ?? $0 }) + } + + public func asString() -> String { + return self.number + } + + public func mainPart() -> String { + let index = self.numberEnglish.index(self.numberEnglish.startIndex, offsetBy: 6) + return String(self.numberEnglish[.. String { + let index = self.numberEnglish.index(self.numberEnglish.startIndex, offsetBy: 6) + return String(self.numberEnglish[index...]) + } +} diff --git a/Shared/Models/Settings.swift b/Shared/Models/Settings.swift index e52beca..29bcbd6 100644 --- a/Shared/Models/Settings.swift +++ b/Shared/Models/Settings.swift @@ -1,7 +1,18 @@ import Foundation -public class Settings: ObservableObject { - private var defaults: UserDefaults +public protocol SettingsProtocol { + + var user: User { get set } + var recognizeAlternativeOrder: Bool { get set } + var recognizeShortenedNumbers: Bool { get set } + var defaultRegion: String { get set } + var recordBeep: Bool { get set } + var showDebugInfo: Bool { get set } + +} + +public class Settings: ObservableObject, SettingsProtocol { + private let defaults: UserDefaults public static let shared = Settings() @Published diff --git a/Shared/Models/VBrand.swift b/Shared/Models/VBrand.swift new file mode 100644 index 0000000..9431752 --- /dev/null +++ b/Shared/Models/VBrand.swift @@ -0,0 +1,22 @@ +import Foundation +import CoreData + +public struct VBrand: Decodable { + + let logo: String? + let name: VName? +} + +extension CDVBrand { + + convenience init(vbrand: VBrand, context: NSManagedObjectContext) { + + self.init(context: context) + self.logo = vbrand.logo + + if let vname = vbrand.name { + self.name = CDVName(vname: vname, context: context) + } + } + +} diff --git a/Shared/Models/VName.swift b/Shared/Models/VName.swift new file mode 100644 index 0000000..7236600 --- /dev/null +++ b/Shared/Models/VName.swift @@ -0,0 +1,19 @@ +import Foundation +import CoreData + +public struct VName: Decodable { + + let normalized: String? + let original: String? +} + +extension CDVName { + + convenience init(vname: VName, context: NSManagedObjectContext) { + + self.init(context: context) + self.normalized = vname.normalized + self.original = vname.original + } + +} diff --git a/Shared/Models/Vehicle.swift b/Shared/Models/Vehicle.swift index b6b9f89..0b9af43 100644 --- a/Shared/Models/Vehicle.swift +++ b/Shared/Models/Vehicle.swift @@ -1,7 +1,13 @@ import Foundation import CoreData -extension Vehicle { +public struct Vehicle: Decodable { + + let number: String + let currentNumber: String? + let brand: VBrand? + + // TODO: Remove code duplication public var unrecognized: Bool { return self.brand == nil } @@ -14,3 +20,30 @@ extension Vehicle { } } } + +extension CDVehicle { + + convenience init(vehicle: Vehicle, context: NSManagedObjectContext) { + + self.init(context: context) + self.number = vehicle.number + self.currentNumber = vehicle.currentNumber + + if let vbrand = vehicle.brand { + self.brand = CDVBrand(vbrand: vbrand, context: context) + } + } + + public var unrecognized: Bool { + return self.brand == nil + } + + public var outdated: Bool { + if let current = self.currentNumber { + return current != self.number + } else { + return false + } + } + +} diff --git a/Shared/Services/StorageService.swift b/Shared/Services/StorageService.swift new file mode 100644 index 0000000..884e213 --- /dev/null +++ b/Shared/Services/StorageService.swift @@ -0,0 +1,56 @@ +import Foundation +import CoreData + +protocol StorageServiceProtocol { + + var context: NSManagedObjectContext { get } + func store(vehicle: Vehicle) throws -> CDVehicle +} + +class StorageService: StorageServiceProtocol { + + private let container: NSPersistentCloudKitContainer + + static let shared = StorageService() + + init(inMemory: Bool = false) { + + container = NSPersistentCloudKitContainer(name: "AutoCat2") + if inMemory { + container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") + } + + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + // TODO: Handle error properly + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + } + + private func save() throws { + guard context.hasChanges else { + return + } + + do { + try context.save() + } catch { + context.rollback() + throw error + } + } + + // MARK: - StorageServiceProtocol + + var context: NSManagedObjectContext { + container.viewContext + } + + func store(vehicle: Vehicle) throws -> CDVehicle { + + let cdVehicle = CDVehicle(vehicle: vehicle, context: context) + try save() + return cdVehicle + } +} diff --git a/Shared/Services/VehicleService.swift b/Shared/Services/VehicleService.swift new file mode 100644 index 0000000..502096a --- /dev/null +++ b/Shared/Services/VehicleService.swift @@ -0,0 +1,29 @@ +import Foundation + +protocol VehicleServiceProtocol { + + func check(plateNumber: String, force: Bool) async throws -> CDVehicle +} + +class VehicleService: VehicleServiceProtocol { + + private let api: ApiProtocol + private let storage: StorageServiceProtocol + + static let shared = VehicleService() + + init(api: ApiProtocol = Api.shared, storage: StorageServiceProtocol = StorageService.shared) { + + self.api = api + self.storage = storage + } + + // MARK: - VehicleServiceProtocol + + func check(plateNumber: String, force: Bool) async throws -> CDVehicle { + + let vehicle = try await api.check(plateNumber: plateNumber, force: force) + return try storage.store(vehicle: vehicle) + } + +} diff --git a/Shared/ThirdParty/GenericJSON/Initialization.swift b/Shared/ThirdParty/GenericJSON/Initialization.swift deleted file mode 100644 index 032b0c5..0000000 --- a/Shared/ThirdParty/GenericJSON/Initialization.swift +++ /dev/null @@ -1,124 +0,0 @@ -import Foundation - -private struct InitializationError: Error {} - -extension JSON { - - /// Create a JSON value from anything. - /// - /// Argument has to be a valid JSON structure: A `Double`, `Int`, `String`, - /// `Bool`, an `Array` of those types or a `Dictionary` of those types. - /// - /// You can also pass `nil` or `NSNull`, both will be treated as `.null`. - public init(_ value: Any) throws { - switch value { - case _ as NSNull: - self = .null - case let opt as Optional where opt == nil: - self = .null - case let num as NSNumber: - if num.isBool { - self = .bool(num.boolValue) - } else { - self = .number(num.doubleValue) - } - case let str as String: - self = .string(str) - case let bool as Bool: - self = .bool(bool) - case let array as [Any]: - self = .array(try array.map(JSON.init)) - case let dict as [String:Any]: - self = .object(try dict.mapValues(JSON.init)) - default: - throw InitializationError() - } - } -} - -extension JSON { - - /// Create a JSON value from an `Encodable`. This will give you access to the “raw” - /// encoded JSON value the `Encodable` is serialized into. - public init(encodable: T) throws { - let encoded = try JSONEncoder().encode(encodable) - self = try JSONDecoder().decode(JSON.self, from: encoded) - } -} - -extension JSON: ExpressibleByBooleanLiteral { - - public init(booleanLiteral value: Bool) { - self = .bool(value) - } -} - -extension JSON: ExpressibleByNilLiteral { - - public init(nilLiteral: ()) { - self = .null - } -} - -extension JSON: ExpressibleByArrayLiteral { - - public init(arrayLiteral elements: JSON...) { - self = .array(elements) - } -} - -extension JSON: ExpressibleByDictionaryLiteral { - - public init(dictionaryLiteral elements: (String, JSON)...) { - var object: [String:JSON] = [:] - for (k, v) in elements { - object[k] = v - } - self = .object(object) - } -} - -extension JSON: ExpressibleByFloatLiteral { - - public init(floatLiteral value: Double) { - self = .number(value) - } -} - -extension JSON: ExpressibleByIntegerLiteral { - - public init(integerLiteral value: Int) { - self = .number(Double(value)) - } -} - -extension JSON: ExpressibleByStringLiteral { - - public init(stringLiteral value: String) { - self = .string(value) - } -} - -// MARK: - NSNumber - -extension NSNumber { - - /// Boolean value indicating whether this `NSNumber` wraps a boolean. - /// - /// For example, when using `NSJSONSerialization` Bool values are converted into `NSNumber` instances. - /// - /// - seealso: https://stackoverflow.com/a/49641315/3589408 - fileprivate var isBool: Bool { - let objCType = String(cString: self.objCType) - if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType) { - return true - } else { - return false - } - } -} - -private let trueNumber = NSNumber(value: true) -private let falseNumber = NSNumber(value: false) -private let trueObjCType = String(cString: trueNumber.objCType) -private let falseObjCType = String(cString: falseNumber.objCType) diff --git a/Shared/ThirdParty/GenericJSON/JSON.swift b/Shared/ThirdParty/GenericJSON/JSON.swift deleted file mode 100644 index 34a05b3..0000000 --- a/Shared/ThirdParty/GenericJSON/JSON.swift +++ /dev/null @@ -1,82 +0,0 @@ -import Foundation - -/// A JSON value representation. This is a bit more useful than the naïve `[String:Any]` type -/// for JSON values, since it makes sure only valid JSON values are present & supports `Equatable` -/// and `Codable`, so that you can compare values for equality and code and decode them into data -/// or strings. -@dynamicMemberLookup public enum JSON: Equatable { - case string(String) - case number(Double) - case object([String:JSON]) - case array([JSON]) - case bool(Bool) - case null -} - -extension JSON: Codable { - - public func encode(to encoder: Encoder) throws { - - var container = encoder.singleValueContainer() - - switch self { - case let .array(array): - try container.encode(array) - case let .object(object): - try container.encode(object) - case let .string(string): - try container.encode(string) - case let .number(number): - try container.encode(number) - case let .bool(bool): - try container.encode(bool) - case .null: - try container.encodeNil() - } - } - - public init(from decoder: Decoder) throws { - - let container = try decoder.singleValueContainer() - - if let object = try? container.decode([String: JSON].self) { - self = .object(object) - } else if let array = try? container.decode([JSON].self) { - self = .array(array) - } else if let string = try? container.decode(String.self) { - self = .string(string) - } else if let bool = try? container.decode(Bool.self) { - self = .bool(bool) - } else if let number = try? container.decode(Double.self) { - self = .number(number) - } else if container.decodeNil() { - self = .null - } else { - throw DecodingError.dataCorrupted( - .init(codingPath: decoder.codingPath, debugDescription: "Invalid JSON value.") - ) - } - } -} - -extension JSON: CustomDebugStringConvertible { - - public var debugDescription: String { - switch self { - case .string(let str): - return str.debugDescription - case .number(let num): - return num.debugDescription - case .bool(let bool): - return bool.description - case .null: - return "null" - default: - let encoder = JSONEncoder() - encoder.outputFormatting = [.prettyPrinted] - return try! String(data: encoder.encode(self), encoding: .utf8)! - } - } -} - -extension JSON: Hashable {} diff --git a/Shared/ThirdParty/GenericJSON/Merging.swift b/Shared/ThirdParty/GenericJSON/Merging.swift deleted file mode 100644 index b16e1d2..0000000 --- a/Shared/ThirdParty/GenericJSON/Merging.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation - -extension JSON { - - /// Return a new JSON value by merging two other ones - /// - /// If we call the current JSON value `old` and the incoming JSON value - /// `new`, the precise merging rules are: - /// - /// 1. If `old` or `new` are anything but an object, return `new`. - /// 2. If both `old` and `new` are objects, create a merged object like this: - /// 1. Add keys from `old` not present in `new` (“no change” case). - /// 2. Add keys from `new` not present in `old` (“create” case). - /// 3. For keys present in both `old` and `new`, apply merge recursively to their values (“update” case). - public func merging(with new: JSON) -> JSON { - - // If old or new are anything but an object, return new. - guard case .object(let lhs) = self, case .object(let rhs) = new else { - return new - } - - var merged: [String: JSON] = [:] - - // Add keys from old not present in new (“no change” case). - for (key, val) in lhs where rhs[key] == nil { - merged[key] = val - } - - // Add keys from new not present in old (“create” case). - for (key, val) in rhs where lhs[key] == nil { - merged[key] = val - } - - // For keys present in both old and new, apply merge recursively to their values. - for key in lhs.keys where rhs[key] != nil { - merged[key] = lhs[key]?.merging(with: rhs[key]!) - } - - return JSON.object(merged) - } -} diff --git a/Shared/ThirdParty/GenericJSON/Querying.swift b/Shared/ThirdParty/GenericJSON/Querying.swift deleted file mode 100644 index 7566b1f..0000000 --- a/Shared/ThirdParty/GenericJSON/Querying.swift +++ /dev/null @@ -1,107 +0,0 @@ -import Foundation - -public extension JSON { - - /// Return the string value if this is a `.string`, otherwise `nil` - var stringValue: String? { - if case .string(let value) = self { - return value - } - return nil - } - - /// Return the double value if this is a `.number`, otherwise `nil` - var doubleValue: Double? { - if case .number(let value) = self { - return value - } - return nil - } - - /// Return the bool value if this is a `.bool`, otherwise `nil` - var boolValue: Bool? { - if case .bool(let value) = self { - return value - } - return nil - } - - /// Return the object value if this is an `.object`, otherwise `nil` - var objectValue: [String: JSON]? { - if case .object(let value) = self { - return value - } - return nil - } - - /// Return the array value if this is an `.array`, otherwise `nil` - var arrayValue: [JSON]? { - if case .array(let value) = self { - return value - } - return nil - } - - /// Return `true` iff this is `.null` - var isNull: Bool { - if case .null = self { - return true - } - return false - } - - /// If this is an `.array`, return item at index - /// - /// If this is not an `.array` or the index is out of bounds, returns `nil`. - subscript(index: Int) -> JSON? { - if case .array(let arr) = self, arr.indices.contains(index) { - return arr[index] - } - return nil - } - - /// If this is an `.object`, return item at key - subscript(key: String) -> JSON? { - if case .object(let dict) = self { - return dict[key] - } - return nil - } - - /// Dynamic member lookup sugar for string subscripts - /// - /// This lets you write `json.foo` instead of `json["foo"]`. - subscript(dynamicMember member: String) -> JSON? { - return self[member] - } - - /// Return the JSON type at the keypath if this is an `.object`, otherwise `nil` - /// - /// This lets you write `json[keyPath: "foo.bar.jar"]`. - subscript(keyPath keyPath: String) -> JSON? { - return queryKeyPath(keyPath.components(separatedBy: ".")) - } - - func queryKeyPath(_ path: T) -> JSON? where T: Collection, T.Element == String { - - // Only object values may be subscripted - guard case .object(let object) = self else { - return nil - } - - // Is the path non-empty? - guard let head = path.first else { - return nil - } - - // Do we have a value at the required key? - guard let value = object[head] else { - return nil - } - - let tail = path.dropFirst() - - return tail.isEmpty ? value : value.queryKeyPath(tail) - } - -} diff --git a/Shared/Utils/Api.swift b/Shared/Utils/Api.swift index 3b45843..bd62446 100644 --- a/Shared/Utils/Api.swift +++ b/Shared/Utils/Api.swift @@ -1,12 +1,23 @@ import Foundation -public class Api { +public protocol ApiProtocol { + + func login(email: String, password: String) async throws -> User + func check(plateNumber: String, force: Bool) async throws -> Vehicle + +} - private var session: URLSession +public class Api: ApiProtocol { + + private let session: URLSession + private let settings: SettingsProtocol public static let shared = Api() - public init(session: URLSession? = nil) { + public init(session: URLSession? = nil, settings: SettingsProtocol = Settings.shared) { + + self.settings = settings + if let session = session { self.session = session } else { @@ -34,7 +45,7 @@ public class Api { request.httpMethod = method request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Accept") - request.addValue("Bearer " + Settings.shared.user.token, forHTTPHeaderField: "Authorization") + request.addValue("Bearer " + settings.user.token, forHTTPHeaderField: "Authorization") if let body = body, method.uppercased() != "GET" { let encoder = JSONEncoder() @@ -103,10 +114,26 @@ public class Api { // MARK: - AutoCat public API public func login(email: String, password: String) async throws -> User { + let body = [ "email": email, "password": password ] + return try await self.makeBodyRequest(api: "user/login", body: body) } + + public func check(plateNumber: String, force: Bool = false) async throws -> Vehicle { + + var body = [ + "number": AnyEncodable(plateNumber), + "forceUpdate": AnyEncodable(force) + ] + + if let token = settings.user.firebaseIdToken { + body["googleIdToken"] = AnyEncodable(token) + } + + return try await self.makeBodyRequest(api: "vehicles/check", body: body) + } } diff --git a/Shared/Utils/Constants.swift b/Shared/Utils/Constants.swift index 5588fa4..21b54cc 100644 --- a/Shared/Utils/Constants.swift +++ b/Shared/Utils/Constants.swift @@ -3,11 +3,15 @@ import Foundation public struct Constants { public static var baseUrl: String { #if DEBUG - //return "http://127.0.0.1:3000/" + return "http://127.0.0.1:3000/" //return "http://192.168.1.67:3000/" - return "https://vps.aliencat.pro:8443/" + //return "https://vps.aliencat.pro:8443/" #else return "https://vps.aliencat.pro:8443/" #endif } + + public static let pnLettersMap: [Character: Character] = [ + "А": "A", "В": "B", "Е": "E", "К": "K", "М": "M", "Н": "H", "О": "O", "Р": "P", "С": "C", "Т": "T", "У": "Y", "Х": "X" + ] } diff --git a/Shared/ViewModels/AuthVM.swift b/Shared/ViewModels/AuthVM.swift index a1cd530..4a8cdeb 100644 --- a/Shared/ViewModels/AuthVM.swift +++ b/Shared/ViewModels/AuthVM.swift @@ -1,7 +1,17 @@ import Foundation public class AuthVM: ObservableObject { + + private let api: ApiProtocol + private var settings: SettingsProtocol + + init(api: ApiProtocol = Api.shared, settings: SettingsProtocol = Settings.shared) { + + self.api = api + self.settings = settings + } + public func login(user: String, password: String) async throws { - Settings.shared.user = try await Api.shared.login(email: user, password: password) + settings.user = try await api.login(email: user, password: password) } } diff --git a/Shared/Views/AuthView.swift b/Shared/Views/AuthView.swift index 7d5c3ff..ed0b556 100644 --- a/Shared/Views/AuthView.swift +++ b/Shared/Views/AuthView.swift @@ -47,6 +47,7 @@ struct AuthView: View { Spacer() } .buttonStyle(.bordered) + .controlSize(.large) .textFieldStyle(.roundedBorder) .padding(20) diff --git a/Shared/Views/CheckView.swift b/Shared/Views/CheckView.swift index 50db450..8a58a50 100644 --- a/Shared/Views/CheckView.swift +++ b/Shared/Views/CheckView.swift @@ -2,7 +2,7 @@ import SwiftUI struct CheckView: View { @State private var number: String = "" - @FetchRequest(entity: Vehicle.entity(), sortDescriptors: []) var vehicles: FetchedResults + @FetchRequest(entity: CDVehicle.entity(), sortDescriptors: []) var vehicles: FetchedResults var body: some View { NavigationView { diff --git a/Shared/Views/PlateNumberView.swift b/Shared/Views/PlateNumberView.swift new file mode 100644 index 0000000..efacb01 --- /dev/null +++ b/Shared/Views/PlateNumberView.swift @@ -0,0 +1,67 @@ +// +// PlateNumberView.swift +// AutoCat2 +// +// Created by Selim Mustafaev on 21.11.2021. +// + +import SwiftUI + +struct PlateNumberView: View { + + let number: PlateNumber + let unrecognized: Bool + let outdated: Bool + + private var fgColor: Color { + if unrecognized { + return Color("PlateBackgroundError") + } else { + return Color("PlateForeground") + } + } + + var body: some View { + ZStack { + RoundedRectangle(cornerRadius: 6) + .fill(fgColor) + GeometryReader { geometry in + HStack(alignment: .center, spacing: 2) { + ZStack { + RoundedRectangle(cornerRadius: 4) + .fill(Color("PlateBackground")) + Text(number.mainPart()) + .font(Font.custom("RoadNumbers", size: geometry.size.height*0.9)) + } + .frame(width: geometry.size.width*0.73 - 1) + ZStack { + RoundedRectangle(cornerRadius: 4) + .fill(Color("PlateBackground")) + VStack(spacing: 0) { + Text(number.region()) + .frame(height: geometry.size.height*0.65, alignment: .center) + HStack { + + } + .frame(height: geometry.size.height*0.35, alignment: .center) + } + } + .frame(width: geometry.size.width*0.27 - 1) + } + } + .padding(2) + } + .aspectRatio(520.0/112.0, contentMode: .fit) + } +} + +struct PlateNumberView_Previews: PreviewProvider { + static var previews: some View { + Group { + PlateNumberView(number: PlateNumber("Е201АМ761"), unrecognized: false, outdated: false) + PlateNumberView(number: PlateNumber("Е201АМ761"), unrecognized: true, outdated: false) + PlateNumberView(number: PlateNumber("Е201АМ761"), unrecognized: false, outdated: true) + } + .previewLayout(.fixed(width: 200, height: 50)) + } +} diff --git a/Shared/Views/macOS/CheckNumberPopup.swift b/Shared/Views/macOS/CheckNumberPopup.swift new file mode 100644 index 0000000..d40d5fc --- /dev/null +++ b/Shared/Views/macOS/CheckNumberPopup.swift @@ -0,0 +1,56 @@ +// +// CheckNumberPopup.swift +// AutoCat2 (macOS) +// +// Created by Selim Mustafaev on 07.11.2021. +// + +import SwiftUI + +struct CheckNumberPopup: View { + @Environment(\.presentationMode) var presentation + @State var plateNumber: String = "" + @State var progress = false + @State private var alert: AlertMessage? = nil + + var body: some View { + VStack(alignment: .center, spacing: 8) { + Text("Check new plate number") + TextField("qwe", text: $plateNumber, prompt: Text("asdf")) + .disabled(progress) + Spacer() + if progress { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } + Spacer() + HStack { + Button("Cancel") { + self.presentation.wrappedValue.dismiss() + } + .disabled(progress) + Button("Check") { + Task.init { + progress = true + do { + try await VehicleService.shared.check(plateNumber: plateNumber.uppercased(), force: false) + } catch { + alert = .error(error: error) + } + progress = false + } + } + .alert(item: $alert, content: Alert.init) + .disabled(progress) + } + } + .padding() + .frame(minWidth: 300, minHeight: 180) + } +} + +struct CheckNumberPopup_Previews: PreviewProvider { + static var previews: some View { + CheckNumberPopup() + } +} diff --git a/Shared/Views/MainViewBig.swift b/Shared/Views/macOS/MainViewBig.swift similarity index 56% rename from Shared/Views/MainViewBig.swift rename to Shared/Views/macOS/MainViewBig.swift index 1a774f5..0e54a00 100644 --- a/Shared/Views/MainViewBig.swift +++ b/Shared/Views/macOS/MainViewBig.swift @@ -12,21 +12,22 @@ struct MainViewBig: View { struct SidebarView: View { @State var selection: String? + @State private var showAddPlateNumberView = false - @FetchRequest(entity: Vehicle.entity(), sortDescriptors: []) var vehicles: FetchedResults + @FetchRequest(entity: CDVehicle.entity(), sortDescriptors: []) var vehicles: FetchedResults var body: some View { List(selection: $selection) { Section("History") { - NavigationLink(destination: VehiclesListView()) { + NavigationLink(destination: VehiclesListView(vehicles: vehicles)) { Label("All", systemImage: "car.2") .badge(vehicles.count) } - NavigationLink(destination: VehiclesListView()) { + NavigationLink(destination: VehiclesListView(vehicles: vehicles.filter(\.unrecognized))) { Label("Unreconized", systemImage: "eye.slash") .badge(vehicles.filter(\.unrecognized).count) } - NavigationLink(destination: VehiclesListView()) { + NavigationLink(destination: VehiclesListView(vehicles: vehicles.filter(\.outdated))) { Label("Outdated", systemImage: "wind") .badge(vehicles.filter(\.outdated).count) } @@ -40,13 +41,18 @@ struct SidebarView: View { .collapsible(false) } .frame(minWidth: 180) + .sheet(isPresented: $showAddPlateNumberView, onDismiss: { + print("Dismiss") + }, content: { + CheckNumberPopup() + }) .toolbar { ToolbarItem { Spacer() } ToolbarItem { Button { - + self.showAddPlateNumberView = true } label: { Image(systemName: "plus") } @@ -56,16 +62,18 @@ struct SidebarView: View { } } -struct VehiclesListView: View { +struct VehiclesListView: View where VehicleCollection: RandomAccessCollection, VehicleCollection.Element == CDVehicle { + + var vehicles: VehicleCollection + @State var selection: CDVehicle? + var body: some View { - Text("Vehicles list") - .toolbar { - ToolbarItem(placement: .primaryAction) { - Button("xxx") { - - } - } + List(selection: $selection) { + ForEach(vehicles, id: \.self) { vehicle in + let number = PlateNumber(vehicle.number ?? "") + PlateNumberView(number: number, unrecognized: vehicle.unrecognized, outdated: vehicle.outdated) } + } } }