From ca4ccfc3aefb3c15c0ed8253373c263601de45ea Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 20 Nov 2018 14:47:47 -0600 Subject: [PATCH] Adds basic GameSyncStatusViewController to view status of game-related records --- Delta.xcodeproj/project.pbxproj | 4 + Delta/Base.lproj/Settings.storyboard | 41 +++++ .../GameSyncStatusViewController.swift | 154 ++++++++++++++++++ .../Syncing/SyncStatusViewController.swift | 39 +++-- External/Harmony | 2 +- 5 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 Delta/Settings/Syncing/GameSyncStatusViewController.swift diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index 212c9b8..463bb95 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -99,6 +99,7 @@ BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */; }; BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */; }; BF8A333421A484A000A42FD4 /* BadgedTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8A333321A484A000A42FD4 /* BadgedTableViewCell.swift */; }; + BF8A334621A4926F00A42FD4 /* GameSyncStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8A334521A4926F00A42FD4 /* GameSyncStatusViewController.swift */; }; BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */; }; BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */; }; BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2761E4977BF0030E7AD /* GameMetadata.swift */; }; @@ -247,6 +248,7 @@ BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridMenuViewController.swift; sourceTree = ""; }; BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Delta.swift"; sourceTree = ""; }; BF8A333321A484A000A42FD4 /* BadgedTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgedTableViewCell.swift; sourceTree = ""; }; + BF8A334521A4926F00A42FD4 /* GameSyncStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSyncStatusViewController.swift; sourceTree = ""; }; BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverMenuController.swift; sourceTree = ""; }; BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputCalloutView.swift; sourceTree = ""; }; BF95E2761E4977BF0030E7AD /* GameMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameMetadata.swift; sourceTree = ""; }; @@ -375,6 +377,7 @@ children = ( BF48F74D219A16DA00BC2FC1 /* SyncingServicesViewController.swift */, BFDB3417219E4B1700595A62 /* SyncStatusViewController.swift */, + BF8A334521A4926F00A42FD4 /* GameSyncStatusViewController.swift */, ); path = Syncing; sourceTree = ""; @@ -930,6 +933,7 @@ BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */, BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */, BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */, + BF8A334621A4926F00A42FD4 /* GameSyncStatusViewController.swift in Sources */, BF59427E1E09BC830051894B /* Game.swift in Sources */, BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */, BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */, diff --git a/Delta/Base.lproj/Settings.storyboard b/Delta/Base.lproj/Settings.storyboard index 2ce7533..4be6b9a 100644 --- a/Delta/Base.lproj/Settings.storyboard +++ b/Delta/Base.lproj/Settings.storyboard @@ -828,6 +828,9 @@ + + + @@ -840,5 +843,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Delta/Settings/Syncing/GameSyncStatusViewController.swift b/Delta/Settings/Syncing/GameSyncStatusViewController.swift new file mode 100644 index 0000000..b66a7ea --- /dev/null +++ b/Delta/Settings/Syncing/GameSyncStatusViewController.swift @@ -0,0 +1,154 @@ +// +// GameSyncStatusViewController.swift +// Delta +// +// Created by Riley Testut on 11/20/18. +// Copyright © 2018 Riley Testut. All rights reserved. +// + +import UIKit + +import Roxas +import Harmony + +extension GameSyncStatusViewController +{ + private enum Section: Int, CaseIterable + { + case game + case saveStates + case cheats + } +} + +class GameSyncStatusViewController: UITableViewController +{ + var game: Game! + + private lazy var dataSource = self.makeDataSource() + + private let dateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.timeStyle = .short + dateFormatter.dateStyle = .short + + return dateFormatter + }() + + private var recordsByObjectURI = [URL: Record]() + + override func viewDidLoad() + { + super.viewDidLoad() + + self.title = self.game.name + + self.tableView.dataSource = self.dataSource + } + + override func viewWillAppear(_ animated: Bool) + { + self.fetchRecords() + + super.viewWillAppear(animated) + } +} + +private extension GameSyncStatusViewController +{ + private func makeDataSource() -> RSTCompositeTableViewDataSource + { + func configure(_ cell: UITableViewCell, recordedObject: NSManagedObject) + { + if let record = self.recordsByObjectURI[recordedObject.objectID.uriRepresentation()], record.isConflicted + { + cell.textLabel?.textColor = .red + } + else + { + cell.textLabel?.textColor = .darkText + } + } + + let gameDataSource = RSTArrayTableViewDataSource(items: [self.game]) + gameDataSource.cellConfigurationHandler = { (cell, game, indexPath) in + cell.textLabel?.text = game.name + + configure(cell, recordedObject: game) + } + + let saveStatesFetchRequest = SaveState.fetchRequest() as NSFetchRequest + saveStatesFetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(SaveState.game), self.game) + saveStatesFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \SaveState.creationDate, ascending: true)] + + let saveStatesDataSource = RSTFetchedResultsTableViewDataSource(fetchRequest: saveStatesFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) + saveStatesDataSource.cellConfigurationHandler = { (cell, saveState, indexPath) in + if let name = saveState.name + { + cell.textLabel?.text = name + } + else + { + cell.textLabel?.text = self.dateFormatter.string(from: saveState.modifiedDate) + } + + configure(cell, recordedObject: saveState) + } + + let cheatsFetchRequest = Cheat.fetchRequest() as NSFetchRequest + cheatsFetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(Cheat.game), self.game) + cheatsFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Cheat.name, ascending: true)] + + let cheatsDataSource = RSTFetchedResultsTableViewDataSource(fetchRequest: cheatsFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) + cheatsDataSource.cellConfigurationHandler = { (cell, cheat, indexPath) in + cell.textLabel?.text = cheat.name + + configure(cell, recordedObject: cheat) + } + + let dataSources = [gameDataSource, saveStatesDataSource, cheatsDataSource] as! [RSTArrayTableViewDataSource] + + let dataSource = RSTCompositeTableViewDataSource(dataSources: dataSources) + dataSource.proxy = self + return dataSource + } + + func fetchRecords() + { + var recordsByObjectURI = [URL: Record]() + + do + { + let recordedObjects = ([self.game!] + Array(self.game.saveStates) + Array(self.game.cheats)) as! [SyncableManagedObject] + let records = try SyncManager.shared.recordController.fetchRecords(for: recordedObjects) + + for record in records + { + guard let recordedObject = record.recordedObject else { continue } + + recordsByObjectURI[recordedObject.objectID.uriRepresentation()] = record + } + } + catch + { + print(error) + } + + self.recordsByObjectURI = recordsByObjectURI + } +} + +extension GameSyncStatusViewController +{ + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? + { + guard self.dataSource.tableView(self.tableView, numberOfRowsInSection: section) > 0 else { return nil } + + switch Section.allCases[section] + { + case .game: return NSLocalizedString("Game", comment: "") + case .saveStates: return NSLocalizedString("Save States", comment: "") + case .cheats: return NSLocalizedString("Cheats", comment: "") + } + } +} diff --git a/Delta/Settings/Syncing/SyncStatusViewController.swift b/Delta/Settings/Syncing/SyncStatusViewController.swift index 4e29c20..b60b69b 100644 --- a/Delta/Settings/Syncing/SyncStatusViewController.swift +++ b/Delta/Settings/Syncing/SyncStatusViewController.swift @@ -30,6 +30,18 @@ class SyncStatusViewController: UITableViewController self.fetchConflictedRecords() } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) + { + guard segue.identifier == "showGame" else { return } + + guard let cell = sender as? UITableViewCell, let indexPath = self.tableView.indexPath(for: cell) else { return } + + let game = self.dataSource.item(at: indexPath) + + let gameSyncStatusViewController = segue.destination as! GameSyncStatusViewController + gameSyncStatusViewController.game = game + } } private extension SyncStatusViewController @@ -92,20 +104,21 @@ private extension SyncStatusViewController for record in records { guard let recordedObject = record.recordedObject else { continue } - - let conflictedGame: Game? - - switch recordedObject - { - case let game as Game: conflictedGame = game - case let saveState as SaveState: conflictedGame = saveState.game - case let cheat as Cheat: conflictedGame = cheat.game - default: conflictedGame = nil + recordedObject.managedObjectContext?.performAndWait { + let conflictedGame: Game? + + switch recordedObject + { + case let game as Game: conflictedGame = game + case let saveState as SaveState: conflictedGame = saveState.game + case let cheat as Cheat: conflictedGame = cheat.game + default: conflictedGame = nil + } + + guard let game = conflictedGame else { return } + + gameConflictsCount[game.objectID.uriRepresentation(), default: 0] += 1 } - - guard let game = conflictedGame else { continue } - - gameConflictsCount[game.objectID.uriRepresentation(), default: 0] += 1 } self.gameConflictsCount = gameConflictsCount diff --git a/External/Harmony b/External/Harmony index 6f5dc28..ef2c685 160000 --- a/External/Harmony +++ b/External/Harmony @@ -1 +1 @@ -Subproject commit 6f5dc280077cee8382ccc6622ee8383aadb59d48 +Subproject commit ef2c685333abe1cce9ee0a43150656634ab06081