Displaying history

This commit is contained in:
Selim Mustafaev 2022-03-28 20:10:47 +03:00
parent 49046d5619
commit aa7efbe1b0
9 changed files with 324 additions and 47 deletions

View File

@ -18,6 +18,8 @@
6841A8FF53F0AADF96B138C1 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6841AFE790F6FC06838B1E2C /* UIControl.swift */; }; 6841A8FF53F0AADF96B138C1 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6841AFE790F6FC06838B1E2C /* UIControl.swift */; };
6841ABD5E4B126DEF3612BBD /* PNKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6841AB0052E9DB6914901EA3 /* PNKeyboard.swift */; }; 6841ABD5E4B126DEF3612BBD /* PNKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6841AB0052E9DB6914901EA3 /* PNKeyboard.swift */; };
6841AF924E165F1B3A3B5FB5 /* AuthController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6841ABEA0314E3B4E438C311 /* AuthController.swift */; }; 6841AF924E165F1B3A3B5FB5 /* AuthController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6841ABEA0314E3B4E438C311 /* AuthController.swift */; };
7A1D80E027F1F275007BD64F /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7A1D80DF27F1F275007BD64F /* DifferenceKit */; };
7A1D80E627F20FCB007BD64F /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7A1D80E527F20FCB007BD64F /* DifferenceKit */; };
7A24C19727EE212E00049E7F /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7A24C19527EE212E00049E7F /* RoadNumbers.otf */; }; 7A24C19727EE212E00049E7F /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7A24C19527EE212E00049E7F /* RoadNumbers.otf */; };
7A24C19827EE212E00049E7F /* RoadNumbers2.0.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7A24C19627EE212E00049E7F /* RoadNumbers2.0.otf */; }; 7A24C19827EE212E00049E7F /* RoadNumbers2.0.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7A24C19627EE212E00049E7F /* RoadNumbers2.0.otf */; };
7A24C19C27EE25B400049E7F /* PlateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A24C19A27EE25B400049E7F /* PlateView.swift */; }; 7A24C19C27EE25B400049E7F /* PlateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A24C19A27EE25B400049E7F /* PlateView.swift */; };
@ -183,6 +185,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
7A1D80E027F1F275007BD64F /* DifferenceKit in Frameworks */,
7A49F4EC27D4064500AEAAE0 /* AutoCatCore.framework in Frameworks */, 7A49F4EC27D4064500AEAAE0 /* AutoCatCore.framework in Frameworks */,
7A48B26727D9442A004D1A4B /* PKHUD in Frameworks */, 7A48B26727D9442A004D1A4B /* PKHUD in Frameworks */,
7A28283627E74C110049BDBF /* SwiftEntryKit in Frameworks */, 7A28283627E74C110049BDBF /* SwiftEntryKit in Frameworks */,
@ -207,6 +210,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
7A1D80E627F20FCB007BD64F /* DifferenceKit in Frameworks */,
7AE32D6E27F06D2D004EF6E0 /* SwiftDate in Frameworks */, 7AE32D6E27F06D2D004EF6E0 /* SwiftDate in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -477,6 +481,7 @@
packageProductDependencies = ( packageProductDependencies = (
7A48B26627D9442A004D1A4B /* PKHUD */, 7A48B26627D9442A004D1A4B /* PKHUD */,
7A28283527E74C110049BDBF /* SwiftEntryKit */, 7A28283527E74C110049BDBF /* SwiftEntryKit */,
7A1D80DF27F1F275007BD64F /* DifferenceKit */,
); );
productName = AutoCat2; productName = AutoCat2;
productReference = 7A49F49F27D4061900AEAAE0 /* AutoCat2.app */; productReference = 7A49F49F27D4061900AEAAE0 /* AutoCat2.app */;
@ -534,6 +539,7 @@
name = AutoCatCore; name = AutoCatCore;
packageProductDependencies = ( packageProductDependencies = (
7AE32D6D27F06D2D004EF6E0 /* SwiftDate */, 7AE32D6D27F06D2D004EF6E0 /* SwiftDate */,
7A1D80E527F20FCB007BD64F /* DifferenceKit */,
); );
productName = AutoCatCore; productName = AutoCatCore;
productReference = 7A49F4D727D4064500AEAAE0 /* AutoCatCore.framework */; productReference = 7A49F4D727D4064500AEAAE0 /* AutoCatCore.framework */;
@ -601,6 +607,8 @@
7A48B26527D9442A004D1A4B /* XCRemoteSwiftPackageReference "PKHUD" */, 7A48B26527D9442A004D1A4B /* XCRemoteSwiftPackageReference "PKHUD" */,
7A28283427E74C110049BDBF /* XCRemoteSwiftPackageReference "SwiftEntryKit" */, 7A28283427E74C110049BDBF /* XCRemoteSwiftPackageReference "SwiftEntryKit" */,
7AE32D6C27F06D2D004EF6E0 /* XCRemoteSwiftPackageReference "SwiftDate" */, 7AE32D6C27F06D2D004EF6E0 /* XCRemoteSwiftPackageReference "SwiftDate" */,
7A1D80DE27F1F275007BD64F /* XCRemoteSwiftPackageReference "DifferenceKit" */,
7A1D80E427F20FCB007BD64F /* XCRemoteSwiftPackageReference "DifferenceKit" */,
); );
productRefGroup = 7A49F4A027D4061900AEAAE0 /* Products */; productRefGroup = 7A49F4A027D4061900AEAAE0 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -1199,6 +1207,22 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */
7A1D80DE27F1F275007BD64F /* XCRemoteSwiftPackageReference "DifferenceKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ra1028/DifferenceKit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
};
};
7A1D80E427F20FCB007BD64F /* XCRemoteSwiftPackageReference "DifferenceKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ra1028/DifferenceKit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
};
};
7A28283427E74C110049BDBF /* XCRemoteSwiftPackageReference "SwiftEntryKit" */ = { 7A28283427E74C110049BDBF /* XCRemoteSwiftPackageReference "SwiftEntryKit" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/huri000/SwiftEntryKit"; repositoryURL = "https://github.com/huri000/SwiftEntryKit";
@ -1226,6 +1250,16 @@
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
7A1D80DF27F1F275007BD64F /* DifferenceKit */ = {
isa = XCSwiftPackageProductDependency;
package = 7A1D80DE27F1F275007BD64F /* XCRemoteSwiftPackageReference "DifferenceKit" */;
productName = DifferenceKit;
};
7A1D80E527F20FCB007BD64F /* DifferenceKit */ = {
isa = XCSwiftPackageProductDependency;
package = 7A1D80DE27F1F275007BD64F /* XCRemoteSwiftPackageReference "DifferenceKit" */;
productName = DifferenceKit;
};
7A28283527E74C110049BDBF /* SwiftEntryKit */ = { 7A28283527E74C110049BDBF /* SwiftEntryKit */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 7A28283427E74C110049BDBF /* XCRemoteSwiftPackageReference "SwiftEntryKit" */; package = 7A28283427E74C110049BDBF /* XCRemoteSwiftPackageReference "SwiftEntryKit" */;

View File

@ -1,5 +1,14 @@
{ {
"pins" : [ "pins" : [
{
"identity" : "differencekit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ra1028/DifferenceKit",
"state" : {
"revision" : "62745d7780deef4a023a792a1f8f763ec7bf9705",
"version" : "1.2.0"
}
},
{ {
"identity" : "pkhud", "identity" : "pkhud",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@ -16,6 +16,27 @@
</dict> </dict>
<key>AutoCatCore.xcscheme_^#shared#^_</key> <key>AutoCatCore.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>DifferenceKit (Playground) 1.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>6</integer>
</dict>
<key>DifferenceKit (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>7</integer>
</dict>
<key>DifferenceKit (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key> <key>orderHint</key>
<integer>1</integer> <integer>1</integer>
</dict> </dict>
@ -24,21 +45,21 @@
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key> <key>orderHint</key>
<integer>3</integer> <integer>4</integer>
</dict> </dict>
<key>SwiftDate (Playground) 2.xcscheme</key> <key>SwiftDate (Playground) 2.xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key> <key>orderHint</key>
<integer>4</integer> <integer>5</integer>
</dict> </dict>
<key>SwiftDate (Playground).xcscheme</key> <key>SwiftDate (Playground).xcscheme</key>
<dict> <dict>
<key>isShown</key> <key>isShown</key>
<false/> <false/>
<key>orderHint</key> <key>orderHint</key>
<integer>2</integer> <integer>3</integer>
</dict> </dict>
</dict> </dict>
</dict> </dict>

View File

@ -10,9 +10,60 @@ import AutoCatCore
class VehicleCell: UITableViewCell { class VehicleCell: UITableViewCell {
private let numberLabel = UILabel() private let nameLabel = UILabel()
.disableTranslatesAutoresizingMaskIntoConstraints() .disableTranslatesAutoresizingMaskIntoConstraints()
private lazy var nameStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [nameLabel])
return stack
}()
private let plateView: PlateView = {
let view = PlateView(frame: .zero)
view.fontSize = 48
view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return view
}()
private let addedDateLabel: UILabel = {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .footnote)
label.textColor = .tertiaryLabel
return label
}()
private let updatedDateLabel: UILabel = {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .footnote)
label.textColor = .secondaryLabel
return label
}()
private lazy var dateStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [updatedDateLabel, addedDateLabel])
stack.axis = .vertical
stack.spacing = 8
stack.alignment = .trailing
return stack
}()
private lazy var numberStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [plateView, dateStackView])
stack.spacing = 8
stack.alignment = .bottom
return stack
}()
private lazy var mainStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [nameStackView, numberStackView])
stack.axis = .vertical
stack.spacing = 8
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
let formatter = DateFormatter()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier) super.init(style: style, reuseIdentifier: reuseIdentifier)
setup() setup()
@ -23,15 +74,40 @@ class VehicleCell: UITableViewCell {
} }
func setup() { func setup() {
contentView.addSubview(numberLabel) contentView.addSubview(mainStackView)
numberLabel.pin(to: contentView, insets: .init(all: 8)) mainStackView.pin(to: contentView, insets: .init(vertical: 8, horizontal: 16))
formatter.dateStyle = .short
formatter.timeStyle = .short
} }
} }
extension VehicleCell: ConfigurableCell { extension VehicleCell: ConfigurableCell {
func configure(with item: CDVehicle) { func configure(with vehicle: CDVehicle) {
guard let number = vehicle.number else {
return
}
plateView.number = PlateNumber(number)
nameLabel.text = vehicle.brand?.name?.original ?? "<Unknown>"
if vehicle.unrecognized {
plateView.foreground = .systemRed
} else if vehicle.outdated {
plateView.foreground = .systemGray3
} else {
plateView.foreground = nil
}
if vehicle.updatedDate - vehicle.addedDate > 60 {
addedDateLabel.text = formatter.string(from: Date(timeIntervalSince1970: vehicle.addedDate))
updatedDateLabel.text = formatter.string(from: Date(timeIntervalSince1970: vehicle.updatedDate))
addedDateLabel.isHidden = false
} else {
addedDateLabel.isHidden = true
updatedDateLabel.text = formatter.string(from: Date(timeIntervalSince1970: vehicle.updatedDate))
}
} }
} }

