diff --git a/AutoCat2.xcodeproj/project.pbxproj b/AutoCat2.xcodeproj/project.pbxproj index 9734037..d3612e7 100644 --- a/AutoCat2.xcodeproj/project.pbxproj +++ b/AutoCat2.xcodeproj/project.pbxproj @@ -59,6 +59,11 @@ 7A49F51227D406CB00AEAAE0 /* VName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F50B27D406CB00AEAAE0 /* VName.swift */; }; 7A49F51527D40C6100AEAAE0 /* AutoCat2.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 7A49F51327D40C6100AEAAE0 /* AutoCat2.xcdatamodeld */; }; 7A9F2AC327E71531006492A9 /* ACTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9F2AC227E71531006492A9 /* ACTabBarController.swift */; }; + 7AE32D6427F05F89004EF6E0 /* VehicleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE32D6327F05F89004EF6E0 /* VehicleCell.swift */; }; + 7AE32D6627F063A1004EF6E0 /* UIEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE32D6527F063A1004EF6E0 /* UIEdgeInsets.swift */; }; + 7AE32D6927F06536004EF6E0 /* CoreDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE32D6827F06536004EF6E0 /* CoreDataSource.swift */; }; + 7AE32D6E27F06D2D004EF6E0 /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 7AE32D6D27F06D2D004EF6E0 /* SwiftDate */; }; + 7AE32D7127F06DA4004EF6E0 /* DateSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE32D7027F06DA4004EF6E0 /* DateSection.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -167,6 +172,10 @@ 7A49F50B27D406CB00AEAAE0 /* VName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VName.swift; sourceTree = ""; }; 7A49F51427D40C6100AEAAE0 /* Shared.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Shared.xcdatamodel; sourceTree = ""; }; 7A9F2AC227E71531006492A9 /* ACTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACTabBarController.swift; sourceTree = ""; }; + 7AE32D6327F05F89004EF6E0 /* VehicleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleCell.swift; sourceTree = ""; }; + 7AE32D6527F063A1004EF6E0 /* UIEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEdgeInsets.swift; sourceTree = ""; }; + 7AE32D6827F06536004EF6E0 /* CoreDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataSource.swift; sourceTree = ""; }; + 7AE32D7027F06DA4004EF6E0 /* DateSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateSection.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -198,6 +207,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7AE32D6E27F06D2D004EF6E0 /* SwiftDate in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -220,6 +230,7 @@ 6841A709FA6B606978425A26 /* UIViewController.swift */, 7A28283027E721A70049BDBF /* UIView.swift */, 7A28283227E7263B0049BDBF /* UIStackView.swift */, + 7AE32D6527F063A1004EF6E0 /* UIEdgeInsets.swift */, ); path = Extensions; sourceTree = ""; @@ -248,6 +259,7 @@ 6841AEAC436660C91E968C9F /* Components */ = { isa = PBXGroup; children = ( + 7AE32D6727F06512004EF6E0 /* TableView */, 7A24C19927EE259000049E7F /* PlateView */, 7A28282D27E720830049BDBF /* ACTabBar */, 6841A0EB55A59DAC94129594 /* Extensions */, @@ -312,6 +324,7 @@ 7A49F4A127D4061900AEAAE0 /* AutoCat2 */ = { isa = PBXGroup; children = ( + 7AE32D6227F05F5D004EF6E0 /* Cells */, 7A24C19427EE212E00049E7F /* Fonts */, 7A49F4A227D4061900AEAAE0 /* AppDelegate.swift */, 7A49F4A427D4061900AEAAE0 /* SceneDelegate.swift */, @@ -347,6 +360,7 @@ 7A49F4D827D4064500AEAAE0 /* AutoCatCore */ = { isa = PBXGroup; children = ( + 7AE32D6F27F06D87004EF6E0 /* DataSource */, 7A49F51327D40C6100AEAAE0 /* AutoCat2.xcdatamodeld */, 7A49F50427D406CB00AEAAE0 /* Models */, 7A49F4FF27D406C300AEAAE0 /* Services */, @@ -407,6 +421,30 @@ path = Models; sourceTree = ""; }; + 7AE32D6227F05F5D004EF6E0 /* Cells */ = { + isa = PBXGroup; + children = ( + 7AE32D6327F05F89004EF6E0 /* VehicleCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 7AE32D6727F06512004EF6E0 /* TableView */ = { + isa = PBXGroup; + children = ( + 7AE32D6827F06536004EF6E0 /* CoreDataSource.swift */, + ); + path = TableView; + sourceTree = ""; + }; + 7AE32D6F27F06D87004EF6E0 /* DataSource */ = { + isa = PBXGroup; + children = ( + 7AE32D7027F06DA4004EF6E0 /* DateSection.swift */, + ); + path = DataSource; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -494,6 +532,9 @@ dependencies = ( ); name = AutoCatCore; + packageProductDependencies = ( + 7AE32D6D27F06D2D004EF6E0 /* SwiftDate */, + ); productName = AutoCatCore; productReference = 7A49F4D727D4064500AEAAE0 /* AutoCatCore.framework */; productType = "com.apple.product-type.framework"; @@ -559,6 +600,7 @@ packageReferences = ( 7A48B26527D9442A004D1A4B /* XCRemoteSwiftPackageReference "PKHUD" */, 7A28283427E74C110049BDBF /* XCRemoteSwiftPackageReference "SwiftEntryKit" */, + 7AE32D6C27F06D2D004EF6E0 /* XCRemoteSwiftPackageReference "SwiftDate" */, ); productRefGroup = 7A49F4A027D4061900AEAAE0 /* Products */; projectDirPath = ""; @@ -626,6 +668,8 @@ 7A49F4A727D4061900AEAAE0 /* ViewController.swift in Sources */, 7A49F4A327D4061900AEAAE0 /* AppDelegate.swift in Sources */, 7A49F4A527D4061900AEAAE0 /* SceneDelegate.swift in Sources */, + 7AE32D6427F05F89004EF6E0 /* VehicleCell.swift in Sources */, + 7AE32D6927F06536004EF6E0 /* CoreDataSource.swift in Sources */, 6841AF924E165F1B3A3B5FB5 /* AuthController.swift in Sources */, 6841A592745CDD869709EFA7 /* MainTabController.swift in Sources */, 6841A8FF53F0AADF96B138C1 /* UIControl.swift in Sources */, @@ -634,6 +678,7 @@ 6841A80BB89BFD8D18E79680 /* UITextField.swift in Sources */, 7A24C19F27EE26B900049E7F /* CenterTextLayer.swift in Sources */, 6841A5FE3E4927BF0EFF19F5 /* UIViewController.swift in Sources */, + 7AE32D6627F063A1004EF6E0 /* UIEdgeInsets.swift in Sources */, 7A28282F27E720AC0049BDBF /* ACTabBarButton.swift in Sources */, 6841A0EE0ECCDB3519F728E6 /* HistoryController.swift in Sources */, 7A28283127E721A70049BDBF /* UIView.swift in Sources */, @@ -682,6 +727,7 @@ 7A49F51527D40C6100AEAAE0 /* AutoCat2.xcdatamodeld in Sources */, 7A49F50E27D406CB00AEAAE0 /* User.swift in Sources */, 7A49F4FA27D406B200AEAAE0 /* ApiError.swift in Sources */, + 7AE32D7127F06DA4004EF6E0 /* DateSection.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1169,6 +1215,14 @@ minimumVersion = 5.0.0; }; }; + 7AE32D6C27F06D2D004EF6E0 /* XCRemoteSwiftPackageReference "SwiftDate" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/malcommac/SwiftDate"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 6.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1182,6 +1236,11 @@ package = 7A48B26527D9442A004D1A4B /* XCRemoteSwiftPackageReference "PKHUD" */; productName = PKHUD; }; + 7AE32D6D27F06D2D004EF6E0 /* SwiftDate */ = { + isa = XCSwiftPackageProductDependency; + package = 7AE32D6C27F06D2D004EF6E0 /* XCRemoteSwiftPackageReference "SwiftDate" */; + productName = SwiftDate; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/AutoCat2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AutoCat2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d0c7372..b4b79ad 100644 --- a/AutoCat2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AutoCat2.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,15 @@ "version" : "5.4.0" } }, + { + "identity" : "swiftdate", + "kind" : "remoteSourceControl", + "location" : "https://github.com/malcommac/SwiftDate", + "state" : { + "revision" : "6190d0cefff3013e77ed567e6b074f324e5c5bf5", + "version" : "6.3.1" + } + }, { "identity" : "swiftentrykit", "kind" : "remoteSourceControl", diff --git a/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist b/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist index e2ffec3..154e6de 100644 --- a/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/AutoCat2.xcodeproj/xcuserdata/selim.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,12 +12,33 @@ AutoCat2.xcscheme_^#shared#^_ orderHint - 1 + 0 AutoCatCore.xcscheme_^#shared#^_ orderHint - 0 + 1 + + SwiftDate (Playground) 1.xcscheme + + isShown + + orderHint + 3 + + SwiftDate (Playground) 2.xcscheme + + isShown + + orderHint + 4 + + SwiftDate (Playground).xcscheme + + isShown + + orderHint + 2 diff --git a/AutoCat2/Cells/VehicleCell.swift b/AutoCat2/Cells/VehicleCell.swift new file mode 100644 index 0000000..2406d70 --- /dev/null +++ b/AutoCat2/Cells/VehicleCell.swift @@ -0,0 +1,37 @@ +// +// VehicleCell.swift +// AutoCat2 +// +// Created by Selim Mustafaev on 27.03.2022. +// + +import UIKit +import AutoCatCore + +class VehicleCell: UITableViewCell { + + private let numberLabel = UILabel() + .disableTranslatesAutoresizingMaskIntoConstraints() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setup() { + contentView.addSubview(numberLabel) + numberLabel.pin(to: contentView, insets: .init(all: 8)) + } + +} + +extension VehicleCell: ConfigurableCell { + + func configure(with item: CDVehicle) { + + } +} diff --git a/AutoCat2/Components/Extensions/UIEdgeInsets.swift b/AutoCat2/Components/Extensions/UIEdgeInsets.swift new file mode 100644 index 0000000..42498ab --- /dev/null +++ b/AutoCat2/Components/Extensions/UIEdgeInsets.swift @@ -0,0 +1,20 @@ +// +// UIEdgeInsets.swift +// AutoCat2 +// +// Created by Selim Mustafaev on 27.03.2022. +// + +import UIKit + +extension UIEdgeInsets { + + init(all: CGFloat) { + self.init(top: all, left: all, bottom: all, right: all) + } + + init(vertical: CGFloat = 0, horizontal: CGFloat = 0) { + self.init(top: vertical, left: horizontal, bottom: vertical, right: horizontal) + } + +} diff --git a/AutoCat2/Components/TableView/CoreDataSource.swift b/AutoCat2/Components/TableView/CoreDataSource.swift new file mode 100644 index 0000000..cfb3538 --- /dev/null +++ b/AutoCat2/Components/TableView/CoreDataSource.swift @@ -0,0 +1,64 @@ +// +// CoreDataSource.swift +// AutoCat2 +// +// Created by Selim Mustafaev on 27.03.2022. +// + +import UIKit +import CoreData +import AutoCatCore + +protocol ConfigurableCell { + + associatedtype Item + + func configure(with item: Item) +} + +typealias ConfigurableTableViewCell = UITableViewCell & ConfigurableCell + +class CoreDataSource: NSObject, NSFetchedResultsControllerDelegate where Cell.Item == Item { + + private let tableView: UITableView + private let dataSource: UITableViewDiffableDataSource, Item> + private let fetchedResults: NSFetchedResultsController + + init(tableView: UITableView, context: NSManagedObjectContext) { + let cellIdentifier = String(describing: Cell.self) + self.tableView = tableView + + self.dataSource = UITableViewDiffableDataSource(tableView: tableView) { tv, ip, item in + let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, + for: ip) as? Cell + cell?.configure(with: item) + return cell + } + + if let fetchRequest = Item.fetchRequest() as? NSFetchRequest { + fetchRequest.sortDescriptors = [NSSortDescriptor(key: "number", ascending: true)] + let fr = NSFetchRequest(entityName: "Vehicle") + fr.sortDescriptors = [] + self.fetchedResults = NSFetchedResultsController(fetchRequest: fr, + managedObjectContext: context, + sectionNameKeyPath: nil, + cacheName: nil) + } else { + self.fetchedResults = NSFetchedResultsController() + } + + super.init() + + self.tableView.dataSource = self.dataSource + self.fetchedResults.delegate = self + try? self.fetchedResults.performFetch() + } + + // MARK: - NSFetchedResultsControllerDelegate + + func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { + + let snapshotBridged = snapshot as NSDiffableDataSourceSnapshot, Item> + dataSource.apply(snapshotBridged) + } +} diff --git a/AutoCat2/Controllers/HistoryController.swift b/AutoCat2/Controllers/HistoryController.swift index f45bc9e..71e9fc1 100644 --- a/AutoCat2/Controllers/HistoryController.swift +++ b/AutoCat2/Controllers/HistoryController.swift @@ -11,9 +11,13 @@ class HistoryController: UIViewController { private lazy var tableView: UITableView = { let table = UITableView() + let cellIdentifier = String(describing: VehicleCell.self) + table.register(VehicleCell.self, forCellReuseIdentifier: cellIdentifier) table.translatesAutoresizingMaskIntoConstraints = false return table }() + + private var dataSource: CoreDataSource? override func viewDidLoad() { super.viewDidLoad() @@ -22,6 +26,19 @@ class HistoryController: UIViewController { view.addSubview(tableView) tableView.pin(to: view) + + showHistory() + } + + func showHistory() { + Task { + do { + let storageService = try await StorageService.shared + dataSource = CoreDataSource(tableView: tableView, context: storageService.context) + } catch { + show(error: error) + } + } } func checkPlateNumber(_ number: String) async { diff --git a/AutoCatCore/DataSource/DateSection.swift b/AutoCatCore/DataSource/DateSection.swift new file mode 100644 index 0000000..3becccf --- /dev/null +++ b/AutoCatCore/DataSource/DateSection.swift @@ -0,0 +1,60 @@ +// +// DateSection.swift +// AutoCatCore +// +// Created by Selim Mustafaev on 27.03.2022. +// + +import Foundation +import SwiftDate + +public class DateSection { + + private var timestamp: Double = 0 + private var header: String + private var elements: [T] + + public init(timestamp: Double, items: [T]) { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .medium + + let now = DateInRegion(Date(), region: Region.current) + let monthStart = now.dateAtStartOf(.month) + let weekStart = now.dateAtStartOf(.weekOfMonth) + + let date = Date(timeIntervalSince1970: timestamp) + let dateInRegion = DateInRegion(date, region: Region.current) + if dateInRegion.isToday { + self.header = NSLocalizedString("Today", comment: "") + } + else if dateInRegion.isYesterday { + self.header = NSLocalizedString("Yesterday", comment: "") + } else if dateInRegion.isAfterDate(weekStart, granularity: .day) { + formatter.dateFormat = "EEEE" + self.header = formatter.string(from: date) + } else if dateInRegion.isAfterDate(monthStart, orEqual: false, granularity: .day) { + formatter.dateStyle = .medium + formatter.timeStyle = .none + self.header = formatter.string(from: date) + } else { + formatter.dateFormat = "LLLL yyyy" + self.header = formatter.string(from: date) + } + + self.timestamp = timestamp + self.elements = items + } +} + +extension DateSection: Hashable { + + public static func == (lhs: DateSection, rhs: DateSection) -> 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) + } +} diff --git a/AutoCatCore/Services/StorageService.swift b/AutoCatCore/Services/StorageService.swift index d7b2740..175f250 100644 --- a/AutoCatCore/Services/StorageService.swift +++ b/AutoCatCore/Services/StorageService.swift @@ -7,11 +7,11 @@ protocol StorageServiceProtocol { func store(vehicle: Vehicle) throws -> CDVehicle } -class StorageService: StorageServiceProtocol { +public class StorageService: StorageServiceProtocol { private let container: NSPersistentCloudKitContainer - static var shared: StorageService { + public static var shared: StorageService { get async throws { print("!!!!!!!!!!!!!!!!!!!!!!!!! StorageService init") let service = StorageService() @@ -59,11 +59,11 @@ class StorageService: StorageServiceProtocol { // MARK: - StorageServiceProtocol - var context: NSManagedObjectContext { + public var context: NSManagedObjectContext { container.viewContext } - func store(vehicle: Vehicle) throws -> CDVehicle { + public func store(vehicle: Vehicle) throws -> CDVehicle { let cdVehicle = CDVehicle(vehicle: vehicle, context: context) try save()