Adds Delete action when previewing/long pressing games
This commit is contained in:
parent
75903552f9
commit
89905618e4
@ -16,28 +16,26 @@ extension Action
|
||||
case cancel
|
||||
case destructive
|
||||
case selected
|
||||
}
|
||||
}
|
||||
|
||||
extension Action.Style
|
||||
{
|
||||
var alertActionStyle: UIAlertActionStyle
|
||||
{
|
||||
switch self
|
||||
|
||||
var alertActionStyle: UIAlertActionStyle
|
||||
{
|
||||
case .default, .selected: return .default
|
||||
case .cancel: return .cancel
|
||||
case .destructive: return .destructive
|
||||
switch self
|
||||
{
|
||||
case .default, .selected: return .default
|
||||
case .cancel: return .cancel
|
||||
case .destructive: return .destructive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var previewActionStyle: UIPreviewActionStyle
|
||||
{
|
||||
switch self
|
||||
|
||||
var previewActionStyle: UIPreviewActionStyle?
|
||||
{
|
||||
case .default, .cancel: return .default
|
||||
case .destructive: return .destructive
|
||||
case .selected: return .selected
|
||||
switch self
|
||||
{
|
||||
case .default: return .default
|
||||
case .destructive: return .destructive
|
||||
case .selected: return .selected
|
||||
case .cancel: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,38 +45,52 @@ struct Action
|
||||
let title: String
|
||||
let style: Style
|
||||
let action: ((Action) -> Void)?
|
||||
|
||||
var alertAction: UIAlertAction
|
||||
{
|
||||
let alertAction = UIAlertAction(title: self.title, style: self.style.alertActionStyle) { (action) in
|
||||
self.action?(self)
|
||||
}
|
||||
return alertAction
|
||||
}
|
||||
|
||||
var previewAction: UIPreviewAction
|
||||
{
|
||||
let previewAction = UIPreviewAction(title: self.title, style: self.style.previewActionStyle) { (action, viewController) in
|
||||
self.action?(self)
|
||||
}
|
||||
return previewAction
|
||||
}
|
||||
}
|
||||
|
||||
// There is no public designated initializer for UIAlertAction or UIPreviewAction, so we cannot add our own convenience init
|
||||
// If only there were factory initializers... https://github.com/apple/swift-evolution/pull/247
|
||||
/*
|
||||
extension UIAlertAction
|
||||
{
|
||||
convenience init(action: Action)
|
||||
convenience init(_ action: Action)
|
||||
{
|
||||
self.init(title: action.title, style: action.style.alertActionStyle) { (alertAction) in
|
||||
action.action?(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIPreviewAction
|
||||
{
|
||||
convenience init(action: Action)
|
||||
convenience init?(_ action: Action)
|
||||
{
|
||||
guard let previewActionStyle = action.style.previewActionStyle else { return nil }
|
||||
|
||||
self.init(title: action.title, style: previewActionStyle) { (previewAction, viewController) in
|
||||
action.action?(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIAlertController
|
||||
{
|
||||
convenience init(actions: [Action])
|
||||
{
|
||||
self.init(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
for action in actions.alertActions
|
||||
{
|
||||
self.addAction(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension RangeReplaceableCollection where Iterator.Element == Action
|
||||
{
|
||||
var alertActions: [UIAlertAction] {
|
||||
let actions = self.map { UIAlertAction($0) }
|
||||
return actions
|
||||
}
|
||||
|
||||
var previewActions: [UIPreviewAction] {
|
||||
let actions = self.flatMap { UIPreviewAction($0) }
|
||||
return actions
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@ -41,6 +41,7 @@ class Game: NSManagedObject, GameProtocol
|
||||
|
||||
@NSManaged var gameCollections: Set<GameCollection>
|
||||
@NSManaged var previewSaveState: SaveState?
|
||||
@NSManaged var saveStates: Set<SaveState>
|
||||
|
||||
var fileURL: URL {
|
||||
var fileURL: URL!
|
||||
@ -87,6 +88,13 @@ extension Game
|
||||
managedObjectContext.delete(collection)
|
||||
}
|
||||
|
||||
// Manually cascade deletion since SaveState.fileURL references Game, and so we need to ensure we delete SaveState's before Game
|
||||
// Otherwise, we crash when accessing SaveState.game since it is nil
|
||||
for saveState in self.saveStates
|
||||
{
|
||||
managedObjectContext.delete(saveState)
|
||||
}
|
||||
|
||||
if managedObjectContext.hasChanges
|
||||
{
|
||||
managedObjectContext.saveWithErrorLogging()
|
||||
|
||||
@ -37,9 +37,15 @@ class GameViewController: DeltaCore.GameViewController
|
||||
override var game: GameProtocol? {
|
||||
willSet {
|
||||
self.emulatorCore?.removeObserver(self, forKeyPath: #keyPath(EmulatorCore.state), context: &kvoContext)
|
||||
|
||||
let game = self.game as? Game
|
||||
NotificationCenter.default.removeObserver(self, name: .NSManagedObjectContextDidSave, object: game?.managedObjectContext)
|
||||
}
|
||||
didSet {
|
||||
self.emulatorCore?.addObserver(self, forKeyPath: #keyPath(EmulatorCore.state), options: [.old], context: &kvoContext)
|
||||
|
||||
let game = self.game as? Game
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.managedObjectContextDidChange(with:)), name: .NSManagedObjectContextObjectsDidChange, object: game?.managedObjectContext)
|
||||
}
|
||||
}
|
||||
|
||||
@ -713,4 +719,16 @@ private extension GameViewController
|
||||
{
|
||||
self.updateAutoSaveState()
|
||||
}
|
||||
|
||||
@objc func managedObjectContextDidChange(with notification: Notification)
|
||||
{
|
||||
guard let deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject> else { return }
|
||||
guard let game = self.game as? Game else { return }
|
||||
|
||||
if deletedObjects.contains(game)
|
||||
{
|
||||
self.emulatorCore?.gameViews.forEach { $0.inputImage = nil }
|
||||
self.game = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,6 +53,9 @@ extension GameCollectionViewController
|
||||
layout.itemWidth = 90
|
||||
|
||||
self.registerForPreviewing(with: self, sourceView: self.collectionView!)
|
||||
|
||||
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(GameCollectionViewController.handleLongPressGesture(_:)))
|
||||
self.collectionView?.addGestureRecognizer(longPressGestureRecognizer)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool)
|
||||
@ -187,6 +190,52 @@ private extension GameCollectionViewController
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Game Actions -
|
||||
private extension GameCollectionViewController
|
||||
{
|
||||
func actions(for game: Game) -> [Action]
|
||||
{
|
||||
let cancelAction = Action(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, action: nil)
|
||||
|
||||
let deleteAction = Action(title: NSLocalizedString("Delete", comment: ""), style: .destructive, action: { [unowned self] action in
|
||||
self.delete(game)
|
||||
})
|
||||
|
||||
return [cancelAction, deleteAction]
|
||||
}
|
||||
|
||||
func delete(_ game: Game)
|
||||
{
|
||||
let confirmationAlertController = UIAlertController(title: NSLocalizedString("Are you sure you want to delete this game? All associated data, such as saves, save states, and cheat codes, will also be deleted.", comment: ""), message: nil, preferredStyle: .actionSheet)
|
||||
confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Delete Game", comment: ""), style: .destructive, handler: { action in
|
||||
|
||||
DatabaseManager.shared.performBackgroundTask { (context) in
|
||||
let temporaryGame = context.object(with: game.objectID) as! Game
|
||||
context.delete(temporaryGame)
|
||||
|
||||
context.saveWithErrorLogging()
|
||||
}
|
||||
|
||||
}))
|
||||
confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))
|
||||
|
||||
self.present(confirmationAlertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func handleLongPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer)
|
||||
{
|
||||
guard gestureRecognizer.state == .began else { return }
|
||||
|
||||
guard let indexPath = self.collectionView?.indexPathForItem(at: gestureRecognizer.location(in: self.collectionView)) else { return }
|
||||
|
||||
let game = self.dataSource.fetchedResultsController.object(at: indexPath)
|
||||
let actions = self.actions(for: game)
|
||||
|
||||
let alertController = UIAlertController(actions: actions)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - UIViewControllerPreviewingDelegate -
|
||||
/// UIViewControllerPreviewingDelegate
|
||||
extension GameCollectionViewController: UIViewControllerPreviewingDelegate
|
||||
@ -212,6 +261,9 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
|
||||
gameViewController.previewImage = UIImage(contentsOfFile: previewSaveState.imageFileURL.path)
|
||||
}
|
||||
|
||||
let actions = self.actions(for: game).previewActions
|
||||
gameViewController.overridePreviewActionItems = actions
|
||||
|
||||
return gameViewController
|
||||
}
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@ extension GamesViewController
|
||||
|
||||
if self.fetchedResultsController.performFetchIfNeeded()
|
||||
{
|
||||
self.updateSections()
|
||||
self.updateSections(animated: false)
|
||||
}
|
||||
|
||||
DispatchQueue.global().async {
|
||||
@ -189,10 +189,13 @@ private extension GamesViewController
|
||||
viewController.theme = self.theme
|
||||
viewController.activeEmulatorCore = self.activeEmulatorCore
|
||||
|
||||
// Need to set content inset here AND willTransitionTo callback to ensure its correct for all edge cases
|
||||
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
|
||||
|
||||
return viewController
|
||||
}
|
||||
|
||||
func updateSections()
|
||||
func updateSections(animated: Bool)
|
||||
{
|
||||
let sections = self.fetchedResultsController.sections?.first?.numberOfObjects ?? 0
|
||||
self.pageControl.numberOfPages = sections
|
||||
@ -214,7 +217,7 @@ private extension GamesViewController
|
||||
|
||||
}
|
||||
|
||||
self.navigationController?.setToolbarHidden(sections < 2, animated: self.view.window != nil)
|
||||
self.navigationController?.setToolbarHidden(sections < 2, animated: animated)
|
||||
|
||||
if sections > 0
|
||||
{
|
||||
@ -223,8 +226,8 @@ private extension GamesViewController
|
||||
{
|
||||
if let viewController = self.viewControllerForIndex(0)
|
||||
{
|
||||
self.pageViewController.view.isHidden = false
|
||||
self.backgroundView.isHidden = true
|
||||
self.pageViewController.view.setHidden(false, animated: animated)
|
||||
self.backgroundView.setHidden(true, animated: animated)
|
||||
|
||||
self.pageViewController.setViewControllers([viewController], direction: .forward, animated: false, completion: nil)
|
||||
|
||||
@ -240,11 +243,8 @@ private extension GamesViewController
|
||||
{
|
||||
self.title = NSLocalizedString("Games", comment: "")
|
||||
|
||||
if !self.pageViewController.view.isHidden
|
||||
{
|
||||
self.pageViewController.view.isHidden = true
|
||||
self.backgroundView.isHidden = false
|
||||
}
|
||||
self.pageViewController.view.setHidden(true, animated: animated)
|
||||
self.backgroundView.setHidden(false, animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -313,6 +313,6 @@ extension GamesViewController: NSFetchedResultsControllerDelegate
|
||||
{
|
||||
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
|
||||
{
|
||||
self.updateSections()
|
||||
self.updateSections(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,15 +251,9 @@ private extension SaveStatesViewController
|
||||
|
||||
let saveState = self.fetchedResultsController.object(at: indexPath) as! SaveState
|
||||
|
||||
guard let actions = self.actionsForSaveState(saveState)?.map({ $0.alertAction }) else { return }
|
||||
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
for action in actions
|
||||
{
|
||||
alertController.addAction(action)
|
||||
}
|
||||
guard let actions = self.actionsForSaveState(saveState) else { return }
|
||||
|
||||
let alertController = UIAlertController(actions: actions)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@ -559,10 +553,11 @@ extension SaveStatesViewController: UIViewControllerPreviewingDelegate
|
||||
self.emulatorCoreSaveState != nil
|
||||
else { return nil }
|
||||
|
||||
|
||||
previewingContext.sourceRect = layoutAttributes.frame
|
||||
|
||||
let saveState = self.fetchedResultsController.object(at: indexPath) as! SaveState
|
||||
let actions = self.actionsForSaveState(saveState)?.lazy.filter{ $0.style != .cancel }.map{ $0.previewAction } ?? []
|
||||
let actions = self.actionsForSaveState(saveState)?.previewActions ?? []
|
||||
let previewImage = self.imageCache.object(forKey: saveState.imageFileURL) ?? UIImage(contentsOfFile: saveState.imageFileURL.path)
|
||||
|
||||
let previewGameViewController = PreviewGameViewController()
|
||||
|
||||
2
External/Roxas
vendored
2
External/Roxas
vendored
@ -1 +1 @@
|
||||
Subproject commit f0661f78b095212ed2f564a4bbf1c1f6c9c50384
|
||||
Subproject commit e334730200712202feb470f1525c7eea30542146
|
||||
Loading…
Reference in New Issue
Block a user