View File

@ -8,6 +8,7 @@
import UIKit import UIKit
import CoreData import CoreData
import AutoCatCore import AutoCatCore
import DifferenceKit
protocol ConfigurableCell { protocol ConfigurableCell {
@ -18,28 +19,29 @@ protocol ConfigurableCell {
typealias ConfigurableTableViewCell = UITableViewCell & ConfigurableCell typealias ConfigurableTableViewCell = UITableViewCell & ConfigurableCell
class CoreDataSource<Item: NSManagedObject, Cell: ConfigurableTableViewCell>: NSObject, NSFetchedResultsControllerDelegate where Cell.Item == Item { class CoreDataSource<Item: NSManagedObject & Dated & Differentiable, Cell: ConfigurableTableViewCell>
: NSObject, NSFetchedResultsControllerDelegate, UITableViewDataSource where Cell.Item == Item {
private let cellIdentifier: String
private let tableView: UITableView private let tableView: UITableView
private let dataSource: UITableViewDiffableDataSource<DateSection<Item>, Item> //private let dataSource: UITableViewDiffableDataSource<DateSection<Item>, Item>
private let fetchedResults: NSFetchedResultsController<Item> private let fetchedResults: NSFetchedResultsController<Item>
private var sections: [DateSection<Item>] = []
init(tableView: UITableView, context: NSManagedObjectContext) { init(tableView: UITableView, context: NSManagedObjectContext, cellIdentifier: String = String(describing: Cell.self)) {
let cellIdentifier = String(describing: Cell.self)
self.tableView = tableView self.tableView = tableView
self.cellIdentifier = cellIdentifier
self.dataSource = UITableViewDiffableDataSource(tableView: tableView) { tv, ip, item in // self.dataSource = UITableViewDiffableDataSource(tableView: tableView) { tv, ip, item in
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, // let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier,
for: ip) as? Cell // for: ip) as? Cell
cell?.configure(with: item) // cell?.configure(with: item)
return cell // return cell
} // }
if let fetchRequest = Item.fetchRequest() as? NSFetchRequest<Item> { if let fetchRequest = Item.fetchRequest() as? NSFetchRequest<Item> {
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "number", ascending: true)] fetchRequest.sortDescriptors = []
let fr = NSFetchRequest<Item>(entityName: "Vehicle") self.fetchedResults = NSFetchedResultsController(fetchRequest: fetchRequest,
fr.sortDescriptors = []
self.fetchedResults = NSFetchedResultsController(fetchRequest: fr,
managedObjectContext: context, managedObjectContext: context,
sectionNameKeyPath: nil, sectionNameKeyPath: nil,
cacheName: nil) cacheName: nil)
@ -49,16 +51,74 @@ class CoreDataSource<Item: NSManagedObject, Cell: ConfigurableTableViewCell>: NS
super.init() super.init()
self.tableView.dataSource = self.dataSource self.tableView.dataSource = self //self.dataSource
self.tableView.reloadData()
self.fetchedResults.delegate = self self.fetchedResults.delegate = self
try? self.fetchedResults.performFetch() try? self.fetchedResults.performFetch()
reload()
}
func reload() {
let items: [Item] = fetchedResults.fetchedObjects ?? []
DispatchQueue.global().async {
let newSections = items.groupedByDate()
let changeset = StagedChangeset(source: self.sections, target: newSections)
DispatchQueue.main.async {
print("reloading tableView")
self.tableView.reload(using: changeset, with: .fade) { newSects in
print("updating sections")
self.sections = newSects
}
}
}
} }
// MARK: - NSFetchedResultsControllerDelegate // MARK: - NSFetchedResultsControllerDelegate
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { // func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
//
// let snapshotBridged = snapshot as NSDiffableDataSourceSnapshot<DateSection<Item>, Item>
// dataSource.apply(snapshotBridged)
// }
let snapshotBridged = snapshot as NSDiffableDataSourceSnapshot<DateSection<Item>, Item> func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
dataSource.apply(snapshotBridged) }
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
reload()
}
// MARK: - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int {
return self.sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sections[section].elements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? Cell else {
return UITableViewCell()
}
let item = self.sections[indexPath.section].elements[indexPath.row]
cell.configure(with: item)
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sections[section].header
} }
} }

