Adds ability to import game save files
This commit is contained in:
parent
43b4aeac51
commit
803d180a9b
@ -64,6 +64,7 @@ class GameCollectionViewController: UICollectionViewController
|
|||||||
|
|
||||||
private var _renameAction: UIAlertAction?
|
private var _renameAction: UIAlertAction?
|
||||||
private var _changingArtworkGame: Game?
|
private var _changingArtworkGame: Game?
|
||||||
|
private var _importingSaveFileGame: Game?
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder)
|
required init?(coder aDecoder: NSCoder)
|
||||||
{
|
{
|
||||||
@ -395,6 +396,10 @@ private extension GameCollectionViewController
|
|||||||
self.viewSaveStates(for: game)
|
self.viewSaveStates(for: game)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let importSaveFile = Action(title: NSLocalizedString("Import Save File", comment: ""), style: .default) { [unowned self] _ in
|
||||||
|
self.importSaveFile(for: game)
|
||||||
|
}
|
||||||
|
|
||||||
let deleteAction = Action(title: NSLocalizedString("Delete", comment: ""), style: .destructive, action: { [unowned self] action in
|
let deleteAction = Action(title: NSLocalizedString("Delete", comment: ""), style: .destructive, action: { [unowned self] action in
|
||||||
self.delete(game)
|
self.delete(game)
|
||||||
})
|
})
|
||||||
@ -402,7 +407,7 @@ private extension GameCollectionViewController
|
|||||||
switch game.type
|
switch game.type
|
||||||
{
|
{
|
||||||
case GameType.unknown: return [cancelAction, renameAction, changeArtworkAction, shareAction, deleteAction]
|
case GameType.unknown: return [cancelAction, renameAction, changeArtworkAction, shareAction, deleteAction]
|
||||||
default: return [cancelAction, renameAction, changeArtworkAction, shareAction, saveStatesAction, deleteAction]
|
default: return [cancelAction, renameAction, changeArtworkAction, shareAction, saveStatesAction, importSaveFile, deleteAction]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,6 +486,87 @@ private extension GameCollectionViewController
|
|||||||
self.present(importController, animated: true, completion: nil)
|
self.present(importController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func changeArtwork(for game: Game, toImageAt url: URL?, errors: [Error])
|
||||||
|
{
|
||||||
|
var errors = errors
|
||||||
|
|
||||||
|
var imageURL: URL?
|
||||||
|
|
||||||
|
if let url = url
|
||||||
|
{
|
||||||
|
if url.isFileURL
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let imageData = try Data(contentsOf: url)
|
||||||
|
|
||||||
|
if
|
||||||
|
let image = UIImage(data: imageData),
|
||||||
|
let resizedImage = image.resizing(toFit: CGSize(width: 300, height: 300)),
|
||||||
|
let resizedData = resizedImage.jpegData(compressionQuality: 0.85)
|
||||||
|
{
|
||||||
|
let destinationURL = DatabaseManager.artworkURL(for: game)
|
||||||
|
try resizedData.write(to: destinationURL, options: .atomic)
|
||||||
|
|
||||||
|
imageURL = destinationURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
errors.append(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
imageURL = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for error in errors
|
||||||
|
{
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let imageURL = imageURL
|
||||||
|
{
|
||||||
|
DatabaseManager.shared.performBackgroundTask { (context) in
|
||||||
|
let temporaryGame = context.object(with: game.objectID) as! Game
|
||||||
|
temporaryGame.artworkURL = imageURL
|
||||||
|
context.saveWithErrorLogging()
|
||||||
|
|
||||||
|
// Local image URLs may not change despite being a different image, so manually mark record as updated.
|
||||||
|
SyncManager.shared.recordController.updateRecord(for: temporaryGame)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.presentedViewController?.dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
func presentAlertController()
|
||||||
|
{
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Unable to Change Artwork", comment: ""), message: NSLocalizedString("The image might be corrupted or in an unsupported format.", comment: ""), preferredStyle: .alert)
|
||||||
|
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("OK"), style: .cancel, handler: nil))
|
||||||
|
self.present(alertController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let presentedViewController = self.presentedViewController
|
||||||
|
{
|
||||||
|
presentedViewController.dismiss(animated: true) {
|
||||||
|
presentAlertController()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
presentAlertController()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func share(_ game: Game)
|
func share(_ game: Game)
|
||||||
{
|
{
|
||||||
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
||||||
@ -513,6 +599,54 @@ private extension GameCollectionViewController
|
|||||||
self.present(activityViewController, animated: true, completion: nil)
|
self.present(activityViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func importSaveFile(for game: Game)
|
||||||
|
{
|
||||||
|
self._importingSaveFileGame = game
|
||||||
|
|
||||||
|
let importController = ImportController(documentTypes: [kUTTypeItem as String])
|
||||||
|
importController.delegate = self
|
||||||
|
self.present(importController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importSaveFile(for game: Game, from fileURL: URL?, error: Error?)
|
||||||
|
{
|
||||||
|
// Dispatch to main queue so we can access game.gameSaveURL on its context's thread (main thread).
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
if let fileURL = fileURL
|
||||||
|
{
|
||||||
|
try FileManager.default.copyItem(at: fileURL, to: game.gameSaveURL, shouldReplace: true)
|
||||||
|
|
||||||
|
if let gameSave = game.gameSave
|
||||||
|
{
|
||||||
|
SyncManager.shared.recordController.updateRecord(for: gameSave)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Failed to Import Save File", comment: ""), error: error)
|
||||||
|
|
||||||
|
if let presentedViewController = self.presentedViewController
|
||||||
|
{
|
||||||
|
presentedViewController.dismiss(animated: true) {
|
||||||
|
self.present(alertController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.present(alertController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc func textFieldTextDidChange(_ textField: UITextField)
|
@objc func textFieldTextDidChange(_ textField: UITextField)
|
||||||
{
|
{
|
||||||
let text = textField.text ?? ""
|
let text = textField.text ?? ""
|
||||||
@ -618,82 +752,17 @@ extension GameCollectionViewController: ImportControllerDelegate
|
|||||||
{
|
{
|
||||||
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>, errors: [Error])
|
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>, errors: [Error])
|
||||||
{
|
{
|
||||||
guard let game = self._changingArtworkGame else { return }
|
if let game = self._changingArtworkGame
|
||||||
|
|
||||||
var errors = errors
|
|
||||||
|
|
||||||
var imageURL: URL?
|
|
||||||
|
|
||||||
if let url = urls.first
|
|
||||||
{
|
{
|
||||||
if url.isFileURL
|
self.changeArtwork(for: game, toImageAt: urls.first, errors: errors)
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
let imageData = try Data(contentsOf: url)
|
|
||||||
|
|
||||||
if
|
|
||||||
let image = UIImage(data: imageData),
|
|
||||||
let resizedImage = image.resizing(toFit: CGSize(width: 300, height: 300)),
|
|
||||||
let resizedData = resizedImage.jpegData(compressionQuality: 0.85)
|
|
||||||
{
|
|
||||||
let destinationURL = DatabaseManager.artworkURL(for: game)
|
|
||||||
try resizedData.write(to: destinationURL, options: .atomic)
|
|
||||||
|
|
||||||
imageURL = destinationURL
|
|
||||||
}
|
}
|
||||||
}
|
else if let game = self._importingSaveFileGame
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
errors.append(error)
|
self.importSaveFile(for: game, from: urls.first, error: errors.first)
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
imageURL = url
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for error in errors
|
self._changingArtworkGame = nil
|
||||||
{
|
self._importingSaveFileGame = nil
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let imageURL = imageURL
|
|
||||||
{
|
|
||||||
DatabaseManager.shared.performBackgroundTask { (context) in
|
|
||||||
let temporaryGame = context.object(with: game.objectID) as! Game
|
|
||||||
temporaryGame.artworkURL = imageURL
|
|
||||||
context.saveWithErrorLogging()
|
|
||||||
|
|
||||||
// Local image URLs may not change despite being a different image, so manually mark record as updated.
|
|
||||||
SyncManager.shared.recordController.updateRecord(for: temporaryGame)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.presentedViewController?.dismiss(animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
func presentAlertController()
|
|
||||||
{
|
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Unable to Change Artwork", comment: ""), message: NSLocalizedString("The image might be corrupted or in an unsupported format.", comment: ""), preferredStyle: .alert)
|
|
||||||
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("OK"), style: .cancel, handler: nil))
|
|
||||||
self.present(alertController, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let presentedViewController = self.presentedViewController
|
|
||||||
{
|
|
||||||
presentedViewController.dismiss(animated: true) {
|
|
||||||
presentAlertController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
presentAlertController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func importControllerDidCancel(_ importController: ImportController)
|
func importControllerDidCancel(_ importController: ImportController)
|
||||||
|
|||||||
@ -76,26 +76,20 @@ class ImportController: NSObject
|
|||||||
self.finish(with: urls, errors: [])
|
self.finish(with: urls, errors: [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let filesAction = UIAlertAction(title: NSLocalizedString("Files", comment: ""), style: .default) { (action) in
|
let filesAction = UIAlertAction(title: NSLocalizedString("Files", comment: ""), style: .default) { (action) in
|
||||||
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(ImportController.cancel))
|
self.presentDocumentBrowser()
|
||||||
|
|
||||||
let documentBrowserViewController = UIDocumentBrowserViewController(forOpeningFilesWithContentTypes: Array(self.documentTypes))
|
|
||||||
documentBrowserViewController.delegate = self
|
|
||||||
documentBrowserViewController.browserUserInterfaceStyle = .dark
|
|
||||||
documentBrowserViewController.allowsPickingMultipleItems = true
|
|
||||||
documentBrowserViewController.allowsDocumentCreation = false
|
|
||||||
documentBrowserViewController.additionalTrailingNavigationBarButtonItems = [cancelButton]
|
|
||||||
|
|
||||||
self.presentedViewController = documentBrowserViewController
|
|
||||||
self.presentingViewController?.present(documentBrowserViewController, animated: true, completion: nil)
|
|
||||||
}
|
}
|
||||||
alertController.addAction(filesAction)
|
alertController.addAction(filesAction)
|
||||||
|
|
||||||
self.presentedViewController = alertController
|
self.presentedViewController = alertController
|
||||||
self.presentingViewController?.present(alertController, animated: true, completion: nil)
|
self.presentingViewController?.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.presentDocumentBrowser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func cancel()
|
@objc private func cancel()
|
||||||
{
|
{
|
||||||
@ -117,6 +111,21 @@ class ImportController: NSObject
|
|||||||
|
|
||||||
self.presentingViewController?.importController = nil
|
self.presentingViewController?.importController = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentDocumentBrowser()
|
||||||
|
{
|
||||||
|
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(ImportController.cancel))
|
||||||
|
|
||||||
|
let documentBrowserViewController = UIDocumentBrowserViewController(forOpeningFilesWithContentTypes: Array(self.documentTypes))
|
||||||
|
documentBrowserViewController.delegate = self
|
||||||
|
documentBrowserViewController.browserUserInterfaceStyle = .dark
|
||||||
|
documentBrowserViewController.allowsPickingMultipleItems = true
|
||||||
|
documentBrowserViewController.allowsDocumentCreation = false
|
||||||
|
documentBrowserViewController.additionalTrailingNavigationBarButtonItems = [cancelButton]
|
||||||
|
|
||||||
|
self.presentedViewController = documentBrowserViewController
|
||||||
|
self.presentingViewController?.present(documentBrowserViewController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ImportController: UIDocumentBrowserViewControllerDelegate
|
extension ImportController: UIDocumentBrowserViewControllerDelegate
|
||||||
@ -149,8 +158,7 @@ extension ImportController: UIDocumentBrowserViewControllerDelegate
|
|||||||
|
|
||||||
// Use url, not intent.url, to ensure the file name matches what was in the document browser.
|
// Use url, not intent.url, to ensure the file name matches what was in the document browser.
|
||||||
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
|
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
|
||||||
|
try FileManager.default.copyItem(at: intent.url, to: temporaryURL, shouldReplace: true)
|
||||||
try FileManager.default.copyItem(at: intent.url, to: temporaryURL)
|
|
||||||
|
|
||||||
coordinatedURLs.insert(temporaryURL)
|
coordinatedURLs.insert(temporaryURL)
|
||||||
}
|
}
|
||||||
|
|||||||
2
External/Roxas
vendored
2
External/Roxas
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 91eaa0876c7c0f83c3800873c2dff7f445265d88
|
Subproject commit 1945d97204d113c635ec959f7777e7200d40cdee
|
||||||
Loading…
Reference in New Issue
Block a user