Speeding up local search. Support for regular expressions.

This commit is contained in:
Selim Mustafaev 2023-02-12 01:38:17 +03:00
parent ae99fb98a7
commit 4496149fb7
12 changed files with 195 additions and 137 deletions

View File

@ -12,6 +12,7 @@
7A0420B12561A0E100034941 /* OsagoAddController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0420B02561A0E100034941 /* OsagoAddController.swift */; };
7A0420B62568650C00034941 /* DkbmController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0420B52568650C00034941 /* DkbmController.swift */; };
7A0420BA25693D2C00034941 /* dkbm.js in Resources */ = {isa = PBXBuildFile; fileRef = 7A0420B925693D2C00034941 /* dkbm.js */; };
7A0B663729984201006F5189 /* DateCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0B663629984201006F5189 /* DateCache.swift */; };
7A0B96A0257D6D4B000B39AD /* MultilineLabelRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0B969F257D6D4B000B39AD /* MultilineLabelRow.swift */; };
7A1090E824A394F100B4F0B2 /* AudioRecordCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */; };
7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */; };
@ -80,7 +81,6 @@
7AA7BC3325A5DFB80053A5D5 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D332402A91C00CE01A0 /* Kingfisher */; };
7AA7BC3525A5DFB80053A5D5 /* ExceptionCatcher in Frameworks */ = {isa = PBXBuildFile; productRef = 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */; };
7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; };
7AABB1F0267E9CAA00D7AB32 /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABB1EF267E9CAA00D7AB32 /* DifferenceKit */; settings = {ATTRIBUTES = (Required, ); }; };
7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABB1F1267E9CC800D7AB32 /* SwiftDate */; };
7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */; };
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; };
@ -174,6 +174,7 @@
7A0420B52568650C00034941 /* DkbmController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DkbmController.swift; sourceTree = "<group>"; };
7A0420B925693D2C00034941 /* dkbm.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = dkbm.js; sourceTree = "<group>"; };
7A0516192414FF0900FC55AC /* DateSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateSection.swift; sourceTree = "<group>"; };
7A0B663629984201006F5189 /* DateCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateCache.swift; sourceTree = "<group>"; };
7A0B969F257D6D4B000B39AD /* MultilineLabelRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineLabelRow.swift; sourceTree = "<group>"; };
7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordCell.swift; sourceTree = "<group>"; };
7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
@ -321,7 +322,6 @@
buildActionMask = 2147483647;
files = (
7AA54C1E26CD977A00F2BF28 /* RxSwift in Frameworks */,
7AABB1F0267E9CAA00D7AB32 /* DifferenceKit in Frameworks */,
7AF6D2252677C2B40086EA64 /* RealmSwift in Frameworks */,
7AA54C1C26CD977A00F2BF28 /* RxCocoa in Frameworks */,
7AF6D2232677C2B40086EA64 /* Realm in Frameworks */,
@ -646,6 +646,7 @@
7A11474323FF06CA00B424AF /* Api.swift */,
7A96AE30246B2FE400297C33 /* Constants.swift */,
7A000AA124C2EEDE001F5B00 /* Location.swift */,
7A0B663629984201006F5189 /* DateCache.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -733,7 +734,6 @@
packageProductDependencies = (
7AF6D2222677C2B40086EA64 /* Realm */,
7AF6D2242677C2B40086EA64 /* RealmSwift */,
7AABB1EF267E9CAA00D7AB32 /* DifferenceKit */,
7AABB1F1267E9CC800D7AB32 /* SwiftDate */,
7AA54C1B26CD977A00F2BF28 /* RxCocoa */,
7AA54C1D26CD977A00F2BF28 /* RxSwift */,
@ -783,7 +783,6 @@
7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */,
7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */,
7AABDE1B2532F3EB0041AFC6 /* XCRemoteSwiftPackageReference "PKHUD" */,
7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */,
7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */,
7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */,
);
@ -941,6 +940,7 @@
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
7AF6D2162677C1680086EA64 /* Cloneable.swift in Sources */,
7AF6D21A2677C1680086EA64 /* User.swift in Sources */,
7A0B663729984201006F5189 /* DateCache.swift in Sources */,
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
7AF6D2152677C1680086EA64 /* Settings.swift in Sources */,
7A761C052677F1BC0005F28F /* CocoaError.swift in Sources */,
@ -1140,7 +1140,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 110;
CURRENT_PROJECT_VERSION = 111;
DEVELOPMENT_TEAM = 46DTTB8X4S;
INFOPLIST_FILE = AutoCat/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -1165,7 +1165,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 110;
CURRENT_PROJECT_VERSION = 111;
DEVELOPMENT_TEAM = 46DTTB8X4S;
INFOPLIST_FILE = AutoCat/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -1381,14 +1381,6 @@
minimumVersion = 5.4.0;
};
};
7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ra1028/DifferenceKit.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.1.5;
};
};
7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/huri000/SwiftEntryKit";
@ -1433,11 +1425,6 @@
package = 7A530B89240181F500CBFE6E /* XCRemoteSwiftPackageReference "RxRealm" */;
productName = RxRealm;
};
7AABB1EF267E9CAA00D7AB32 /* DifferenceKit */ = {
isa = XCSwiftPackageProductDependency;
package = 7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */;
productName = DifferenceKit;
};
7AABB1F1267E9CC800D7AB32 /* SwiftDate */ = {
isa = XCSwiftPackageProductDependency;
package = 7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */;

View File

@ -1,14 +1,5 @@
{
"pins" : [
{
"identity" : "differencekit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ra1028/DifferenceKit.git",
"state" : {
"revision" : "14c66681e12a38b81045f44c6c29724a0d4b0e72",
"version" : "1.1.5"
}
},
{
"identity" : "eureka",
"kind" : "remoteSourceControl",

View File

@ -294,18 +294,24 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
// MARK: - UISearchResultsUpdating
func updateSearchResults(for searchController: UISearchController) {
guard let realm = try? Realm() else {
guard let text = searchController.searchBar.text?.uppercased(),
!text.isEmpty
else {
historyDataSource.setSearchPredicate(nil)
return
}
var query = realm.objects(Vehicle.self)
.sorted(byKeyPath: "updatedDate", ascending: false)
let regex = try? NSRegularExpression(pattern: text)
if let text = searchController.searchBar.text?.uppercased(), !text.isEmpty {
query = query.filter("number CONTAINS '\(text)'")
historyDataSource.setSearchPredicate { vehicle in
let number = vehicle.getNumber()
if let regex {
let range = NSRange(location: 0, length: number.utf16.count)
return regex.numberOfMatches(in: number, range: range) > 0
} else {
return number.contains(text)
}
}
historyDataSource.setQuery(query)
}
// MARK: - Contextual actions

View File

@ -10,6 +10,13 @@ class MainTabController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
#if targetEnvironment(macCatalyst)
// Remove "+" tab for macOS version (it will be on the toolbar)
viewControllers?.remove(at: 2)
#endif
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {

View File

@ -1,14 +1,16 @@
import Foundation
import SwiftDate
import DifferenceKit
import AutoCatCore
protocol Dated {
var added: Date { get }
var addedTimestamp: TimeInterval { get }
var updated: Date { get }
var updatedTimestamp: TimeInterval { get }
}
extension Vehicle: Dated {
var updated: Date {
Date(timeIntervalSince1970: self.updatedDate)
}
@ -16,6 +18,14 @@ extension Vehicle: Dated {
var added: Date {
Date(timeIntervalSince1970: self.addedDate)
}
var addedTimestamp: TimeInterval {
addedDate
}
var updatedTimestamp: TimeInterval {
updatedDate
}
}
extension AudioRecord: Dated {
@ -26,38 +36,48 @@ extension AudioRecord: Dated {
var added: Date {
Date(timeIntervalSince1970: self.getAddedDate())
}
var addedTimestamp: TimeInterval {
getAddedDate()
}
var updatedTimestamp: TimeInterval {
getAddedDate()
}
}
extension RandomAccessCollection where Element: Dated & Hashable & Differentiable {
extension RandomAccessCollection where Element: Dated & Identifiable {
func groupedByDate(type: SortParameter = .updatedDate) -> [DateSection<Element>] {
let now = Date()
let monthStart = now.dateAtStartOf(.month)
var sectionsIndices: [TimeInterval: Int] = [:]
var sectionsArray: [DateSection<Element>] = []
for vehicle in self {
let date = self.date(of: type, for: vehicle)
let dateInRegion = DateInRegion(date, region: Region.current)
var key: TimeInterval = 0
var keyNext: TimeInterval = 0
var index = self.index(before: endIndex)
let currentMonthStart = DateCache.shared.monthStart.timeIntervalSince1970
var key = dateInRegion.dateAtStartOf(.day).timeIntervalSince1970
if date.isBeforeDate(monthStart, orEqual: false, granularity: .day) {
key = dateInRegion.dateAtStartOf(.month).timeIntervalSince1970
while index >= startIndex {
let timestamp: TimeInterval
switch type {
case .addedDate: timestamp = self[index].addedTimestamp
case .updatedDate: timestamp = self[index].updatedTimestamp
}
if let index = sectionsIndices[key] {
sectionsArray[index].append(vehicle)
} else {
sectionsArray.append(DateSection<Element>(timestamp: key, items: [vehicle]))
sectionsIndices[key] = sectionsArray.count - 1
if keyNext == 0 || timestamp > keyNext {
let component: Calendar.Component = timestamp >= currentMonthStart ? .day : .month
let dateInRegion = DateInRegion(seconds: timestamp, region: .current)
let startOfPeriod = dateInRegion.dateAtStartOf(component)
key = startOfPeriod.timeIntervalSince1970
sectionsArray.insert(DateSection<Element>(timestamp: key, items: []), at: 0)
keyNext = startOfPeriod.dateByAdding(1, component).timeIntervalSince1970
}
sectionsArray[0].insert(self[index], at: 0)
index = self.index(before: index)
}
return sectionsArray
}
func date(of type: SortParameter, for object: Dated) -> Date {
switch type {
case .addedDate: return object.added
case .updatedDate: return object.updated
}
}
}

View File

@ -39,6 +39,21 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
self.window?.rootViewController = storyboard.instantiateViewController(identifier: "MainSplitController")
}
#if targetEnvironment(macCatalyst)
let toolbar = NSToolbar(identifier: "main")
toolbar.delegate = self
toolbar.displayMode = .iconOnly
if let titlebar = windowScene.titlebar {
titlebar.toolbar = toolbar
if #available(macCatalyst 14.0, *) {
titlebar.toolbarStyle = .automatic
}
}
#endif
self.window?.makeKeyAndVisible()
}
@ -163,3 +178,20 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
}
#if targetEnvironment(macCatalyst)
extension SceneDelegate: NSToolbarDelegate {
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
let identifiers: [NSToolbarItem.Identifier] = [
.toggleSidebar
]
return identifiers
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return toolbarDefaultItemIdentifiers(toolbar)
}
}
#endif

View File

@ -1,13 +1,12 @@
import UIKit
import RealmSwift
import DifferenceKit
import ExceptionCatcher
import AutoCatCore
typealias FilterPredicate<T> = (T) -> Bool
class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
where Item: Object & Identifiable & Dated & Differentiable & Cloneable,
where Item: Object & Identifiable & Dated & Cloneable,
Cell: UITableViewCell & ConfigurableCell,
Cell.Item == Item {
@ -17,8 +16,11 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
private var sections: [DateSection<Item>] = []
private var cellIdentifier: String
private var filterPredicate: FilterPredicate<Item>?
private var searchPredicate: FilterPredicate<Item>?
private let groupQueue = DispatchQueue(label: "group")
private var objects: [Item] = []
private let onSizeChanged: ((Int) -> Void)?
init(table: UITableView, data: Results<Item>, cellIdentifier: String = String(describing: Cell.self), onSizeChanged: ((Int) -> Void)? = nil) {
@ -35,24 +37,30 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
func startObservingChanges() {
self.notificationToken = self.data.observe { changes in
self.onSizeChanged?(self.data.count)
switch changes {
case .initial:
self.objects = self.data.toArray().map { $0.shallowClone() }
self.reload(animated: false)
case .update(_, let deletions, let insertions, let modifications):
if deletions.isEmpty && modifications.isEmpty && insertions.count == 1 && insertions.first == 0 {
// Most common use case - adding new element to the top of the list
// Example - checking new number
self.insertFirst()
} else if deletions.isEmpty && insertions.isEmpty && modifications.count == 1 && modifications.first == 0 {
self.reloadFirst()
} else if modifications.isEmpty && deletions.count == 1 && insertions.count == 1 && insertions.first == 0 {
// Probably moving an item to the first position
// For example - updating number info
self.moveToFirst(deleteIndex: deletions.first!)
} else {
self.reload(animated: false)
}
deletions.forEach { self.objects.remove(at: $0) }
insertions.forEach { self.objects.insert(self.data[$0].shallowClone(), at: $0) }
modifications.forEach { self.objects[$0] = self.data[$0].shallowClone() }
self.reload()
// if deletions.isEmpty && modifications.isEmpty && insertions.count == 1 && insertions.first == 0 {
// // Most common use case - adding new element to the top of the list
// // Example - checking new number
// self.insertFirst()
// } else if deletions.isEmpty && insertions.isEmpty && modifications.count == 1 && modifications.first == 0 {
// self.reloadFirst()
// } else if modifications.isEmpty && deletions.count == 1 && insertions.count == 1 && insertions.first == 0 {
// // Probably moving an item to the first position
// // For example - updating number info
// self.moveToFirst(deleteIndex: deletions.first!)
// } else {
// self.reload(animated: false)
// }
case .error(let err):
print("Realm observer error: \(err)")
}
@ -90,26 +98,23 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
}
func reload(animated: Bool = true) {
var items = self.data.toArray().map { $0.shallowClone() }
if let predicate = self.filterPredicate {
items = items.filter(predicate)
var items = objects
if let filterPredicate = self.filterPredicate {
items = items.filter(filterPredicate)
}
self.groupQueue.async {
let newSections = items.groupedByDate()
// let changeset = StagedChangeset(source: self.sections, target: newSections)
DispatchQueue.main.async {
self.sections = newSections
self.tv.reloadData()
// self.tv.reload(using: changeset, with: animated ? .fade : .none) { newSects in
// self.sections = newSects
// }
}
if let searchPredicate = self.searchPredicate {
items = items.filter(searchPredicate)
}
self.onSizeChanged?(items.count)
self.sections = items.groupedByDate()
self.tv.reloadData()
}
func insertFirst() {
guard let item = data.first?.clone() else {
guard let item = data.first?.shallowClone() else {
reload(animated: false)
return
}
@ -157,10 +162,23 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
}
func setFilterPredicate(_ predicate: FilterPredicate<Item>?) {
guard self.filterPredicate != nil || predicate != nil else {
return
}
self.filterPredicate = predicate
self.reload()
}
func setSearchPredicate(_ predicate: FilterPredicate<Item>?) {
guard self.searchPredicate != nil || predicate != nil else {
return
}
self.searchPredicate = predicate
self.reload()
}
func makeCsv() throws -> String {
guard let type = Item.self as? Exportable.Type else {
throw CocoaError.error("Type \(Item.self) is not exportable")
@ -185,9 +203,4 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
return result
}
func setQuery(_ query: Results<Item>) {
self.data = query
startObservingChanges()
}
}

View File

@ -1,10 +1,9 @@
import UIKit
import DifferenceKit
import RxSwift
import RxCocoa
import AutoCatCore
class RxSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where Item: Dated & Hashable & Differentiable & Decodable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item {
class RxSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where Item: Dated & Decodable & Identifiable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item {
private var tv: UITableView
private var cellIdentifier: String
private var sections: [DateSection<Item>] = []

View File

@ -1,8 +1,7 @@
import Foundation
import RealmSwift
import DifferenceKit
public class AudioRecord: Object, Identifiable, Differentiable, Cloneable {
public class AudioRecord: Object, Identifiable, Cloneable {
@objc public dynamic var path: String = ""
@objc public dynamic var number: String?

View File

@ -1,8 +1,7 @@
import Foundation
import SwiftDate
import DifferenceKit
public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable where T: Differentiable & Hashable {
public struct DateSection<T: Identifiable> {
var timestamp: Double = 0
var dateString: String
@ -12,10 +11,10 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
"\(dateString) (\(elements.count))"
}
public init<C>(source: DateSection<T>, elements: C) where C : Swift.Collection, C.Element == Self.Collection.Element {
public init(source: DateSection<T>, elements: [T]) {
self.timestamp = source.timestamp
self.dateString = source.dateString
self.elements = Array(elements)
self.elements = elements
}
public init(original: DateSection, items: [T]) {
@ -28,9 +27,8 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
formatter.dateStyle = .medium
formatter.timeStyle = .medium
let now = DateInRegion(Date(), region: Region.current)
let monthStart = now.dateAtStartOf(.month)
let weekStart = now.dateAtStartOf(.weekOfMonth)
let monthStart = DateCache.shared.monthStart
let weekStart = DateCache.shared.weekStart
let date = Date(timeIntervalSince1970: timestamp)
let dateInRegion = DateInRegion(date, region: Region.current)
@ -64,7 +62,7 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
}
public func index(of element: T) -> Int? {
elements.firstIndex { $0.differenceIdentifier == element.differenceIdentifier }
elements.firstIndex { $0.id == element.id }
}
public mutating func replace(_ element: T, at index: Int) {
@ -74,25 +72,4 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
public mutating func remove(at index: Int) {
elements.remove(at: index)
}
// MARK: - Differentiable
public var differenceIdentifier: String {
return dateString
}
public func isContentEqual(to source: DateSection<T>) -> Bool {
return self.differenceIdentifier == source.differenceIdentifier
}
// MARK: - Hashable
public static func == (lhs: DateSection<T>, rhs: DateSection<T>) -> Bool {
return lhs.timestamp == rhs.timestamp && lhs.elements == rhs.elements
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.timestamp)
hasher.combine(self.elements)
}
}

View File

@ -1,6 +1,5 @@
import Foundation
import RealmSwift
import DifferenceKit
public class VehicleName: Object, Decodable, Cloneable {
@objc public dynamic var original: String?
@ -160,7 +159,7 @@ public class VehicleOwnershipPeriod: Object, Decodable, Cloneable {
}
}
public final class Vehicle: Object, Decodable, Identifiable, Differentiable, Cloneable, Exportable {
public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportable {
@objc public dynamic var brand: VehicleBrand?
@objc public dynamic var model: VehicleModel?
@objc public dynamic var color: String?
@ -194,14 +193,6 @@ public final class Vehicle: Object, Decodable, Identifiable, Differentiable, Clo
return f
}()
public var differenceIdentifier: String { self.number }
public func isContentEqual(to source: Vehicle) -> Bool {
return self.number == source.number &&
self.addedDate == source.addedDate &&
self.updatedDate == source.updatedDate
}
enum CodingKeys: String, CodingKey {
case brand
case model

View File

@ -0,0 +1,36 @@
//
// DateCache.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 12.02.2023.
// Copyright © 2023 Selim Mustafaev. All rights reserved.
//
import Foundation
import SwiftDate
public class DateCache {
public static let shared = DateCache()
public var monthStart: DateInRegion
public var weekStart: DateInRegion
public init() {
let now = DateInRegion(Date(), region: Region.current)
self.monthStart = now.dateAtStartOf(.month)
self.weekStart = now.dateAtStartOf(.weekOfMonth)
NotificationCenter.default.addObserver(self,
selector: #selector(dayChanged),
name: .NSCalendarDayChanged,
object: nil)
}
@objc func dayChanged(_ notification: Notification) {
let now = DateInRegion(Date(), region: Region.current)
self.monthStart = now.dateAtStartOf(.month)
self.weekStart = now.dateAtStartOf(.weekOfMonth)
}
}