View File

@ -1,13 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21A559" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21E230" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="VBrand" representedClassName=".CDVBrand" syncable="YES" codeGenerationType="class"> <entity name="VBrand" representedClassName=".CDVBrand" syncable="YES" codeGenerationType="class">
<attribute name="logo" optional="YES" attributeType="String"/> <attribute name="logo" optional="YES" attributeType="String"/>
<relationship name="name" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="VName" inverseName="brand" inverseEntity="VName"/> <relationship name="name" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="VName" inverseName="brand" inverseEntity="VName"/>
<relationship name="vehicle" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Vehicle" inverseName="brand" inverseEntity="Vehicle"/> <relationship name="vehicle" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Vehicle" inverseName="brand" inverseEntity="Vehicle"/>
</entity> </entity>
<entity name="Vehicle" representedClassName=".CDVehicle" syncable="YES" codeGenerationType="class"> <entity name="Vehicle" representedClassName=".CDVehicle" syncable="YES" codeGenerationType="class">
<attribute name="addedDate" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="currentNumber" optional="YES" attributeType="String"/> <attribute name="currentNumber" optional="YES" attributeType="String"/>
<attribute name="number" optional="YES" attributeType="String"/> <attribute name="number" optional="YES" attributeType="String"/>
<attribute name="updatedDate" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="brand" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="VBrand" inverseName="vehicle" inverseEntity="VBrand"/> <relationship name="brand" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="VBrand" inverseName="vehicle" inverseEntity="VBrand"/>
</entity> </entity>
<entity name="VName" representedClassName=".CDVName" syncable="YES" codeGenerationType="class"> <entity name="VName" representedClassName=".CDVName" syncable="YES" codeGenerationType="class">
@ -17,7 +19,7 @@
</entity> </entity>
<elements> <elements>
<element name="VBrand" positionX="118.7528686523438" positionY="-209.1094360351562" width="128" height="74"/> <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="74"/> <element name="Vehicle" positionX="-145.9427490234375" positionY="-363.4805297851562" width="128" height="104"/>
<element name="VName" positionX="-54" positionY="9" width="128" height="74"/> <element name="VName" positionX="-54" positionY="9" width="128" height="74"/>
</elements> </elements>
</model> </model>

