Displaying history
This commit is contained in:
parent
49046d5619
commit
aa7efbe1b0
@ -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" */;
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
// TODO: Remove code duplication
|
public let updatedDate: TimeInterval
|
||||||
public var unrecognized: Bool {
|
|
||||||
return self.brand == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public var outdated: Bool {
|
extension CDVehicle: Dated {
|
||||||
if let current = self.currentNumber {
|
|
||||||
return current != self.number
|
public var updatedAt: Date {
|
||||||
} else {
|
Date(timeIntervalSince1970: updatedDate)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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)
|
||||||
|
|||||||
@ -9,16 +9,23 @@ 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 {
|
||||||
|
if let instance = StorageService.instance {
|
||||||
|
return instance
|
||||||
|
} else {
|
||||||
print("!!!!!!!!!!!!!!!!!!!!!!!!! StorageService init")
|
print("!!!!!!!!!!!!!!!!!!!!!!!!! StorageService init")
|
||||||
let service = StorageService()
|
let service = StorageService()
|
||||||
try await service.loadPersistentStores()
|
try await service.loadPersistentStores()
|
||||||
|
StorageService.instance = service
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(inMemory: Bool = false) {
|
init(inMemory: Bool = false) {
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user