Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ONMIR/Core/CoreData/ContextManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ extension ContextManager {

os.Logger.contextManager.debug("\(sqliteURL)")
let storeDescription = NSPersistentStoreDescription(url: sqliteURL)
// storeDescription.url = Constants.inMemoryStoreURL
#if DEBUG
storeDescription.url = Constants.inMemoryStoreURL
#endif
storeDescription.type = NSSQLiteStoreType
storeDescription.setOption(
stagedMigrationFactory.create(),
Expand Down
6 changes: 6 additions & 0 deletions ONMIR/Core/CoreData/Entity+Sendable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import CoreData

#warning("TODO: Sendable")
extension BookEntity: @unchecked Sendable {}
extension QuoteEntity: @unchecked Sendable {}
extension ReadingLogEntity: @unchecked Sendable {}
122 changes: 122 additions & 0 deletions ONMIR/Feature/BookDetail/AllQuotesViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import UIKit
import CoreData
import SnapKit

#warning("TODO: UI")
final class AllQuotesViewController: UIViewController {
private let tableView = UITableView()
private let bookObjectID: NSManagedObjectID
private lazy var fetchedResultsController = makeFetchedResultsController()
private lazy var dataSource = makeDataSource()

init(bookObjectID: NSManagedObjectID) {
self.bookObjectID = bookObjectID
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
setupUI()

do {
try fetchedResultsController.performFetch()
updateSnapshot()
} catch {
print("Failed to fetch quotes: \(error)")
}
}

private func setupUI() {
title = "All Quotes"
view.backgroundColor = .systemBackground

tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "QuoteCell")
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 80

view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}

private func makeDataSource() -> UITableViewDiffableDataSource<Int, NSManagedObjectID> {
let dataSource = UITableViewDiffableDataSource<Int, NSManagedObjectID>(tableView: tableView) { [weak self] tableView, indexPath, objectID in
let cell = tableView.dequeueReusableCell(withIdentifier: "QuoteCell", for: indexPath)

guard let self = self
else { return cell }

let context = self.fetchedResultsController.managedObjectContext

guard
let quote = context.object(with: objectID) as? QuoteEntity
else {
return cell
}

cell.textLabel?.text = quote.managedObjectContext?.performAndWait { quote.content } ?? ""
cell.detailTextLabel?.text = quote.managedObjectContext?.performAndWait { "\(quote.page) P" } ?? ""
cell.textLabel?.numberOfLines = 0
cell.accessoryType = .disclosureIndicator

return cell
}

tableView.dataSource = dataSource
return dataSource
}

private func makeFetchedResultsController() -> NSFetchedResultsController<QuoteEntity> {
let context = ContextManager.shared.mainContext

guard let book = context.object(with: bookObjectID) as? BookEntity else {
assertionFailure("Book not found")
self.dismiss(animated: true)
return .init()
}

let request: NSFetchRequest<QuoteEntity> = QuoteEntity.fetchRequest()
request.predicate = NSPredicate(format: "book == %@", book)
request.sortDescriptors = [NSSortDescriptor(keyPath: \QuoteEntity.page, ascending: false)]

let controller = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: context,
sectionNameKeyPath: nil,
cacheName: nil
)

controller.delegate = self
return controller
}

private func updateSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>()
snapshot.appendSections([0])

let objectIDs = fetchedResultsController.fetchedObjects?.map { $0.objectID } ?? []
snapshot.appendItems(objectIDs, toSection: 0)

dataSource.apply(snapshot, animatingDifferences: true)
}
}


extension AllQuotesViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}

extension AllQuotesViewController: @preconcurrency NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
let typedSnapshot = snapshot as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>
dataSource.apply(typedSnapshot, animatingDifferences: true)
}
}
125 changes: 125 additions & 0 deletions ONMIR/Feature/BookDetail/AllReadingLogsViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import UIKit
import CoreData
import SnapKit

#warning("TODO: UI")
final class AllReadingLogsViewController: UIViewController {
private let tableView = UITableView()
private let bookObjectID: NSManagedObjectID
private lazy var fetchedResultsController = makeFetchedResultsController()
private lazy var dataSource = makeDataSource()

private static let timeFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute]
formatter.unitsStyle = .abbreviated
return formatter
}()


init(bookObjectID: NSManagedObjectID) {
self.bookObjectID = bookObjectID
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
setupUI()

do {
try fetchedResultsController.performFetch()
updateSnapshot()
} catch {
print("Failed to fetch reading logs: \(error)")
}
}

private func setupUI() {
title = "All Book Logs"
view.backgroundColor = .systemBackground

tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ReadingLogCell")

view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}

private func makeDataSource() -> UITableViewDiffableDataSource<Int, NSManagedObjectID> {
let dataSource = UITableViewDiffableDataSource<Int, NSManagedObjectID>(tableView: tableView) { [weak self] tableView, indexPath, objectID in
let cell = tableView.dequeueReusableCell(withIdentifier: "ReadingLogCell", for: indexPath)

guard let self else { return cell }

let context = self.fetchedResultsController.managedObjectContext
guard
let log = context.object(with: objectID) as? ReadingLogEntity
else {
return cell
}

cell.textLabel?.text = log.managedObjectContext?.performAndWait { "\(log.startPage) - \(log.endPage)" } ?? ""
cell.detailTextLabel?.text = log.managedObjectContext?.performAndWait { Self.timeFormatter.string(from: log.readingSeconds) } ?? ""
cell.accessoryType = .disclosureIndicator

return cell
}

tableView.dataSource = dataSource
return dataSource
}

private func makeFetchedResultsController() -> NSFetchedResultsController<ReadingLogEntity> {
let context = ContextManager.shared.mainContext

guard let book = context.object(with: bookObjectID) as? BookEntity else {
assertionFailure("Book not found")
self.dismiss(animated: true)
return .init()
}

let request: NSFetchRequest<ReadingLogEntity> = ReadingLogEntity.fetchRequest()
request.predicate = NSPredicate(format: "book == %@", book)
request.sortDescriptors = [NSSortDescriptor(keyPath: \ReadingLogEntity.startPage, ascending: false)]

let controller = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: context,
sectionNameKeyPath: nil,
cacheName: nil
)

controller.delegate = self
return controller
}

private func updateSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>()
snapshot.appendSections([0])

let objectIDs = fetchedResultsController.fetchedObjects?.map { $0.objectID } ?? []
snapshot.appendItems(objectIDs, toSection: 0)

dataSource.apply(snapshot, animatingDifferences: true)
}
}


extension AllReadingLogsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}

extension AllReadingLogsViewController: @preconcurrency NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
let typedSnapshot = snapshot as NSDiffableDataSourceSnapshot<Int, NSManagedObjectID>
dataSource.apply(typedSnapshot, animatingDifferences: true)
}
}
Loading