View File

@ -7,12 +7,18 @@
import Foundation import Foundation
import SwiftDate import SwiftDate
import DifferenceKit
public class DateSection<T: Hashable> { public protocol Dated {
var addedAt: Date { get }
var updatedAt: Date { get }
}
public class DateSection<T: Hashable & Differentiable>: DifferentiableSection {
private var timestamp: Double = 0 private var timestamp: Double = 0
private var header: String public private(set) var header: String
private var elements: [T] public private(set) var elements: [T]
public init(timestamp: Double, items: [T]) { public init(timestamp: Double, items: [T]) {
let formatter = DateFormatter() let formatter = DateFormatter()
@ -45,6 +51,26 @@ public class DateSection<T: Hashable> {
self.timestamp = timestamp self.timestamp = timestamp
self.elements = items self.elements = items
} }
public func append(_ element: T) {
self.elements.append(element)
}
// MARK: - DifferentiableSection
public required init<C>(source: DateSection<T>, elements: C) where C : Collection, C.Element == T {
self.timestamp = source.timestamp
self.header = source.header
self.elements = Array(elements)
}
public var differenceIdentifier: String {
return header
}
public func isContentEqual(to source: DateSection<T>) -> Bool {
return differenceIdentifier == source.differenceIdentifier
}
} }
extension DateSection: Hashable { extension DateSection: Hashable {
@ -58,3 +84,31 @@ extension DateSection: Hashable {
hasher.combine(self.elements) hasher.combine(self.elements)
} }
} }
extension RandomAccessCollection where Element: Dated & Hashable & Differentiable {
public func groupedByDate() -> [DateSection<Element>] {
let now = Date()
let monthStart = now.dateAtStartOf(.month)
var sectionsIndices: [TimeInterval: Int] = [:]
var sectionsArray: [DateSection<Element>] = []
for item in self {
let date = item.updatedAt
let dateInRegion = DateInRegion(date, region: Region.current)
var key = dateInRegion.dateAtStartOf(.day).timeIntervalSince1970
if date.isBeforeDate(monthStart, orEqual: false, granularity: .day) {
key = dateInRegion.dateAtStartOf(.month).timeIntervalSince1970
}
if let index = sectionsIndices[key] {
sectionsArray[index].append(item)
} else {
sectionsArray.append(DateSection<Element>(timestamp: key, items: [item]))
sectionsIndices[key] = sectionsArray.count - 1
}
}
return sectionsArray
}
}

