Switching from CoreData to Realm
This commit is contained in:
parent
21d396b311
commit
18f1a00d82
@ -49,6 +49,9 @@
|
||||
7A36E56027FB5A2F0025AACB /* MockURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AAE27FA3CCF001A18EE /* MockURLProtocol.swift */; };
|
||||
7A36E56127FB5A330025AACB /* ApiMethodMockProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AAF27FA3CCF001A18EE /* ApiMethodMockProtocol.swift */; };
|
||||
7A36E56327FB5BEB0025AACB /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A36E56227FB5BEB0025AACB /* TestError.swift */; };
|
||||
7A39D0772998F25600A6F6FC /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A39D0762998F25600A6F6FC /* PlaceholderView.swift */; };
|
||||
7A39D07A2998FF3500A6F6FC /* RealmDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A39D0792998FF3500A6F6FC /* RealmDecoding.swift */; };
|
||||
7A39D07D299904B700A6F6FC /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A39D07C299904B700A6F6FC /* SidebarView.swift */; };
|
||||
7A48B26727D9442A004D1A4B /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7A48B26627D9442A004D1A4B /* PKHUD */; };
|
||||
7A4951B7288D3A6100C644B6 /* AutoCat2SUIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4951B6288D3A6100C644B6 /* AutoCat2SUIApp.swift */; };
|
||||
7A4951B9288D3A6100C644B6 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4951B8288D3A6100C644B6 /* RootView.swift */; };
|
||||
@ -87,9 +90,10 @@
|
||||
7A49F51027D406CB00AEAAE0 /* MainSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F50927D406CB00AEAAE0 /* MainSettings.swift */; };
|
||||
7A49F51127D406CB00AEAAE0 /* PlateNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F50A27D406CB00AEAAE0 /* PlateNumber.swift */; };
|
||||
7A49F51227D406CB00AEAAE0 /* VName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F50B27D406CB00AEAAE0 /* VName.swift */; };
|
||||
7A49F51527D40C6100AEAAE0 /* AutoCat2.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F51327D40C6100AEAAE0 /* AutoCat2.xcdatamodeld */; };
|
||||
7A558AB027FA3CCF001A18EE /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AA527FA3CCF001A18EE /* SettingsTests.swift */; };
|
||||
7A558AB127FA3CCF001A18EE /* ApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A558AA627FA3CCF001A18EE /* ApiTests.swift */; };
|
||||
7A91E8112986DE9C0008A982 /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 7A91E8102986DE9C0008A982 /* Realm */; };
|
||||
7A91E8132986DE9C0008A982 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A91E8122986DE9C0008A982 /* RealmSwift */; };
|
||||
7A9471BD28C52DD80054CB0A /* PlateViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9471BC28C52DD80054CB0A /* PlateViewItem.swift */; };
|
||||
7A9F2AC327E71531006492A9 /* ACTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9F2AC227E71531006492A9 /* ACTabBarController.swift */; };
|
||||
7A9FD4072857AF590057ECFA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FD4062857AF590057ECFA /* AppDelegate.swift */; };
|
||||
@ -246,6 +250,9 @@
|
||||
7A2B6CD527FCEC8600519F1E /* XCUIApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIApplication.swift; sourceTree = "<group>"; };
|
||||
7A36E55B27FB55570025AACB /* Testing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Testing.swift; sourceTree = "<group>"; };
|
||||
7A36E56227FB5BEB0025AACB /* TestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestError.swift; sourceTree = "<group>"; };
|
||||
7A39D0762998F25600A6F6FC /* PlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderView.swift; sourceTree = "<group>"; };
|
||||
7A39D0792998FF3500A6F6FC /* RealmDecoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmDecoding.swift; sourceTree = "<group>"; };
|
||||
7A39D07C299904B700A6F6FC /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; };
|
||||
7A4951B4288D3A6100C644B6 /* AutoCat2SUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AutoCat2SUI.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7A4951B6288D3A6100C644B6 /* AutoCat2SUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCat2SUIApp.swift; sourceTree = "<group>"; };
|
||||
7A4951B8288D3A6100C644B6 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
|
||||
@ -286,7 +293,6 @@
|
||||
7A49F50927D406CB00AEAAE0 /* MainSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainSettings.swift; sourceTree = "<group>"; };
|
||||
7A49F50A27D406CB00AEAAE0 /* PlateNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlateNumber.swift; sourceTree = "<group>"; };
|
||||
7A49F50B27D406CB00AEAAE0 /* VName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VName.swift; sourceTree = "<group>"; };
|
||||
7A49F51427D40C6100AEAAE0 /* Shared.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Shared.xcdatamodel; sourceTree = "<group>"; };
|
||||
7A558AA527FA3CCF001A18EE /* SettingsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = "<group>"; };
|
||||
7A558AA627FA3CCF001A18EE /* ApiTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiTests.swift; sourceTree = "<group>"; };
|
||||
7A558AA927FA3CCF001A18EE /* LoginMethodMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginMethodMock.swift; sourceTree = "<group>"; };
|
||||
@ -367,6 +373,8 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7A91E8132986DE9C0008A982 /* RealmSwift in Frameworks */,
|
||||
7A91E8112986DE9C0008A982 /* Realm in Frameworks */,
|
||||
7A1D80E627F20FCB007BD64F /* DifferenceKit in Frameworks */,
|
||||
7AE32D6E27F06D2D004EF6E0 /* SwiftDate in Frameworks */,
|
||||
);
|
||||
@ -523,6 +531,22 @@
|
||||
path = Testing;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A39D0782998FF1800A6F6FC /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A39D0792998FF3500A6F6FC /* RealmDecoding.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A39D07B2999049F00A6F6FC /* Sidebar */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A39D07C299904B700A6F6FC /* SidebarView.swift */,
|
||||
);
|
||||
path = Sidebar;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A4951B5288D3A6100C644B6 /* AutoCat2SUI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -550,6 +574,7 @@
|
||||
7A4951C3288D3AF000C644B6 /* Screens */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A39D07B2999049F00A6F6FC /* Sidebar */,
|
||||
7AE9F20228CDFCAB00ABF6DF /* VehicleDetail */,
|
||||
7A163BEF28BBE9DA0005A0A4 /* VehiclesList */,
|
||||
7AF1D0D628BB5768004E19F7 /* CheckNumber */,
|
||||
@ -582,6 +607,7 @@
|
||||
7A054FD728C4BB700002C386 /* PlateView */,
|
||||
7A4951B8288D3A6100C644B6 /* RootView.swift */,
|
||||
7A4951D0288D5C4300C644B6 /* ACProgressView.swift */,
|
||||
7A39D0762998F25600A6F6FC /* PlaceholderView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@ -663,9 +689,9 @@
|
||||
7A49F4D827D4064500AEAAE0 /* AutoCatCore */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A39D0782998FF1800A6F6FC /* Extensions */,
|
||||
7A36E55A27FB54610025AACB /* Testing */,
|
||||
7AE32D6F27F06D87004EF6E0 /* DataSource */,
|
||||
7A49F51327D40C6100AEAAE0 /* AutoCat2.xcdatamodeld */,
|
||||
7A49F50427D406CB00AEAAE0 /* Models */,
|
||||
7A49F4FF27D406C300AEAAE0 /* Services */,
|
||||
7A49F4FC27D406BA00AEAAE0 /* ThirdParty */,
|
||||
@ -973,6 +999,8 @@
|
||||
packageProductDependencies = (
|
||||
7AE32D6D27F06D2D004EF6E0 /* SwiftDate */,
|
||||
7A1D80E527F20FCB007BD64F /* DifferenceKit */,
|
||||
7A91E8102986DE9C0008A982 /* Realm */,
|
||||
7A91E8122986DE9C0008A982 /* RealmSwift */,
|
||||
);
|
||||
productName = AutoCatCore;
|
||||
productReference = 7A49F4D727D4064500AEAAE0 /* AutoCatCore.framework */;
|
||||
@ -1070,6 +1098,7 @@
|
||||
7AE32D6C27F06D2D004EF6E0 /* XCRemoteSwiftPackageReference "SwiftDate" */,
|
||||
7A1D80DE27F1F275007BD64F /* XCRemoteSwiftPackageReference "DifferenceKit" */,
|
||||
7A1D80E427F20FCB007BD64F /* XCRemoteSwiftPackageReference "DifferenceKit" */,
|
||||
7A91E80F2986DE9C0008A982 /* XCRemoteSwiftPackageReference "realm-swift" */,
|
||||
);
|
||||
productRefGroup = 7A49F4A027D4061900AEAAE0 /* Products */;
|
||||
projectDirPath = "";
|
||||
@ -1157,6 +1186,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7A4951D3288D5E2800C644B6 /* AuthVM.swift in Sources */,
|
||||
7A39D07D299904B700A6F6FC /* SidebarView.swift in Sources */,
|
||||
7A054FDA28C4C24B0002C386 /* CenterTextLayer.swift in Sources */,
|
||||
7A163BF128BBE9ED0005A0A4 /* VehiclesListView.swift in Sources */,
|
||||
7A054FD928C4BE560002C386 /* PlateView.swift in Sources */,
|
||||
@ -1164,6 +1194,7 @@
|
||||
7A4951C7288D3BDD00C644B6 /* MainView.swift in Sources */,
|
||||
7AE9F20428CDFCC600ABF6DF /* VehicleDetailView.swift in Sources */,
|
||||
7AF1D0D828BB577E004E19F7 /* CheckNumber.swift in Sources */,
|
||||
7A39D0772998F25600A6F6FC /* PlaceholderView.swift in Sources */,
|
||||
7A4951B7288D3A6100C644B6 /* AutoCat2SUIApp.swift in Sources */,
|
||||
7A9471BD28C52DD80054CB0A /* PlateViewItem.swift in Sources */,
|
||||
7AF1D0DA28BB5BF5004E19F7 /* View.swift in Sources */,
|
||||
@ -1253,9 +1284,9 @@
|
||||
7A49F50F27D406CB00AEAAE0 /* Response.swift in Sources */,
|
||||
929EDE8127F8A75E00E55F65 /* VPhoto.swift in Sources */,
|
||||
7A49F4FB27D406B200AEAAE0 /* Api.swift in Sources */,
|
||||
7A49F51527D40C6100AEAAE0 /* AutoCat2.xcdatamodeld in Sources */,
|
||||
929EDE7F27F89C3000E55F65 /* VEngine.swift in Sources */,
|
||||
929EDE8727F8E11E00E55F65 /* VOsago.swift in Sources */,
|
||||
7A39D07A2998FF3500A6F6FC /* RealmDecoding.swift in Sources */,
|
||||
7A2B6CD427FCE93C00519F1E /* TestSettings.swift in Sources */,
|
||||
7A49F50E27D406CB00AEAAE0 /* User.swift in Sources */,
|
||||
7A36E56127FB5A330025AACB /* ApiMethodMockProtocol.swift in Sources */,
|
||||
@ -1974,6 +2005,14 @@
|
||||
minimumVersion = 5.0.0;
|
||||
};
|
||||
};
|
||||
7A91E80F2986DE9C0008A982 /* XCRemoteSwiftPackageReference "realm-swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/realm/realm-swift.git";
|
||||
requirement = {
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
7AE32D6C27F06D2D004EF6E0 /* XCRemoteSwiftPackageReference "SwiftDate" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/malcommac/SwiftDate";
|
||||
@ -2005,6 +2044,16 @@
|
||||
package = 7A48B26527D9442A004D1A4B /* XCRemoteSwiftPackageReference "PKHUD" */;
|
||||
productName = PKHUD;
|
||||
};
|
||||
7A91E8102986DE9C0008A982 /* Realm */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 7A91E80F2986DE9C0008A982 /* XCRemoteSwiftPackageReference "realm-swift" */;
|
||||
productName = Realm;
|
||||
};
|
||||
7A91E8122986DE9C0008A982 /* RealmSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 7A91E80F2986DE9C0008A982 /* XCRemoteSwiftPackageReference "realm-swift" */;
|
||||
productName = RealmSwift;
|
||||
};
|
||||
7A9FD4192857AF8D0057ECFA /* DifferenceKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 7A1D80DE27F1F275007BD64F /* XCRemoteSwiftPackageReference "DifferenceKit" */;
|
||||
@ -2016,19 +2065,6 @@
|
||||
productName = SwiftDate;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
7A49F51327D40C6100AEAAE0 /* AutoCat2.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
7A49F51427D40C6100AEAAE0 /* Shared.xcdatamodel */,
|
||||
);
|
||||
currentVersion = 7A49F51427D40C6100AEAAE0 /* Shared.xcdatamodel */;
|
||||
path = AutoCat2.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
};
|
||||
/* End XCVersionGroup section */
|
||||
};
|
||||
rootObject = 7A49F49727D4061900AEAAE0 /* Project object */;
|
||||
}
|
||||
|
||||
@ -18,6 +18,24 @@
|
||||
"version" : "5.4.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "realm-core",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/realm/realm-core.git",
|
||||
"state" : {
|
||||
"revision" : "b77443ca7fa25407869ca537bf3ae912b1427bff",
|
||||
"version" : "12.13.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "realm-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/realm/realm-swift.git",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "f5aaf456ea725ac2f352b0b3decc78a7094adbd8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftdate",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
@ -12,17 +12,17 @@
|
||||
<key>AutoCat2.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<key>AutoCat2Mac.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
<key>AutoCat2SUI.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>1</integer>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>AutoCat2UITests.testExample.xcscheme</key>
|
||||
<dict>
|
||||
@ -41,14 +41,14 @@
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
<integer>8</integer>
|
||||
</dict>
|
||||
<key>DifferenceKit (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>4</integer>
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>DifferenceKit (Playground) 3.xcscheme</key>
|
||||
<dict>
|
||||
@ -76,21 +76,42 @@
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
<integer>7</integer>
|
||||
</dict>
|
||||
<key>GettingStarted (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>10</integer>
|
||||
</dict>
|
||||
<key>GettingStarted (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>11</integer>
|
||||
</dict>
|
||||
<key>GettingStarted (Playground).xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
<key>SwiftDate (Playground) 1.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>6</integer>
|
||||
<integer>5</integer>
|
||||
</dict>
|
||||
<key>SwiftDate (Playground) 2.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>7</integer>
|
||||
<integer>6</integer>
|
||||
</dict>
|
||||
<key>SwiftDate (Playground) 3.xcscheme</key>
|
||||
<dict>
|
||||
@ -118,11 +139,16 @@
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
<key>orderHint</key>
|
||||
<integer>5</integer>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>7A4951B3288D3A6100C644B6</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>7A49F4D627D4064500AEAAE0</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
|
||||
@ -86,12 +86,8 @@ class VehicleCell: UITableViewCell {
|
||||
|
||||
extension VehicleCell: ConfigurableCell {
|
||||
|
||||
func configure(with vehicle: CDVehicle) {
|
||||
guard let number = vehicle.number else {
|
||||
return
|
||||
}
|
||||
|
||||
plateView.number = PlateNumber(number)
|
||||
func configure(with vehicle: Vehicle) {
|
||||
plateView.number = PlateNumber(vehicle.number)
|
||||
nameLabel.text = vehicle.brand?.name?.original ?? "<Unknown>"
|
||||
|
||||
if vehicle.unrecognized {
|
||||
|
||||
@ -18,7 +18,7 @@ class HistoryController: UIViewController, UITableViewDelegate {
|
||||
return table
|
||||
}()
|
||||
|
||||
private var dataSource: CoreDataSource<CDVehicle, VehicleCell>?
|
||||
private var dataSource: CoreDataSource<Vehicle, VehicleCell>?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
@ -14,16 +14,12 @@ struct AutoCat2SUIApp: App {
|
||||
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
|
||||
let storageService = StorageService.sharedNotWait
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
if Testing.isUITesting {
|
||||
RootView(settings: getTestSettings())
|
||||
.environment(\.managedObjectContext, storageService.context)
|
||||
} else {
|
||||
RootView(settings: getSettings())
|
||||
.environment(\.managedObjectContext, storageService.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ struct CheckNumber: View {
|
||||
}
|
||||
.disabled(showProgress)
|
||||
Button("Check") {
|
||||
Task.init {
|
||||
Task { @MainActor in
|
||||
showProgress = true
|
||||
do {
|
||||
try await VehicleService.shared.check(plateNumber: plateNumber.uppercased(), force: false)
|
||||
|
||||
@ -7,125 +7,33 @@
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
struct SidebarSection: Identifiable {
|
||||
|
||||
let name: String
|
||||
let filters: [Filter]
|
||||
let id = UUID()
|
||||
|
||||
func contains(filter id: UUID) -> Bool {
|
||||
filters.contains { $0.id == id }
|
||||
}
|
||||
}
|
||||
import RealmSwift
|
||||
|
||||
struct MainView: View {
|
||||
|
||||
@FetchRequest(entity: CDVehicle.entity(), sortDescriptors: []) var vehicles: FetchedResults<CDVehicle>
|
||||
|
||||
@State private var selectedFilterId: UUID?
|
||||
@State var selectedVehicle: CDVehicle?
|
||||
@State private var checkSheetPresented = false
|
||||
@State private var searchText = ""
|
||||
|
||||
@State var columnVisibility = NavigationSplitViewVisibility.all
|
||||
|
||||
private let sections: [SidebarSection] = [
|
||||
SidebarSection(name: "History", filters: [
|
||||
.allLocal,
|
||||
.unrecognized,
|
||||
.outdated
|
||||
]),
|
||||
SidebarSection(name: "Remote", filters: [
|
||||
.allRemote
|
||||
])
|
||||
]
|
||||
|
||||
var selectedFilter: Filter? {
|
||||
guard let filterId = selectedFilterId else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return sections.first(where: { $0.contains(filter: filterId) })?
|
||||
.filters.first(where: { $0.id == filterId })
|
||||
}
|
||||
@State var selectedFilter: Filter?
|
||||
@State var selectedVehicle: Vehicle?
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView(columnVisibility: $columnVisibility) {
|
||||
List(selection: $selectedFilterId) {
|
||||
ForEach(sections) { section in
|
||||
Section(section.name) {
|
||||
ForEach(section.filters) { filter in
|
||||
Label(filter.name, systemImage: filter.iconName)
|
||||
.badge(vehicles.filter(filter.match).count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationSplitViewColumnWidth(180)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .primaryAction) {
|
||||
Spacer()
|
||||
Button {
|
||||
checkSheetPresented = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
.sheet(isPresented: $checkSheetPresented) {
|
||||
CheckNumber()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
SidebarView(selectedFilter: $selectedFilter)
|
||||
} content: {
|
||||
if let filter = selectedFilter {
|
||||
let filtered = vehicles.filter(filter.match)
|
||||
if filtered.isEmpty {
|
||||
VStack {
|
||||
Image(systemName: filter.iconName)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 64)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text("Nothing here")
|
||||
.font(.system(size: 18))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
} else {
|
||||
VehiclesListView(vehicles: filtered, selection: $selectedVehicle)
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button(action: {}) {
|
||||
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(filter.name)
|
||||
.navigationSubtitle("\(filtered.count) vehicles")
|
||||
}
|
||||
VehiclesListView(filter: filter, selection: $selectedVehicle)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
} detail: {
|
||||
if let vehicle = selectedVehicle {
|
||||
VehicleDetailView(vehicle: vehicle)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .automatic) {
|
||||
Button(action: {}) {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(vehicle.brand?.name?.original ?? "")
|
||||
.navigationSubtitle("\(vehicle.year)")
|
||||
.searchable(text: $searchText, placement: .toolbar)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.navigationSplitViewStyle(.balanced)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
74
AutoCat2SUI/Screens/Sidebar/SidebarView.swift
Normal file
74
AutoCat2SUI/Screens/Sidebar/SidebarView.swift
Normal file
@ -0,0 +1,74 @@
|
||||
//
|
||||
// SidebarView.swift
|
||||
// AutoCat2SUI
|
||||
//
|
||||
// Created by Selim Mustafaev on 12.02.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
import RealmSwift
|
||||
|
||||
struct SidebarSection: Identifiable {
|
||||
|
||||
let name: String
|
||||
let filters: [Filter]
|
||||
let id = UUID()
|
||||
|
||||
func contains(filter id: UUID) -> Bool {
|
||||
filters.contains { $0.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
struct SidebarView: View {
|
||||
|
||||
@ObservedResults(Vehicle.self) var vehicles
|
||||
|
||||
@Binding var selectedFilter: Filter?
|
||||
@State private var checkSheetPresented = false
|
||||
|
||||
private let sections: [SidebarSection] = [
|
||||
SidebarSection(name: "History", filters: [
|
||||
.allLocal,
|
||||
.unrecognized,
|
||||
.outdated
|
||||
]),
|
||||
SidebarSection(name: "Remote", filters: [
|
||||
.allRemote
|
||||
])
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
List(selection: $selectedFilter) {
|
||||
ForEach(sections) { section in
|
||||
Section(section.name) {
|
||||
ForEach(section.filters, id: \.self) { filter in
|
||||
Label(filter.name, systemImage: filter.iconName)
|
||||
.badge(vehicles.filter(filter.match).count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationSplitViewColumnWidth(180)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .primaryAction) {
|
||||
Spacer()
|
||||
Button {
|
||||
checkSheetPresented = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
.sheet(isPresented: $checkSheetPresented) {
|
||||
CheckNumber()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SidebarView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SidebarView(selectedFilter: .constant(nil))
|
||||
}
|
||||
}
|
||||
@ -26,16 +26,16 @@ struct ReportTextItem: View {
|
||||
|
||||
struct VehicleDetailView: View {
|
||||
|
||||
let vehicle: CDVehicle
|
||||
let vehicle: Vehicle
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(header: Text("General")) {
|
||||
ReportTextItem(name: "Year", value: String(vehicle.year))
|
||||
ReportTextItem(name: "Year", value: String(vehicle.year ?? 0))
|
||||
ReportTextItem(name: "Color", value: vehicle.color)
|
||||
ReportTextItem(name: "Category", value: vehicle.category)
|
||||
ReportTextItem(name: "Steering wheel position", value: vehicle.isRightWheel ? "Right": "Left")
|
||||
ReportTextItem(name: "Japanese", value: vehicle.isJapanese ? "Yes" : "No")
|
||||
ReportTextItem(name: "Steering wheel position", value: vehicle.isRightWheel == true ? "Right": "Left")
|
||||
ReportTextItem(name: "Japanese", value: vehicle.isJapanese == true ? "Yes" : "No")
|
||||
}
|
||||
Section(header: Text("Identifiers")) {
|
||||
ReportTextItem(name: "Plate number", value: vehicle.number)
|
||||
@ -52,6 +52,15 @@ struct VehicleDetailView: View {
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .automatic) {
|
||||
Button(action: {}) {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(vehicle.brand?.name?.original ?? "")
|
||||
.navigationSubtitle("\(vehicle.year ?? 0)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +73,6 @@ struct VehicleDetailView_Previews: PreviewProvider {
|
||||
addedDate: Date().timeIntervalSince1970,
|
||||
updatedDate: Date().timeIntervalSince1970)
|
||||
|
||||
return VehicleDetailView(vehicle: CDVehicle(vehicle: test1,
|
||||
context: NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)))
|
||||
return VehicleDetailView(vehicle: test1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,27 +7,43 @@
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
import RealmSwift
|
||||
|
||||
struct VehiclesListView: View {
|
||||
|
||||
var vehicles: [CDVehicle]
|
||||
@Binding var selection: CDVehicle?
|
||||
var filter: Filter
|
||||
@Binding var selection: Vehicle?
|
||||
|
||||
@ObservedResults(Vehicle.self) var vehicles
|
||||
|
||||
var body: some View {
|
||||
let filtered: [Vehicle] = vehicles.filter(filter.match)
|
||||
if filtered.isEmpty {
|
||||
PlaceholderView(imageName: filter.iconName)
|
||||
} else {
|
||||
List(selection: $selection) {
|
||||
ForEach(vehicles, id: \.self) { vehicle in
|
||||
ForEach(filtered, id: \.self) { vehicle in
|
||||
PlateViewItem(vehicle: vehicle)
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button(action: {}) {
|
||||
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(filter.name)
|
||||
.navigationSubtitle("\(filtered.count) vehicles")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VehiclesListView_Previews: PreviewProvider {
|
||||
|
||||
@State var selection: CDVehicle?
|
||||
@State var selection: Vehicle?
|
||||
|
||||
static var previews: some View {
|
||||
VehiclesListView(vehicles: [
|
||||
], selection: .constant(nil))
|
||||
VehiclesListView(filter: .allLocal, selection: .constant(nil))
|
||||
}
|
||||
}
|
||||
|
||||
39
AutoCat2SUI/Views/PlaceholderView.swift
Normal file
39
AutoCat2SUI/Views/PlaceholderView.swift
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// PlaceholderView.swift
|
||||
// AutoCat2SUI
|
||||
//
|
||||
// Created by Selim Mustafaev on 12.02.2023.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PlaceholderView: View {
|
||||
|
||||
let imageName: String
|
||||
let text: String
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image(systemName: imageName)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 64)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text(text)
|
||||
.font(.system(size: 18))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
init(imageName: String, text: String = "Nothing here") {
|
||||
self.imageName = imageName
|
||||
self.text = text
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaceholderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PlaceholderView(imageName: "eye", text: "Nothing here")
|
||||
}
|
||||
}
|
||||
@ -10,21 +10,21 @@ import AutoCatCore
|
||||
|
||||
struct PlateViewItem: View {
|
||||
|
||||
let vehicle: CDVehicle
|
||||
let vehicle: Vehicle
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(vehicle.brand?.name?.original ?? "")
|
||||
|
||||
HStack {
|
||||
PlateNumberView(number: PlateNumber(vehicle.number ?? ""),
|
||||
PlateNumberView(number: PlateNumber(vehicle.number),
|
||||
unrecognized: vehicle.unrecognized,
|
||||
outdated: vehicle.outdated,
|
||||
fontSize: 30)
|
||||
VStack {
|
||||
if let upDate = vehicle.updatedDate {
|
||||
|
||||
}
|
||||
// if let upDate = vehicle.updatedDate {
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -41,7 +41,6 @@ struct PlateViewItem_Previews: PreviewProvider {
|
||||
addedDate: Date().timeIntervalSince1970,
|
||||
updatedDate: Date().timeIntervalSince1970)
|
||||
|
||||
return PlateViewItem(vehicle: CDVehicle(vehicle: test1,
|
||||
context: NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)))
|
||||
return PlateViewItem(vehicle: test1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>Shared.xcdatamodel</string>
|
||||
</dict>
|
||||
<dict/>
|
||||
</plist>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="20G80" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22D49" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
|
||||
<entity name="DebugInfo" representedClassName=".CDDebugInfo" syncable="YES" codeGenerationType="class">
|
||||
<relationship name="autocod" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DebugInfoEntry" inverseName="autocodInfo" inverseEntity="DebugInfoEntry"/>
|
||||
<relationship name="nomerogram" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DebugInfoEntry" inverseName="nomerogramInfo" inverseEntity="DebugInfoEntry"/>
|
||||
@ -131,19 +131,4 @@
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
<relationship name="vehicle" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Vehicle" inverseName="photos" inverseEntity="Vehicle"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="VBrand" positionX="118.7528686523438" positionY="-209.1094360351562" width="128" height="74"/>
|
||||
<element name="Vehicle" positionX="-145.9427490234375" positionY="-363.4805297851562" width="128" height="404"/>
|
||||
<element name="VModel" positionX="-9" positionY="-207" width="128" height="59"/>
|
||||
<element name="VName" positionX="-54" positionY="9" width="128" height="89"/>
|
||||
<element name="VEngine" positionX="-9" positionY="-207" width="128" height="119"/>
|
||||
<element name="VPhoto" positionX="0" positionY="-198" width="128" height="104"/>
|
||||
<element name="VOwnershipPeriod" positionX="9" positionY="-189" width="128" height="209"/>
|
||||
<element name="VEvent" positionX="18" positionY="-180" width="128" height="149"/>
|
||||
<element name="VOsago" positionX="27" positionY="-171" width="128" height="194"/>
|
||||
<element name="VAd" positionX="36" positionY="-162" width="128" height="179"/>
|
||||
<element name="VNote" positionX="45" positionY="-153" width="128" height="104"/>
|
||||
<element name="DebugInfoEntry" positionX="-9" positionY="-207" width="128" height="149"/>
|
||||
<element name="DebugInfo" positionX="0" positionY="-198" width="128" height="119"/>
|
||||
</elements>
|
||||
</model>
|
||||
16
AutoCatCore/Extensions/RealmDecoding.swift
Normal file
16
AutoCatCore/Extensions/RealmDecoding.swift
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// RealmDecoding.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 12.02.2023.
|
||||
//
|
||||
|
||||
import RealmSwift
|
||||
|
||||
extension KeyedDecodingContainer {
|
||||
|
||||
func decode<T: Decodable>(_ type: Persisted<List<T>>.Type, forKey key: Key) throws -> Persisted<List<T>> {
|
||||
// Use decode if present, falling back to an empty list
|
||||
try decodeIfPresent(type, forKey: key) ?? Persisted<List<T>>(wrappedValue: List<T>())
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,7 @@ public enum DataSource: CaseIterable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct Filter: Hashable, Identifiable {
|
||||
public final class Filter: Hashable, Identifiable {
|
||||
|
||||
public let id = UUID()
|
||||
public let name: String
|
||||
@ -49,10 +49,20 @@ public struct Filter: Hashable, Identifiable {
|
||||
self.outdated = outdated
|
||||
}
|
||||
|
||||
public func match(vehicle: CDVehicle) -> Bool {
|
||||
public func match(vehicle: Vehicle) -> Bool {
|
||||
return unrecognized == vehicle.unrecognized
|
||||
&& outdated == vehicle.outdated
|
||||
}
|
||||
|
||||
public static func == (lhs: Filter, rhs: Filter) -> Bool {
|
||||
lhs.dataSource == rhs.dataSource
|
||||
&& lhs.unrecognized == rhs.unrecognized
|
||||
&& lhs.outdated == rhs.outdated
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Presets
|
||||
|
||||
@ -1,109 +1,46 @@
|
||||
import Foundation
|
||||
import CoreData
|
||||
import RealmSwift
|
||||
import DifferenceKit
|
||||
|
||||
public struct Vehicle: Decodable {
|
||||
public final class Vehicle: Object, Decodable {
|
||||
|
||||
public var number: String
|
||||
public var currentNumber: String?
|
||||
public var brand: VBrand?
|
||||
public var model: VModel?
|
||||
public var addedDate: TimeInterval
|
||||
public var updatedDate: TimeInterval
|
||||
public var color: String?
|
||||
public var year: Int64?
|
||||
public var category: String?
|
||||
public var vin1: String?
|
||||
public var vin2: String?
|
||||
public var sts: String?
|
||||
public var pts: String?
|
||||
public var isRightWheel: Bool?
|
||||
public var isJapanese: Bool?
|
||||
public var addedBy: String?
|
||||
public var engine: VEngine?
|
||||
public var photos: [VPhoto]?
|
||||
public var ownershipPeriods: [VOwnershipPeriod]?
|
||||
public var events: [VEvent]?
|
||||
public var osagoContracts: [VOsago]?
|
||||
public var ads: [VAd]?
|
||||
public var notes: [VNote]?
|
||||
public var debugInfo: DebugInfo?
|
||||
@Persisted(primaryKey: true) public var number: String
|
||||
@Persisted public var currentNumber: String?
|
||||
@Persisted public var brand: VBrand?
|
||||
@Persisted public var model: VModel?
|
||||
@Persisted public var addedDate: TimeInterval
|
||||
@Persisted public var updatedDate: TimeInterval
|
||||
@Persisted public var color: String?
|
||||
@Persisted public var year: Int64?
|
||||
@Persisted public var category: String?
|
||||
@Persisted public var vin1: String?
|
||||
@Persisted public var vin2: String?
|
||||
@Persisted public var sts: String?
|
||||
@Persisted public var pts: String?
|
||||
@Persisted public var isRightWheel: Bool?
|
||||
@Persisted public var isJapanese: Bool?
|
||||
@Persisted public var addedBy: String?
|
||||
@Persisted public var engine: VEngine?
|
||||
@Persisted public var photos: List<VPhoto>
|
||||
@Persisted public var ownershipPeriods: List<VOwnershipPeriod>
|
||||
@Persisted public var events: List<VEvent>
|
||||
@Persisted public var osagoContracts: List<VOsago>
|
||||
@Persisted public var ads: List<VAd>
|
||||
@Persisted public var notes: List<VNote>
|
||||
@Persisted public var debugInfo: DebugInfo?
|
||||
|
||||
public init(number: String, brandName: String, addedDate: TimeInterval, updatedDate: TimeInterval) {
|
||||
public convenience init(number: String, brandName: String, addedDate: TimeInterval, updatedDate: TimeInterval) {
|
||||
self.init()
|
||||
self.number = number
|
||||
self.currentNumber = number
|
||||
self.addedDate = addedDate
|
||||
self.updatedDate = updatedDate
|
||||
|
||||
self.brand = VBrand(name: VName(original: brandName))
|
||||
}
|
||||
}
|
||||
let name = VName()
|
||||
name.original = brandName
|
||||
|
||||
extension CDVehicle: Dated {
|
||||
|
||||
public var updatedAt: Date {
|
||||
Date(timeIntervalSince1970: updatedDate)
|
||||
}
|
||||
|
||||
public var addedAt: Date {
|
||||
Date(timeIntervalSince1970: addedDate)
|
||||
}
|
||||
}
|
||||
|
||||
extension CDVehicle: Differentiable {
|
||||
|
||||
public var differenceIdentifier: String { number! }
|
||||
|
||||
public func isContentEqual(to source: CDVehicle) -> Bool {
|
||||
return number == source.number &&
|
||||
addedDate == source.addedDate &&
|
||||
updatedDate == source.updatedDate
|
||||
}
|
||||
}
|
||||
|
||||
extension CDVehicle {
|
||||
|
||||
public convenience init(vehicle: Vehicle, context: NSManagedObjectContext) {
|
||||
|
||||
self.init(context: context)
|
||||
self.number = vehicle.number
|
||||
self.currentNumber = vehicle.currentNumber
|
||||
self.addedDate = vehicle.addedDate/1000
|
||||
self.updatedDate = vehicle.updatedDate/1000
|
||||
self.color = vehicle.color
|
||||
self.brand = CDVBrand(vbrand: vehicle.brand, context: context)
|
||||
self.model = CDVModel(model: vehicle.model, context: context)
|
||||
self.year = vehicle.year ?? 0
|
||||
self.category = category
|
||||
self.vin1 = vehicle.vin1
|
||||
self.vin2 = vehicle.vin2
|
||||
self.sts = vehicle.sts
|
||||
self.pts = vehicle.pts
|
||||
self.isRightWheel = vehicle.isRightWheel ?? false
|
||||
self.isJapanese = vehicle.isJapanese ?? false
|
||||
self.addedBy = vehicle.addedBy
|
||||
self.synchronized = true
|
||||
self.engine = CDVEngine(model: vehicle.engine, context: context)
|
||||
|
||||
let photos = vehicle.photos?.map { CDVPhoto(model: $0, context: context) } ?? []
|
||||
self.photos = NSSet(array: photos)
|
||||
|
||||
let ownershipPeriods = vehicle.ownershipPeriods?.map { CDVOwnershipPeriod(model: $0, context: context) } ?? []
|
||||
self.ownershipPeriods = NSSet(array: ownershipPeriods)
|
||||
|
||||
let events = vehicle.events?.map { CDVEvent(model: $0, context: context) } ?? []
|
||||
self.events = NSSet(array: events)
|
||||
|
||||
let osagoContracts = vehicle.osagoContracts?.map { CDVOsago(model: $0, context: context) } ?? []
|
||||
self.osagoContracts = NSSet(array: osagoContracts)
|
||||
|
||||
let ads = vehicle.ads?.map { CDVAd(model: $0, context: context) } ?? []
|
||||
self.ads = NSSet(array: ads)
|
||||
|
||||
let notes = vehicle.notes?.map { CDVNote(model: $0, context: context) } ?? []
|
||||
self.notes = NSSet(array: notes)
|
||||
|
||||
self.debugInfo = CDDebugInfo(model: vehicle.debugInfo, context: context)
|
||||
let brand = VBrand()
|
||||
brand.name = name
|
||||
self.brand = brand
|
||||
}
|
||||
|
||||
public var unrecognized: Bool {
|
||||
@ -117,5 +54,32 @@ extension CDVehicle {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Vehicle: Dated {
|
||||
|
||||
public var updatedAt: Date {
|
||||
Date(timeIntervalSince1970: updatedDate)
|
||||
}
|
||||
|
||||
public var addedAt: Date {
|
||||
Date(timeIntervalSince1970: addedDate)
|
||||
}
|
||||
}
|
||||
|
||||
extension Vehicle: Differentiable {
|
||||
|
||||
public var differenceIdentifier: String { number }
|
||||
|
||||
public func isContentEqual(to source: Vehicle) -> Bool {
|
||||
return number == source.number &&
|
||||
addedDate == source.addedDate &&
|
||||
updatedDate == source.updatedDate
|
||||
}
|
||||
}
|
||||
|
||||
extension Vehicle: Identifiable {
|
||||
|
||||
public var id: String { number }
|
||||
}
|
||||
|
||||
|
||||
@ -1,31 +1,10 @@
|
||||
//
|
||||
// DebugInfo.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 03.04.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public final class DebugInfo: Object, Decodable {
|
||||
|
||||
public struct DebugInfo: Decodable {
|
||||
|
||||
let autocod: DebugInfoEntry?
|
||||
let vin01vin: DebugInfoEntry?
|
||||
let vin01base: DebugInfoEntry?
|
||||
let vin01history: DebugInfoEntry?
|
||||
let nomerogram: DebugInfoEntry?
|
||||
}
|
||||
|
||||
extension CDDebugInfo {
|
||||
|
||||
convenience init(model: DebugInfo?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
|
||||
self.autocod = CDDebugInfoEntry(model: model?.autocod, context: context)
|
||||
self.vin01vin = CDDebugInfoEntry(model: model?.vin01vin, context: context)
|
||||
self.vin01base = CDDebugInfoEntry(model: model?.vin01base, context: context)
|
||||
self.vin01history = CDDebugInfoEntry(model: model?.vin01history, context: context)
|
||||
self.nomerogram = CDDebugInfoEntry(model: model?.nomerogram, context: context)
|
||||
}
|
||||
@Persisted var autocod: DebugInfoEntry?
|
||||
@Persisted var vin01vin: DebugInfoEntry?
|
||||
@Persisted var vin01base: DebugInfoEntry?
|
||||
@Persisted var vin01history: DebugInfoEntry?
|
||||
@Persisted var nomerogram: DebugInfoEntry?
|
||||
}
|
||||
|
||||
@ -1,38 +1,15 @@
|
||||
//
|
||||
// DebugInfoEntry.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 03.04.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public enum DebugInfoStatus: Int64, Decodable, PersistableEnum {
|
||||
|
||||
public enum DebugInfoStatus: Int64 {
|
||||
case success = 0
|
||||
case error = 1
|
||||
case warning = 2
|
||||
}
|
||||
|
||||
public struct DebugInfoEntry: Decodable {
|
||||
public final class DebugInfoEntry: Object, Decodable {
|
||||
|
||||
let fields: Int64
|
||||
let error: String?
|
||||
let status: Int64
|
||||
}
|
||||
|
||||
extension CDDebugInfoEntry {
|
||||
|
||||
convenience init(model: DebugInfoEntry?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
|
||||
self.fields = model?.fields ?? 0
|
||||
self.error = model?.error
|
||||
self.status = model?.status ?? 0
|
||||
}
|
||||
|
||||
public var statusEnum: DebugInfoStatus {
|
||||
get { DebugInfoStatus(rawValue: self.status)! }
|
||||
set { self.status = newValue.rawValue }
|
||||
}
|
||||
@Persisted var fields: Int64
|
||||
@Persisted var error: String?
|
||||
@Persisted var status: DebugInfoStatus?
|
||||
}
|
||||
|
||||
@ -1,39 +1,15 @@
|
||||
//
|
||||
// VAd.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.04.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public final class VAd: Object, Decodable {
|
||||
|
||||
public struct VAd: Decodable {
|
||||
|
||||
let id: Int64
|
||||
let url: String?
|
||||
let price: String?
|
||||
let date: Double
|
||||
let mileage: String?
|
||||
let region: String?
|
||||
let city: String?
|
||||
let adDescription: String?
|
||||
let photos: [String]
|
||||
@Persisted var id: Int64
|
||||
@Persisted var url: String?
|
||||
@Persisted var price: String?
|
||||
@Persisted var date: Double
|
||||
@Persisted var mileage: String?
|
||||
@Persisted var region: String?
|
||||
@Persisted var city: String?
|
||||
@Persisted var adDescription: String?
|
||||
@Persisted var photos: List<String>
|
||||
}
|
||||
|
||||
extension CDVAd {
|
||||
|
||||
convenience init(model: VAd?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
|
||||
self.id = model?.id ?? 0
|
||||
self.url = model?.url
|
||||
self.price = model?.price
|
||||
self.date = model?.date ?? 0
|
||||
self.mileage = model?.mileage
|
||||
self.region = model?.region
|
||||
self.city = model?.city
|
||||
self.adDescription = model?.adDescription
|
||||
self.photos = model?.photos
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,7 @@
|
||||
import Foundation
|
||||
import CoreData
|
||||
import RealmSwift
|
||||
|
||||
public struct VBrand: Decodable {
|
||||
|
||||
public var logo: String?
|
||||
public var name: VName?
|
||||
}
|
||||
|
||||
extension CDVBrand {
|
||||
|
||||
convenience init(vbrand: VBrand?, context: NSManagedObjectContext) {
|
||||
|
||||
self.init(context: context)
|
||||
self.logo = vbrand?.logo
|
||||
self.name = CDVName(vname: vbrand?.name, context: context)
|
||||
}
|
||||
public final class VBrand: Object, Decodable {
|
||||
|
||||
@Persisted public var logo: String?
|
||||
@Persisted public var name: VName?
|
||||
}
|
||||
|
||||
@ -1,31 +1,10 @@
|
||||
//
|
||||
// VEngine.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.04.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public final class VEngine: Object, Decodable {
|
||||
|
||||
public struct VEngine: Decodable {
|
||||
|
||||
let number: String?
|
||||
let volume: Int64?
|
||||
let powerHp: Float?
|
||||
let powerKw: Float?
|
||||
let fuelType: String?
|
||||
}
|
||||
|
||||
extension CDVEngine {
|
||||
|
||||
convenience init(model: VEngine?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
|
||||
self.number = model?.number
|
||||
self.volume = model?.volume ?? 0
|
||||
self.powerHp = model?.powerHp ?? 0
|
||||
self.powerKw = model?.powerKw ?? 0
|
||||
self.fuelType = model?.fuelType
|
||||
}
|
||||
@Persisted public var number: String?
|
||||
@Persisted public var volume: Int64?
|
||||
@Persisted public var powerHp: Float?
|
||||
@Persisted public var powerKw: Float?
|
||||
@Persisted public var fuelType: String?
|
||||
}
|
||||
|
||||
@ -1,35 +1,10 @@
|
||||
//
|
||||
// VEvent.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.04.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public final class VEvent: Object, Decodable {
|
||||
|
||||
public struct VEvent: Decodable {
|
||||
|
||||
let id: String
|
||||
let date: Double
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
let speed: Double
|
||||
let direction: Double
|
||||
let address: String?
|
||||
}
|
||||
|
||||
extension CDVEvent {
|
||||
|
||||
convenience init(model: VEvent?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
|
||||
self.id = model?.id
|
||||
self.date = model?.date ?? 0
|
||||
self.latitude = model?.latitude ?? 0
|
||||
self.longitude = model?.longitude ?? 0
|
||||
self.speed = model?.speed ?? 0
|
||||
self.direction = model?.direction ?? 0
|
||||
self.address = model?.address
|
||||
}
|
||||
@Persisted var id: String
|
||||
@Persisted var date: Double
|
||||
@Persisted var latitude: Double
|
||||
@Persisted var longitude: Double
|
||||
@Persisted var address: String?
|
||||
}
|
||||
|
||||
@ -1,22 +1,6 @@
|
||||
//
|
||||
// VModel.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 29.03.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public final class VModel: Object, Decodable {
|
||||
|
||||
public struct VModel: Decodable {
|
||||
|
||||
let name: VName?
|
||||
}
|
||||
|
||||
extension CDVModel {
|
||||
|
||||
convenience init(model: VModel?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
self.name = CDVName(vname: model?.name, context: context)
|
||||
}
|
||||
@Persisted var name: VName?
|
||||
}
|
||||
|
||||
@ -1,19 +1,7 @@
|
||||
import Foundation
|
||||
import CoreData
|
||||
import RealmSwift
|
||||
|
||||
public struct VName: Decodable {
|
||||
|
||||
public var normalized: String?
|
||||
public var original: String?
|
||||
}
|
||||
|
||||
extension CDVName {
|
||||
|
||||
convenience init(vname: VName?, context: NSManagedObjectContext) {
|
||||
|
||||
self.init(context: context)
|
||||
self.normalized = vname?.normalized
|
||||
self.original = vname?.original
|
||||
}
|
||||
public final class VName: Object, Decodable {
|
||||
|
||||
@Persisted public var normalized: String?
|
||||
@Persisted public var original: String?
|
||||
}
|
||||
|
||||
@ -1,29 +1,9 @@
|
||||
//
|
||||
// VNote.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.04.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public final class VNote: Object, Decodable {
|
||||
|
||||
public struct VNote: Decodable {
|
||||
|
||||
let id: String?
|
||||
let user: String?
|
||||
let date: Double
|
||||
let text: String?
|
||||
}
|
||||
|
||||
extension CDVNote {
|
||||
|
||||
convenience init(model: VNote?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
|
||||
self.id = model?.id
|
||||
self.user = model?.user
|
||||
self.date = model?.date ?? 0
|
||||
self.text = model?.text
|
||||
}
|
||||
@Persisted var id: String?
|
||||
@Persisted var user: String?
|
||||
@Persisted var date: Double
|
||||
@Persisted var text: String?
|
||||
}
|
||||
|
||||
@ -1,41 +1,15 @@
|
||||
//
|
||||
// VOsago.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.04.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public final class VOsago: Object, Decodable {
|
||||
|
||||
public struct VOsago: Decodable {
|
||||
|
||||
let date: Double
|
||||
let number: String?
|
||||
let vin: String?
|
||||
let plateNumber: String?
|
||||
let name: String?
|
||||
let status: String?
|
||||
let restrictions: String?
|
||||
let insurant: String?
|
||||
let owner: String?
|
||||
let usageRegion: String?
|
||||
}
|
||||
|
||||
extension CDVOsago {
|
||||
|
||||
convenience init(model: VOsago?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
|
||||
self.date = model?.date ?? 0
|
||||
self.number = model?.number
|
||||
self.vin = model?.vin
|
||||
self.plateNumber = model?.plateNumber
|
||||
self.name = model?.name
|
||||
self.status = model?.status
|
||||
self.restrictions = model?.restrictions
|
||||
self.insurant = model?.insurant
|
||||
self.owner = model?.owner
|
||||
self.usageRegion = model?.usageRegion
|
||||
}
|
||||
@Persisted var date: Double
|
||||
@Persisted var number: String?
|
||||
@Persisted var vin: String?
|
||||
@Persisted var plateNumber: String?
|
||||
@Persisted var name: String?
|
||||
@Persisted var status: String?
|
||||
@Persisted var restrictions: String?
|
||||
@Persisted var insurant: String?
|
||||
@Persisted var owner: String?
|
||||
@Persisted var usageRegion: String?
|
||||
}
|
||||
|
||||
@ -1,43 +1,16 @@
|
||||
//
|
||||
// VOwnershipPeriod.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.04.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public final class VOwnershipPeriod: Object, Decodable {
|
||||
|
||||
public struct VOwnershipPeriod: Decodable {
|
||||
|
||||
let lastOperation: String?
|
||||
let ownerType: String?
|
||||
let from: Int64?
|
||||
let to: Int64?
|
||||
let region: String?
|
||||
let registrationRegion: String?
|
||||
let locality: String?
|
||||
let code: String?
|
||||
let street: String?
|
||||
let building: String?
|
||||
let inn: String?
|
||||
}
|
||||
|
||||
extension CDVOwnershipPeriod {
|
||||
|
||||
convenience init(model: VOwnershipPeriod?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
|
||||
self.lastOperation = model?.lastOperation
|
||||
self.ownerType = model?.ownerType
|
||||
self.from = model?.from ?? 0
|
||||
self.to = model?.to ?? 0
|
||||
self.region = model?.region
|
||||
self.registrationRegion = model?.registrationRegion
|
||||
self.locality = model?.locality
|
||||
self.code = model?.locality
|
||||
self.street = model?.street
|
||||
self.building = model?.building
|
||||
self.inn = model?.inn
|
||||
}
|
||||
@Persisted var lastOperation: String?
|
||||
@Persisted var ownerType: String?
|
||||
@Persisted var from: Int64?
|
||||
@Persisted var to: Int64?
|
||||
@Persisted var region: String?
|
||||
@Persisted var registrationRegion: String?
|
||||
@Persisted var locality: String?
|
||||
@Persisted var code: String?
|
||||
@Persisted var street: String?
|
||||
@Persisted var building: String?
|
||||
@Persisted var inn: String?
|
||||
}
|
||||
|
||||
@ -1,29 +1,9 @@
|
||||
//
|
||||
// VPhoto.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.04.2022.
|
||||
//
|
||||
import RealmSwift
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
public final class VPhoto: Object, Decodable {
|
||||
|
||||
public struct VPhoto: Decodable {
|
||||
|
||||
let brand: String?
|
||||
let model: String?
|
||||
let date: Double
|
||||
let url: String?
|
||||
}
|
||||
|
||||
extension CDVPhoto {
|
||||
|
||||
convenience init(model: VPhoto?, context: NSManagedObjectContext) {
|
||||
self.init(context: context)
|
||||
|
||||
self.brand = model?.brand
|
||||
self.model = model?.model
|
||||
self.date = model?.date ?? 0
|
||||
self.url = model?.url
|
||||
}
|
||||
@Persisted var brand: String?
|
||||
@Persisted var model: String?
|
||||
@Persisted var date: Double
|
||||
@Persisted var url: String?
|
||||
}
|
||||
|
||||
@ -1,91 +1,57 @@
|
||||
import Foundation
|
||||
import CoreData
|
||||
import RealmSwift
|
||||
|
||||
protocol StorageServiceProtocol {
|
||||
|
||||
var context: NSManagedObjectContext { get }
|
||||
func store(vehicle: Vehicle) throws -> CDVehicle
|
||||
func store(vehicle: Vehicle) throws
|
||||
}
|
||||
|
||||
public class StorageService: StorageServiceProtocol {
|
||||
|
||||
public enum StorageError: String, LocalizedError {
|
||||
|
||||
case openDatabaseError = "Ошибка открытия БД"
|
||||
|
||||
public var errorDescription: String? {
|
||||
rawValue
|
||||
}
|
||||
}
|
||||
|
||||
private static var instance: StorageService?
|
||||
|
||||
private let container: NSPersistentCloudKitContainer
|
||||
private let realm: Realm
|
||||
|
||||
public static var shared: StorageService {
|
||||
get async throws {
|
||||
get throws {
|
||||
if let instance = StorageService.instance {
|
||||
return instance
|
||||
} else {
|
||||
let service = StorageService(inMemory: Testing.isUITesting)
|
||||
try await service.loadPersistentStores()
|
||||
if let service = StorageService(inMemory: Testing.isUITesting) {
|
||||
StorageService.instance = service
|
||||
return service
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var sharedNotWait: StorageService {
|
||||
if let instance = StorageService.instance {
|
||||
return instance
|
||||
} else {
|
||||
let service = StorageService(inMemory: Testing.isUITesting)
|
||||
Task {
|
||||
try? await service.loadPersistentStores()
|
||||
throw StorageError.openDatabaseError
|
||||
}
|
||||
}
|
||||
StorageService.instance = service
|
||||
return service
|
||||
}
|
||||
}
|
||||
|
||||
init(inMemory: Bool = false) {
|
||||
init?(inMemory: Bool = false) {
|
||||
let config = Realm.Configuration(inMemoryIdentifier: inMemory ? "memory" : nil)
|
||||
|
||||
let bundle = Bundle(for: Self.self)
|
||||
let url = bundle.url(forResource: "AutoCat2", withExtension: "momd")
|
||||
let mom = NSManagedObjectModel(contentsOf: url!)
|
||||
|
||||
container = NSPersistentCloudKitContainer(name: "AutoCat2", managedObjectModel: mom!)
|
||||
if inMemory {
|
||||
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
|
||||
}
|
||||
guard let realm = try? Realm(configuration: config) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func loadPersistentStores() async throws {
|
||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
|
||||
if let error = error as NSError? {
|
||||
continuation.resume(throwing: error)
|
||||
} else {
|
||||
continuation.resume(returning: ())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func save() throws {
|
||||
guard context.hasChanges else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
throw error
|
||||
}
|
||||
self.realm = realm
|
||||
}
|
||||
|
||||
// MARK: - StorageServiceProtocol
|
||||
|
||||
public var context: NSManagedObjectContext {
|
||||
container.viewContext
|
||||
public func store(vehicle: Vehicle) throws {
|
||||
|
||||
try realm.write {
|
||||
realm.add(vehicle, update: .all)
|
||||
}
|
||||
|
||||
public func store(vehicle: Vehicle) throws -> CDVehicle {
|
||||
|
||||
let cdVehicle = CDVehicle(vehicle: vehicle, context: context)
|
||||
try save()
|
||||
return cdVehicle
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import Foundation
|
||||
|
||||
protocol VehicleServiceProtocol {
|
||||
|
||||
func check(plateNumber: String, force: Bool) async throws -> CDVehicle
|
||||
func check(plateNumber: String, force: Bool) async throws -> Vehicle
|
||||
}
|
||||
|
||||
public class VehicleService: VehicleServiceProtocol {
|
||||
@ -11,8 +11,8 @@ public class VehicleService: VehicleServiceProtocol {
|
||||
private let storage: StorageServiceProtocol
|
||||
|
||||
public static var shared: VehicleService {
|
||||
get async throws {
|
||||
let storageService = try await StorageService.shared
|
||||
get throws {
|
||||
let storageService = try StorageService.shared
|
||||
return VehicleService(api: Api.shared, storage: storageService)
|
||||
}
|
||||
}
|
||||
@ -25,11 +25,13 @@ public class VehicleService: VehicleServiceProtocol {
|
||||
|
||||
// MARK: - VehicleServiceProtocol
|
||||
|
||||
@MainActor
|
||||
@discardableResult
|
||||
public func check(plateNumber: String, force: Bool) async throws -> CDVehicle {
|
||||
public func check(plateNumber: String, force: Bool) async throws -> Vehicle {
|
||||
|
||||
let vehicle = try await api.check(plateNumber: plateNumber, force: force)
|
||||
return try storage.store(vehicle: vehicle)
|
||||
try storage.store(vehicle: vehicle)
|
||||
return vehicle
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user