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 0000000..7f40a38 Binary files /dev/null and b/Shared/Fonts/RoadNumbers.otf differ diff --git a/Shared/Fonts/RoadNumbers2.0.otf b/Shared/Fonts/RoadNumbers2.0.otf new file mode 100644 index 0000000..390c01e Binary files /dev/null and b/Shared/Fonts/RoadNumbers2.0.otf differ 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) } + } } }