View File

@ -1,23 +1,35 @@
import Foundation import Foundation
import CoreData import CoreData
import DifferenceKit
public struct Vehicle: Decodable { public struct Vehicle: Decodable {
let number: String public let number: String
let currentNumber: String? public let currentNumber: String?
let brand: VBrand? public let brand: VBrand?
public let addedDate: TimeInterval
public let updatedDate: TimeInterval
}
// TODO: Remove code duplication extension CDVehicle: Dated {
public var unrecognized: Bool {
return self.brand == nil public var updatedAt: Date {
Date(timeIntervalSince1970: updatedDate)
} }
public var outdated: Bool { public var addedAt: Date {
if let current = self.currentNumber { Date(timeIntervalSince1970: addedDate)
return current != self.number }
} else { }
return false
} 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
} }
} }
@ -28,6 +40,8 @@ extension CDVehicle {
self.init(context: context) self.init(context: context)
self.number = vehicle.number self.number = vehicle.number
self.currentNumber = vehicle.currentNumber self.currentNumber = vehicle.currentNumber
self.addedDate = vehicle.addedDate/1000
self.updatedDate = vehicle.updatedDate/1000
if let vbrand = vehicle.brand { if let vbrand = vehicle.brand {
self.brand = CDVBrand(vbrand: vbrand, context: context) self.brand = CDVBrand(vbrand: vbrand, context: context)

View File

@ -9,14 +9,21 @@ protocol StorageServiceProtocol {
public class StorageService: StorageServiceProtocol { public class StorageService: StorageServiceProtocol {
private static var instance: StorageService?
private let container: NSPersistentCloudKitContainer private let container: NSPersistentCloudKitContainer
public static var shared: StorageService { public static var shared: StorageService {
get async throws { get async throws {
print("!!!!!!!!!!!!!!!!!!!!!!!!! StorageService init") if let instance = StorageService.instance {
let service = StorageService() return instance
try await service.loadPersistentStores() } else {
return service print("!!!!!!!!!!!!!!!!!!!!!!!!! StorageService init")
let service = StorageService()
try await service.loadPersistentStores()
StorageService.instance = service
return service
}
} }
} }