Adds support for 3D Touch app icon game shortcuts
This commit is contained in:
parent
2e36b8125e
commit
a9c3e85df8
@ -1 +1 @@
|
||||
Subproject commit 6658b7a7557278c9b846466a0599462c1aeb870b
|
||||
Subproject commit 4947d10713ba31017e40df275f40a315cf0da897
|
||||
@ -49,6 +49,8 @@
|
||||
BF4828841F9027B600028B97 /* Delta.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BF4828811F9027B600028B97 /* Delta.xcdatamodeld */; };
|
||||
BF4828861F9028F500028B97 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4828851F9028F500028B97 /* System.swift */; };
|
||||
BF4828881F90290F00028B97 /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4828871F90290F00028B97 /* Action.swift */; };
|
||||
BF525EE81FF5F370004AA849 /* DeepLinkController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF525EE71FF5F370004AA849 /* DeepLinkController.swift */; };
|
||||
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF525EE91FF6CD12004AA849 /* DeepLink.swift */; };
|
||||
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */; };
|
||||
BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */; };
|
||||
BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */; };
|
||||
@ -85,6 +87,8 @@
|
||||
BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */; };
|
||||
BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; };
|
||||
BF70798D1B6B464B0019077C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BF71CF871FE90006001F1613 /* AppIconShortcutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF71CF861FE90006001F1613 /* AppIconShortcutsViewController.swift */; };
|
||||
BF71CF8A1FE904B1001F1613 /* GameTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF71CF891FE904B1001F1613 /* GameTableViewCell.xib */; };
|
||||
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */; };
|
||||
BF7AE8081C2E858400B1B5BC /* GridMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */; };
|
||||
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */; };
|
||||
@ -115,7 +119,7 @@
|
||||
BFF0742D1E9DC17500ACDF4A /* GBCDeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BFF6452E1F7CC5060056533E /* GameControllerInputMappingTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6B82A41F7CC2A300042BFB /* GameControllerInputMappingTransformer.swift */; };
|
||||
BFF93AA01E0FB036005EC865 /* InputStreamOutputWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */; };
|
||||
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */; };
|
||||
BFFA4C091E8A24D600D87934 /* GameTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA4C081E8A24D600D87934 /* GameTableViewCell.swift */; };
|
||||
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; };
|
||||
BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFFA71E01AAC406100EE9DD1 /* Main.storyboard */; };
|
||||
BFFC461E1D59823500AF2CC6 /* GamesPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */; };
|
||||
@ -180,6 +184,8 @@
|
||||
BF4828831F9027B600028B97 /* Delta.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Delta.xcdatamodel; sourceTree = "<group>"; };
|
||||
BF4828851F9028F500028B97 /* System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = "<group>"; };
|
||||
BF4828871F90290F00028B97 /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = "<group>"; };
|
||||
BF525EE71FF5F370004AA849 /* DeepLinkController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkController.swift; sourceTree = "<group>"; };
|
||||
BF525EE91FF6CD12004AA849 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
|
||||
BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadControllerSkinImageOperation.swift; sourceTree = "<group>"; };
|
||||
BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadImageURLOperation.swift; sourceTree = "<group>"; };
|
||||
BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
@ -221,6 +227,8 @@
|
||||
BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _GameControllerInputMapping.swift; sourceTree = "<group>"; };
|
||||
BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameControllerInputMapping.swift; sourceTree = "<group>"; };
|
||||
BF70798B1B6B464B0019077C /* ZipZap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZipZap.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BF71CF861FE90006001F1613 /* AppIconShortcutsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconShortcutsViewController.swift; sourceTree = "<group>"; };
|
||||
BF71CF891FE904B1001F1613 /* GameTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GameTableViewCell.xib; sourceTree = "<group>"; };
|
||||
BF797A2C1C2D339F00F1A000 /* UILabel+FontSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+FontSize.swift"; sourceTree = "<group>"; };
|
||||
BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridMenuViewController.swift; sourceTree = "<group>"; };
|
||||
BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Delta.swift"; sourceTree = "<group>"; };
|
||||
@ -243,7 +251,7 @@
|
||||
BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveStateMigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBCDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFF93A9F1E0FB036005EC865 /* InputStreamOutputWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputStreamOutputWriter.swift; sourceTree = "<group>"; };
|
||||
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameMetadataTableViewCell.swift; sourceTree = "<group>"; };
|
||||
BFFA4C081E8A24D600D87934 /* GameTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameTableViewCell.swift; sourceTree = "<group>"; };
|
||||
BFFA71D71AAC406100EE9DD1 /* Delta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Delta.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BFFA71DB1AAC406100EE9DD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@ -343,12 +351,22 @@
|
||||
path = "Game Selection";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF525EE61FF5F355004AA849 /* Deep Linking */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF525EE91FF6CD12004AA849 /* DeepLink.swift */,
|
||||
BF525EE71FF5F370004AA849 /* DeepLinkController.swift */,
|
||||
);
|
||||
path = "Deep Linking";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF5942571E09BB5D0051894B /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF4828871F90290F00028B97 /* Action.swift */,
|
||||
BFE0229C1F5B56840052D888 /* Popover Menu */,
|
||||
BF5942671E09BBB70051894B /* Collection View */,
|
||||
BF71CF881FE90471001F1613 /* Table View */,
|
||||
BF5942601E09BBA80051894B /* Loading */,
|
||||
);
|
||||
path = Components;
|
||||
@ -467,6 +485,23 @@
|
||||
path = "Import Options";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF71CF851FE8FFF1001F1613 /* App Icon Shortcuts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF71CF861FE90006001F1613 /* AppIconShortcutsViewController.swift */,
|
||||
);
|
||||
path = "App Icon Shortcuts";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF71CF881FE90471001F1613 /* Table View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFFA4C081E8A24D600D87934 /* GameTableViewCell.swift */,
|
||||
BF71CF891FE904B1001F1613 /* GameTableViewCell.xib */,
|
||||
);
|
||||
path = "Table View";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -506,7 +541,6 @@
|
||||
BF59426E1E09BC5D0051894B /* GamesDatabase.swift */,
|
||||
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */,
|
||||
BF95E2781E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift */,
|
||||
BFFA4C081E8A24D600D87934 /* GameMetadataTableViewCell.swift */,
|
||||
);
|
||||
path = OpenVGDB;
|
||||
sourceTree = "<group>";
|
||||
@ -534,6 +568,7 @@
|
||||
BFAA1FEC1B8AA4FA00495943 /* Settings.swift */,
|
||||
BF5E7F451B9A652600AE44F8 /* Settings.storyboard */,
|
||||
BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */,
|
||||
BF71CF851FE8FFF1001F1613 /* App Icon Shortcuts */,
|
||||
BF11734E1DA32CEC00047DF8 /* Controllers */,
|
||||
BF1DAD5B1D9F574900E752A7 /* Controller Skins */,
|
||||
);
|
||||
@ -620,6 +655,7 @@
|
||||
BF59426C1E09BC450051894B /* Database */,
|
||||
BF59428C1E09BCE50051894B /* Importing */,
|
||||
BF930FFB1EB6D6EC00E8DBA0 /* Systems */,
|
||||
BF525EE61FF5F355004AA849 /* Deep Linking */,
|
||||
BF5942571E09BB5D0051894B /* Components */,
|
||||
BF696B7E1D9B2AE6009639E0 /* Theming */,
|
||||
BF090CEE1B490C1A00DCAB45 /* Extensions */,
|
||||
@ -759,6 +795,7 @@
|
||||
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */,
|
||||
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */,
|
||||
BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */,
|
||||
BF71CF8A1FE904B1001F1613 /* GameTableViewCell.xib in Resources */,
|
||||
BFFC46461D59861000AF2CC6 /* LaunchScreen.storyboard in Resources */,
|
||||
BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */,
|
||||
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */,
|
||||
@ -855,6 +892,7 @@
|
||||
BF59427C1E09BC830051894B /* Cheat.swift in Sources */,
|
||||
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */,
|
||||
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */,
|
||||
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */,
|
||||
BF59427E1E09BC830051894B /* Game.swift in Sources */,
|
||||
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
|
||||
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */,
|
||||
@ -875,15 +913,17 @@
|
||||
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
|
||||
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
|
||||
BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */,
|
||||
BF525EE81FF5F370004AA849 /* DeepLinkController.swift in Sources */,
|
||||
BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */,
|
||||
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */,
|
||||
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
|
||||
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
|
||||
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
|
||||
BF71CF871FE90006001F1613 /* AppIconShortcutsViewController.swift in Sources */,
|
||||
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */,
|
||||
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */,
|
||||
BF6BF31C1EB821A0008E83CD /* GamesDatabaseImportOption.swift in Sources */,
|
||||
BFFA4C091E8A24D600D87934 /* GameMetadataTableViewCell.swift in Sources */,
|
||||
BFFA4C091E8A24D600D87934 /* GameTableViewCell.swift in Sources */,
|
||||
BFFC46231D5984A000AF2CC6 /* LaunchViewController.swift in Sources */,
|
||||
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */,
|
||||
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
|
||||
|
||||
@ -17,6 +17,8 @@ import Crashlytics
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate
|
||||
{
|
||||
var window: UIWindow?
|
||||
|
||||
private let deepLinkController = DeepLinkController()
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
|
||||
{
|
||||
@ -35,12 +37,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate
|
||||
}
|
||||
|
||||
// Database
|
||||
|
||||
DatabaseManager.shared.loadPersistentStores { (description, error) in
|
||||
}
|
||||
|
||||
// Controllers
|
||||
ExternalGameControllerManager.shared.startMonitoring()
|
||||
|
||||
// Deep Links
|
||||
if let shortcut = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem
|
||||
{
|
||||
self.deepLinkController.handle(.shortcut(shortcut))
|
||||
|
||||
// false = we handled the deep link, so no need to call delegate method separately.
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -78,8 +88,6 @@ extension AppDelegate
|
||||
func configureAppearance()
|
||||
{
|
||||
self.window?.tintColor = UIColor.deltaPurple
|
||||
|
||||
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes[NSAttributedStringKey.foregroundColor.rawValue] = UIColor.white
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,15 +100,20 @@ extension AppDelegate
|
||||
|
||||
@discardableResult private func openURL(_ url: URL) -> Bool
|
||||
{
|
||||
guard url.isFileURL else { return false }
|
||||
|
||||
if GameType(fileExtension: url.pathExtension) != nil || url.pathExtension.lowercased() == "zip"
|
||||
if url.isFileURL
|
||||
{
|
||||
return self.importGame(at: url)
|
||||
if GameType(fileExtension: url.pathExtension) != nil || url.pathExtension.lowercased() == "zip"
|
||||
{
|
||||
return self.importGame(at: url)
|
||||
}
|
||||
else if url.pathExtension.lowercased() == "deltaskin"
|
||||
{
|
||||
return self.importControllerSkin(at: url)
|
||||
}
|
||||
}
|
||||
else if url.pathExtension.lowercased() == "deltaskin"
|
||||
else
|
||||
{
|
||||
return self.importControllerSkin(at: url)
|
||||
return self.deepLinkController.handle(.url(url))
|
||||
}
|
||||
|
||||
return false
|
||||
@ -145,3 +158,12 @@ extension AppDelegate
|
||||
}
|
||||
}
|
||||
|
||||
extension AppDelegate
|
||||
{
|
||||
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void)
|
||||
{
|
||||
let result = self.deepLinkController.handle(.shortcut(shortcutItem))
|
||||
completionHandler(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13528" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="6bq-zy-UZU">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="6bq-zy-UZU">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13526"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@ -18,52 +17,6 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="4cJ-4B-Kgt" customClass="GameMetadataTableViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="97"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="4cJ-4B-Kgt" id="7ze-s0-mpI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DSH-Hk-snb" userLabel="Selected Background View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="97.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BoxArt" translatesAutoresizingMaskIntoConstraints="NO" id="tNY-2F-llo">
|
||||
<rect key="frame" x="15" y="8" width="80" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="tNY-2F-llo" secondAttribute="height" multiplier="1:1" id="f4E-bV-L96"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Super Mario World" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DND-Fv-FyB">
|
||||
<rect key="frame" x="110" y="38.5" width="250" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="DND-Fv-FyB" firstAttribute="leading" secondItem="tNY-2F-llo" secondAttribute="trailing" constant="15" id="71e-t3-7Av"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="DND-Fv-FyB" secondAttribute="bottom" constant="1" id="9No-RE-0xx"/>
|
||||
<constraint firstItem="DND-Fv-FyB" firstAttribute="top" relation="greaterThanOrEqual" secondItem="7ze-s0-mpI" secondAttribute="top" constant="1" id="F9q-6H-sqC"/>
|
||||
<constraint firstAttribute="trailing" secondItem="DND-Fv-FyB" secondAttribute="trailing" constant="15" id="KFv-7n-LrD"/>
|
||||
<constraint firstItem="DND-Fv-FyB" firstAttribute="centerY" secondItem="7ze-s0-mpI" secondAttribute="centerY" id="YBX-t4-jkR"/>
|
||||
<constraint firstItem="tNY-2F-llo" firstAttribute="top" secondItem="7ze-s0-mpI" secondAttribute="top" constant="8" id="bYX-gA-QvB"/>
|
||||
<constraint firstAttribute="bottom" secondItem="tNY-2F-llo" secondAttribute="bottom" constant="8" id="fxr-wr-I6X"/>
|
||||
<constraint firstItem="tNY-2F-llo" firstAttribute="leading" secondItem="7ze-s0-mpI" secondAttribute="leading" constant="15" id="hX2-Gr-Bnz"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="artworkImageView" destination="tNY-2F-llo" id="GqY-jv-rso"/>
|
||||
<outlet property="artworkImageViewLeadingConstraint" destination="hX2-Gr-Bnz" id="be8-dr-c8K"/>
|
||||
<outlet property="artworkImageViewTrailingConstraint" destination="71e-t3-7Av" id="y62-KO-y1r"/>
|
||||
<outlet property="nameLabel" destination="DND-Fv-FyB" id="LhN-cA-8Hy"/>
|
||||
<outlet property="selectedBackgroundView" destination="DSH-Hk-snb" id="hLY-4k-VxU"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="SB6-jW-dhZ" id="2aq-ZA-84E"/>
|
||||
<outlet property="delegate" destination="SB6-jW-dhZ" id="WgY-cp-m7K"/>
|
||||
@ -101,7 +54,4 @@
|
||||
<point key="canvasLocation" x="1854" y="1002"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="BoxArt" width="100" height="100"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -100,7 +100,7 @@
|
||||
<inset key="sectionInset" minX="0.0" minY="20" maxX="0.0" maxY="20"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" misplaced="YES" reuseIdentifier="Cell" id="6XS-Ne-nGZ" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="6XS-Ne-nGZ" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="20" width="60" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.93725490199999995" green="0.93725490199999995" blue="0.95686274510000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Delta 0.6.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Str-BY-agW">
|
||||
<rect key="frame" x="0.0" y="573" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="673" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@ -222,6 +222,30 @@
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="3D Touch" id="fdp-8c-oOc">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="c5i-qG-ir9" style="IBUITableViewCellStyleDefault" id="SSL-t4-QZj">
|
||||
<rect key="frame" x="0.0" y="611" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="SSL-t4-QZj" id="hQB-Iy-bzy">
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="App Icon Shortcuts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="c5i-qG-ir9">
|
||||
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="yXS-6u-1AN" kind="show" id="b79-OK-isV"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="eHi-aO-uGS" id="rsh-0y-8Ex"/>
|
||||
@ -441,6 +465,51 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2220" y="1181.5592203898052"/>
|
||||
</scene>
|
||||
<!--App Icon Shortcuts-->
|
||||
<scene sceneID="e9N-fv-yuQ">
|
||||
<objects>
|
||||
<tableViewController id="yXS-6u-1AN" customClass="AppIconShortcutsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="97" sectionHeaderHeight="18" sectionFooterHeight="18" id="BGy-Nh-8Yd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="SwitchCell" rowHeight="44" id="Syl-dC-eKZ" customClass="SwitchTableViewCell">
|
||||
<rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Syl-dC-eKZ" id="lw6-ca-UzI">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4bh-Lf-zgk">
|
||||
<rect key="frame" x="310" y="6" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="switchGameShortcutsModeWith:" destination="yXS-6u-1AN" eventType="valueChanged" id="bX8-gd-h4g"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="4bh-Lf-zgk" firstAttribute="trailing" secondItem="lw6-ca-UzI" secondAttribute="trailingMargin" id="N1l-8j-xNY"/>
|
||||
<constraint firstItem="4bh-Lf-zgk" firstAttribute="centerY" secondItem="lw6-ca-UzI" secondAttribute="centerY" id="bTA-Ci-3mD"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="switchView" destination="4bh-Lf-zgk" id="lL9-4t-5VN"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="yXS-6u-1AN" id="gUb-Iy-f9K"/>
|
||||
<outlet property="delegate" destination="yXS-6u-1AN" id="UM5-nl-szw"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="App Icon Shortcuts" id="wfE-7e-l8g"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Lzk-4m-LKw" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2220" y="1897"/>
|
||||
</scene>
|
||||
<!--Controller Skins-->
|
||||
<scene sceneID="IN0-an-SWm">
|
||||
<objects>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// GameMetadataTableViewCell.swift
|
||||
// GameTableViewCell.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 3/27/17.
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class GameMetadataTableViewCell: UITableViewCell
|
||||
class GameTableViewCell: UITableViewCell
|
||||
{
|
||||
@IBOutlet private(set) var nameLabel: UILabel!
|
||||
@IBOutlet private(set) var artworkImageView: UIImageView!
|
||||
63
Delta/Components/Table View/GameTableViewCell.xib
Normal file
63
Delta/Components/Table View/GameTableViewCell.xib
Normal file
@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="FL0-zT-qa3" customClass="GameTableViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="97"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="FL0-zT-qa3" id="zSi-4a-DaH">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5dT-zd-huQ" userLabel="Selected Background View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="BoxArt" translatesAutoresizingMaskIntoConstraints="NO" id="68X-vf-MNx">
|
||||
<rect key="frame" x="15" y="8" width="80" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="68X-vf-MNx" secondAttribute="height" multiplier="1:1" id="P6f-Lc-8B3"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Super Mario World" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hKz-BX-p8h">
|
||||
<rect key="frame" x="110" y="38.5" width="250" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="hKz-BX-p8h" secondAttribute="bottom" constant="1" id="1JB-It-w1s"/>
|
||||
<constraint firstItem="68X-vf-MNx" firstAttribute="leading" secondItem="zSi-4a-DaH" secondAttribute="leading" constant="15" id="CD9-Qz-0UL"/>
|
||||
<constraint firstAttribute="trailing" secondItem="hKz-BX-p8h" secondAttribute="trailing" constant="15" id="RxT-jB-cvp"/>
|
||||
<constraint firstItem="68X-vf-MNx" firstAttribute="top" secondItem="zSi-4a-DaH" secondAttribute="top" constant="8" id="T5j-O5-aTX"/>
|
||||
<constraint firstItem="hKz-BX-p8h" firstAttribute="leading" secondItem="68X-vf-MNx" secondAttribute="trailing" constant="15" id="jks-s2-5ZX"/>
|
||||
<constraint firstItem="hKz-BX-p8h" firstAttribute="top" relation="greaterThanOrEqual" secondItem="zSi-4a-DaH" secondAttribute="top" constant="1" id="swc-Ib-2wh"/>
|
||||
<constraint firstItem="hKz-BX-p8h" firstAttribute="centerY" secondItem="zSi-4a-DaH" secondAttribute="centerY" id="uyb-vA-Qtb"/>
|
||||
<constraint firstAttribute="bottom" secondItem="68X-vf-MNx" secondAttribute="bottom" constant="8" id="wGX-lV-ACr"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="artworkImageView" destination="68X-vf-MNx" id="Sda-Tl-WEd"/>
|
||||
<outlet property="artworkImageViewLeadingConstraint" destination="CD9-Qz-0UL" id="jhw-i7-9ak"/>
|
||||
<outlet property="artworkImageViewTrailingConstraint" destination="jks-s2-5ZX" id="vrM-OV-rsa"/>
|
||||
<outlet property="nameLabel" destination="hKz-BX-p8h" id="gdI-v9-dj3"/>
|
||||
<outlet property="selectedBackgroundView" destination="5dT-zd-huQ" id="jOr-DK-8bp"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="BoxArt" width="100" height="100"/>
|
||||
</resources>
|
||||
</document>
|
||||
@ -62,6 +62,8 @@ final class DatabaseManager: NSPersistentContainer
|
||||
|
||||
private var gamesDatabase: GamesDatabase? = nil
|
||||
|
||||
private var validationManagedObjectContext: NSManagedObjectContext?
|
||||
|
||||
private init()
|
||||
{
|
||||
guard
|
||||
@ -94,11 +96,39 @@ extension DatabaseManager
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Update -
|
||||
private extension DatabaseManager
|
||||
{
|
||||
func updateRecentGameShortcuts()
|
||||
{
|
||||
guard let managedObjectContext = self.validationManagedObjectContext else { return }
|
||||
|
||||
guard Settings.gameShortcutsMode == .recent else { return }
|
||||
|
||||
let fetchRequest = Game.recentlyPlayedFetchRequest
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
|
||||
do
|
||||
{
|
||||
let games = try managedObjectContext.fetch(fetchRequest)
|
||||
Settings.gameShortcuts = games
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Preparation -
|
||||
private extension DatabaseManager
|
||||
{
|
||||
func prepareDatabase(completion: @escaping () -> Void)
|
||||
{
|
||||
self.validationManagedObjectContext = self.newBackgroundContext()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(DatabaseManager.validateManagedObjectContextSave(with:)), name: .NSManagedObjectContextDidSave, object: nil)
|
||||
|
||||
self.performBackgroundTask { (context) in
|
||||
|
||||
for system in System.supportedSystems
|
||||
@ -137,7 +167,6 @@ private extension DatabaseManager
|
||||
}
|
||||
|
||||
completion()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -502,6 +531,28 @@ extension DatabaseManager
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Notifications -
|
||||
private extension DatabaseManager
|
||||
{
|
||||
@objc func validateManagedObjectContextSave(with notification: Notification)
|
||||
{
|
||||
guard (notification.object as? NSManagedObjectContext) != self.validationManagedObjectContext else { return }
|
||||
|
||||
let insertedObjects = (notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject>) ?? []
|
||||
let updatedObjects = (notification.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? []
|
||||
let deletedObjects = (notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? []
|
||||
|
||||
let allObjects = insertedObjects.union(updatedObjects).union(deletedObjects)
|
||||
|
||||
if allObjects.contains(where: { $0 is Game })
|
||||
{
|
||||
self.validationManagedObjectContext?.perform {
|
||||
self.updateRecentGameShortcuts()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Private -
|
||||
private extension DatabaseManager
|
||||
{
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13532" systemVersion="16G29" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13772" systemVersion="16G1114" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0">
|
||||
<entity name="Cheat" representedClassName="Cheat" syncable="YES">
|
||||
<attribute name="code" attributeType="String" syncable="YES"/>
|
||||
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
@ -54,6 +54,7 @@
|
||||
</attribute>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="playedDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="type" attributeType="Transformable" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="GameType"/>
|
||||
@ -130,7 +131,7 @@
|
||||
<elements>
|
||||
<element name="Cheat" positionX="-198" positionY="-63" width="128" height="165"/>
|
||||
<element name="ControllerSkin" positionX="-387" positionY="90" width="128" height="135"/>
|
||||
<element name="Game" positionX="-378" positionY="-54" width="128" height="180"/>
|
||||
<element name="Game" positionX="-378" positionY="-54" width="128" height="195"/>
|
||||
<element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/>
|
||||
<element name="GameControllerInputMapping" positionX="-387" positionY="90" width="128" height="105"/>
|
||||
<element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/>
|
||||
|
||||
@ -55,6 +55,18 @@ public class Game: _Game, GameProtocol
|
||||
}
|
||||
}
|
||||
|
||||
extension Game
|
||||
{
|
||||
class var recentlyPlayedFetchRequest: NSFetchRequest<Game> {
|
||||
let fetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "%K != nil", #keyPath(Game.playedDate))
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Game.playedDate, ascending: false), NSSortDescriptor(keyPath: \Game.name, ascending: true)]
|
||||
fetchRequest.fetchLimit = 4
|
||||
|
||||
return fetchRequest
|
||||
}
|
||||
}
|
||||
|
||||
extension Game
|
||||
{
|
||||
override public func prepareForDeletion()
|
||||
|
||||
@ -22,6 +22,8 @@ public class _Game: NSManagedObject
|
||||
|
||||
@NSManaged public var name: String
|
||||
|
||||
@NSManaged public var playedDate: Date?
|
||||
|
||||
@NSManaged public var type: GameType
|
||||
|
||||
// MARK: - Relationships
|
||||
|
||||
@ -54,6 +54,8 @@ class GamesDatabaseBrowserViewController: UITableViewController
|
||||
|
||||
self.view.backgroundColor = UIColor.deltaDarkGray
|
||||
|
||||
self.tableView.register(GameTableViewCell.nib!, forCellReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.prefetchDataSource = self.dataSource
|
||||
|
||||
@ -61,7 +63,7 @@ class GamesDatabaseBrowserViewController: UITableViewController
|
||||
self.tableView.separatorColor = UIColor.gray
|
||||
|
||||
self.dataSource.searchController.delegate = self
|
||||
self.dataSource.searchController.searchBar.barStyle = .blackTranslucent
|
||||
self.dataSource.searchController.searchBar.barStyle = .black
|
||||
|
||||
if #available(iOS 11, *)
|
||||
{
|
||||
@ -95,7 +97,7 @@ private extension GamesDatabaseBrowserViewController
|
||||
|
||||
/* Cell Configuration */
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, metadata, indexPath) in
|
||||
self.configure(cell: cell as! GameMetadataTableViewCell, with: metadata, for: indexPath)
|
||||
self.configure(cell: cell as! GameTableViewCell, with: metadata, for: indexPath)
|
||||
}
|
||||
|
||||
|
||||
@ -113,7 +115,7 @@ private extension GamesDatabaseBrowserViewController
|
||||
self.dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||
guard let image = image else { return }
|
||||
|
||||
let cell = cell as! GameMetadataTableViewCell
|
||||
let cell = cell as! GameTableViewCell
|
||||
|
||||
let artworkDisplaySize = AVMakeRect(aspectRatio: image.size, insideRect: cell.artworkImageView.bounds)
|
||||
let offset = (cell.artworkImageView.bounds.width - artworkDisplaySize.width) / 2
|
||||
@ -150,7 +152,7 @@ private extension GamesDatabaseBrowserViewController
|
||||
|
||||
private extension GamesDatabaseBrowserViewController
|
||||
{
|
||||
func configure(cell: GameMetadataTableViewCell, with metadata: GameMetadata, for indexPath: IndexPath)
|
||||
func configure(cell: GameTableViewCell, with metadata: GameMetadata, for indexPath: IndexPath)
|
||||
{
|
||||
cell.backgroundColor = UIColor.deltaDarkGray
|
||||
|
||||
|
||||
103
Delta/Deep Linking/DeepLink.swift
Normal file
103
Delta/Deep Linking/DeepLink.swift
Normal file
@ -0,0 +1,103 @@
|
||||
//
|
||||
// DeepLink.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 12/29/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension URL
|
||||
{
|
||||
init(action: DeepLink.Action)
|
||||
{
|
||||
var components = URLComponents()
|
||||
components.host = action.type.rawValue
|
||||
|
||||
switch action
|
||||
{
|
||||
case .launchGame(let identifier): components.path = identifier
|
||||
}
|
||||
|
||||
let url = components.url!
|
||||
self = url
|
||||
}
|
||||
}
|
||||
|
||||
extension UIApplicationShortcutItem
|
||||
{
|
||||
convenience init(localizedTitle: String, action: DeepLink.Action)
|
||||
{
|
||||
var userInfo: [AnyHashable: Any]?
|
||||
|
||||
switch action
|
||||
{
|
||||
case .launchGame(let identifier): userInfo = [DeepLink.Key.identifier.rawValue: identifier]
|
||||
}
|
||||
|
||||
self.init(type: action.type.rawValue, localizedTitle: localizedTitle, localizedSubtitle: nil, icon: nil, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
extension DeepLink
|
||||
{
|
||||
enum Action
|
||||
{
|
||||
case launchGame(identifier: String)
|
||||
|
||||
var type: ActionType {
|
||||
switch self
|
||||
{
|
||||
case .launchGame: return .launchGame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ActionType: String
|
||||
{
|
||||
case launchGame = "game"
|
||||
}
|
||||
|
||||
enum Key: String
|
||||
{
|
||||
case identifier
|
||||
case game
|
||||
}
|
||||
}
|
||||
|
||||
enum DeepLink
|
||||
{
|
||||
case url(URL)
|
||||
case shortcut(UIApplicationShortcutItem)
|
||||
|
||||
var actionType: ActionType? {
|
||||
switch self
|
||||
{
|
||||
case .url(let url):
|
||||
guard let host = url.host else { return nil }
|
||||
|
||||
let type = ActionType(rawValue: host)
|
||||
return type
|
||||
|
||||
case .shortcut(let shortcut):
|
||||
let type = ActionType(rawValue: shortcut.type)
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
var action: Action? {
|
||||
guard let type = self.actionType else { return nil }
|
||||
|
||||
switch (self, type)
|
||||
{
|
||||
case (.url(let url), .launchGame):
|
||||
let identifier = url.lastPathComponent
|
||||
return .launchGame(identifier: identifier)
|
||||
|
||||
case (.shortcut(let shortcut), .launchGame):
|
||||
guard let identifier = shortcut.userInfo?[Key.identifier.rawValue] as? String else { return nil }
|
||||
return .launchGame(identifier: identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
83
Delta/Deep Linking/DeepLinkController.swift
Normal file
83
Delta/Deep Linking/DeepLinkController.swift
Normal file
@ -0,0 +1,83 @@
|
||||
//
|
||||
// DeepLinkController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 12/28/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension Notification.Name
|
||||
{
|
||||
static let deepLinkControllerLaunchGame = Notification.Name("deepLinkControllerLaunchGame")
|
||||
}
|
||||
|
||||
extension UIViewController
|
||||
{
|
||||
var allowsDeepLinkingDismissal: Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
struct DeepLinkController
|
||||
{
|
||||
private var window: UIWindow? {
|
||||
guard let delegate = UIApplication.shared.delegate, let window = delegate.window else { return nil }
|
||||
return window
|
||||
}
|
||||
|
||||
private var topViewController: UIViewController? {
|
||||
guard let window = self.window else { return nil }
|
||||
|
||||
var topViewController = window.rootViewController
|
||||
while topViewController?.presentedViewController != nil
|
||||
{
|
||||
guard !(topViewController?.presentedViewController is UIAlertController) else { break }
|
||||
|
||||
topViewController = topViewController?.presentedViewController
|
||||
}
|
||||
|
||||
return topViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension DeepLinkController
|
||||
{
|
||||
@discardableResult func handle(_ deepLink: DeepLink) -> Bool
|
||||
{
|
||||
guard let action = deepLink.action else { return false }
|
||||
|
||||
switch action
|
||||
{
|
||||
case .launchGame(let identifier): return self.launchGame(withIdentifier: identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension DeepLinkController
|
||||
{
|
||||
func launchGame(withIdentifier identifier: String) -> Bool
|
||||
{
|
||||
guard let topViewController = self.topViewController, topViewController.allowsDeepLinkingDismissal else { return false }
|
||||
|
||||
let fetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(Game.identifier), identifier)
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
|
||||
do
|
||||
{
|
||||
guard let game = try DatabaseManager.shared.viewContext.fetch(fetchRequest).first else { return false }
|
||||
|
||||
NotificationCenter.default.post(name: .deepLinkControllerLaunchGame, object: self, userInfo: [DeepLink.Key.game: game])
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -93,6 +93,21 @@ class GameViewController: DeltaCore.GameViewController
|
||||
}
|
||||
}
|
||||
|
||||
private var _deepLinkResumingSaveState: SaveStateProtocol? {
|
||||
didSet {
|
||||
guard let saveState = oldValue, _deepLinkResumingSaveState == nil else { return }
|
||||
|
||||
do
|
||||
{
|
||||
try FileManager.default.removeItem(at: saveState.fileURL)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var _isLoadingSaveState = false
|
||||
|
||||
private var context = CIContext(options: [kCIContextWorkingColorSpace: NSNull()])
|
||||
@ -127,6 +142,7 @@ class GameViewController: DeltaCore.GameViewController
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.updateControllers), name: .externalGameControllerDidDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.didEnterBackground(with:)), name: .UIApplicationDidEnterBackground, object: UIApplication.shared)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.settingsDidChange(with:)), name: .settingsDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.deepLinkControllerLaunchGame(with:)), name: .deepLinkControllerLaunchGame, object: nil)
|
||||
}
|
||||
|
||||
deinit
|
||||
@ -364,7 +380,11 @@ extension GameViewController
|
||||
@IBAction private func unwindFromGamesViewController(with segue: UIStoryboardSegue)
|
||||
{
|
||||
self.pausedSaveState = nil
|
||||
self.emulatorCore?.resume()
|
||||
|
||||
if let emulatorCore = self.emulatorCore, emulatorCore.state == .paused
|
||||
{
|
||||
emulatorCore.resume()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - KVO
|
||||
@ -375,10 +395,39 @@ extension GameViewController
|
||||
|
||||
guard let rawValue = change?[.oldKey] as? Int, let previousState = EmulatorCore.State(rawValue: rawValue) else { return }
|
||||
|
||||
if let saveState = _deepLinkResumingSaveState, let emulatorCore = self.emulatorCore, emulatorCore.state == .running
|
||||
{
|
||||
emulatorCore.pause()
|
||||
|
||||
do
|
||||
{
|
||||
try emulatorCore.load(saveState)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
_deepLinkResumingSaveState = nil
|
||||
emulatorCore.resume()
|
||||
}
|
||||
|
||||
if previousState == .stopped
|
||||
{
|
||||
self.emulatorCore?.updateCheats()
|
||||
}
|
||||
|
||||
if self.emulatorCore?.state == .running
|
||||
{
|
||||
DatabaseManager.shared.performBackgroundTask { (context) in
|
||||
guard let game = self.game as? Game else { return }
|
||||
|
||||
let backgroundGame = context.object(with: game.objectID) as! Game
|
||||
backgroundGame.playedDate = Date()
|
||||
|
||||
context.saveWithErrorLogging()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -812,7 +861,8 @@ extension GameViewController: GameViewControllerDelegate
|
||||
|
||||
func gameViewControllerShouldResumeEmulation(_ gameViewController: DeltaCore.GameViewController) -> Bool
|
||||
{
|
||||
return (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.isSelectingSustainedButtons && self.view.window != nil
|
||||
let result = (self.presentedViewController == nil || self.presentedViewController?.isDisappearing == true) && !self.isSelectingSustainedButtons && self.view.window != nil
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@ -858,4 +908,44 @@ private extension GameViewController
|
||||
case .translucentControllerSkinOpacity: self.controllerView.translucentControllerSkinOpacity = Settings.translucentControllerSkinOpacity
|
||||
}
|
||||
}
|
||||
|
||||
@objc func deepLinkControllerLaunchGame(with notification: Notification)
|
||||
{
|
||||
guard let game = notification.userInfo?[DeepLink.Key.game] as? Game else { return }
|
||||
|
||||
self.game = game
|
||||
|
||||
if let pausedSaveState = self.pausedSaveState, game == (self.game as? Game)
|
||||
{
|
||||
// Launching current game via deep link, so we store a copy of the paused save state to resume when emulator core is started.
|
||||
|
||||
do
|
||||
{
|
||||
let temporaryURL = FileManager.default.uniqueTemporaryURL()
|
||||
try FileManager.default.copyItem(at: pausedSaveState.fileURL, to: temporaryURL)
|
||||
|
||||
_deepLinkResumingSaveState = DeltaCore.SaveState(fileURL: temporaryURL, gameType: game.type)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
if let pauseViewController = self.pauseViewController
|
||||
{
|
||||
let segue = UIStoryboardSegue(identifier: "unwindFromPauseMenu", source: pauseViewController, destination: self)
|
||||
self.unwindFromPauseViewController(segue)
|
||||
}
|
||||
else if
|
||||
let navigationController = self.presentedViewController as? UINavigationController,
|
||||
let pageViewController = navigationController.topViewController?.childViewControllers.first as? UIPageViewController,
|
||||
let gameCollectionViewController = pageViewController.viewControllers?.first as? GameCollectionViewController
|
||||
{
|
||||
let segue = UIStoryboardSegue(identifier: "unwindFromGames", source: gameCollectionViewController, destination: self)
|
||||
self.unwindFromGamesViewController(with: segue)
|
||||
}
|
||||
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,6 +200,8 @@ private extension GamesViewController
|
||||
searchResultsController?.dataSource.predicate = searchValue.predicate
|
||||
return nil
|
||||
}
|
||||
self.searchController?.searchBar.barStyle = .black
|
||||
|
||||
self.navigationItem.searchController = self.searchController
|
||||
self.navigationItem.hidesSearchBarWhenScrolling = false
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Roxas
|
||||
|
||||
class LaunchViewController: UIViewController
|
||||
{
|
||||
@ -15,6 +16,8 @@ class LaunchViewController: UIViewController
|
||||
|
||||
private var presentedGameViewController: Bool = false
|
||||
|
||||
private var applicationLaunchDeepLinkGame: Game?
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return self.gameViewController?.preferredStatusBarStyle ?? .lightContent
|
||||
}
|
||||
@ -27,6 +30,13 @@ class LaunchViewController: UIViewController
|
||||
return self.gameViewController
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(LaunchViewController.deepLinkControllerLaunchGame(with:)), name: .deepLinkControllerLaunchGame, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewDidAppear(animated)
|
||||
@ -35,10 +45,33 @@ class LaunchViewController: UIViewController
|
||||
{
|
||||
self.presentedGameViewController = true
|
||||
|
||||
self.gameViewController.performSegue(withIdentifier: "showInitialGamesViewController", sender: nil)
|
||||
self.transitionCoordinator?.animate(alongsideTransition: nil, completion: { (context) in
|
||||
func showGameViewController()
|
||||
{
|
||||
self.view.bringSubview(toFront: self.gameViewContainerView)
|
||||
})
|
||||
|
||||
self.setNeedsStatusBarAppearanceUpdate()
|
||||
|
||||
if #available(iOS 11.0, *)
|
||||
{
|
||||
self.setNeedsUpdateOfHomeIndicatorAutoHidden()
|
||||
}
|
||||
}
|
||||
|
||||
if let game = self.applicationLaunchDeepLinkGame
|
||||
{
|
||||
self.gameViewController.game = game
|
||||
|
||||
UIView.transition(with: self.view, duration: 0.3, options: [.transitionCrossDissolve], animations: {
|
||||
showGameViewController()
|
||||
}, completion: nil)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.gameViewController.performSegue(withIdentifier: "showInitialGamesViewController", sender: nil)
|
||||
self.transitionCoordinator?.animate(alongsideTransition: nil, completion: { (context) in
|
||||
showGameViewController()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,3 +82,15 @@ class LaunchViewController: UIViewController
|
||||
self.gameViewController = segue.destination as! GameViewController
|
||||
}
|
||||
}
|
||||
|
||||
private extension LaunchViewController
|
||||
{
|
||||
@objc func deepLinkControllerLaunchGame(with notification: Notification)
|
||||
{
|
||||
guard !self.presentedGameViewController else { return }
|
||||
|
||||
guard let game = notification.userInfo?[DeepLink.Key.game] as? Game else { return }
|
||||
|
||||
self.applicationLaunchDeepLinkGame = game
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,11 +130,11 @@ extension SaveStatesViewController
|
||||
self.navigationController?.toolbar.barStyle = .blackTranslucent
|
||||
|
||||
self.updateTheme()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool)
|
||||
override func viewWillDisappear(_ animated: Bool)
|
||||
{
|
||||
super.viewDidDisappear(animated)
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
self.resetEmulatorCoreIfNeeded()
|
||||
}
|
||||
|
||||
@ -0,0 +1,355 @@
|
||||
//
|
||||
// AppIconShortcutsViewController.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 12/19/17.
|
||||
// Copyright © 2017 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import DeltaCore
|
||||
|
||||
import Roxas
|
||||
|
||||
@objc(SwitchTableViewCell)
|
||||
private class SwitchTableViewCell: UITableViewCell
|
||||
{
|
||||
@IBOutlet var switchView: UISwitch!
|
||||
}
|
||||
|
||||
class AppIconShortcutsViewController: UITableViewController
|
||||
{
|
||||
private lazy var dataSource = RSTCompositeTableViewPrefetchingDataSource<Game, UIImage>(dataSources: [self.modeDataSource, self.shortcutsDataSource, self.gamesDataSource])
|
||||
private let modeDataSource = RSTDynamicTableViewDataSource<Game>()
|
||||
private let shortcutsDataSource = RSTArrayTableViewPrefetchingDataSource<Game, UIImage>(items: [])
|
||||
private let gamesDataSource = RSTFetchedResultsTableViewPrefetchingDataSource<Game, UIImage>(fetchedResultsController: NSFetchedResultsController())
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.prepareDataSource()
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.register(GameTableViewCell.nib!, forCellReuseIdentifier: RSTCellContentGenericCellIdentifier)
|
||||
|
||||
if #available(iOS 11, *)
|
||||
{
|
||||
self.navigationItem.searchController = self.gamesDataSource.searchController
|
||||
self.navigationItem.hidesSearchBarWhenScrolling = false
|
||||
}
|
||||
else
|
||||
{
|
||||
self.tableView.tableHeaderView = self.gamesDataSource.searchController.searchBar
|
||||
}
|
||||
|
||||
self.tableView.dataSource = self.dataSource
|
||||
self.tableView.allowsSelectionDuringEditing = true
|
||||
|
||||
self.updateShortcuts()
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppIconShortcutsViewController
|
||||
{
|
||||
func prepareDataSource()
|
||||
{
|
||||
// Mode
|
||||
self.modeDataSource.numberOfSectionsHandler = { 1 }
|
||||
self.modeDataSource.numberOfItemsHandler = { _ in 1 }
|
||||
self.modeDataSource.cellIdentifierHandler = { _ in "SwitchCell" }
|
||||
|
||||
// Shortcuts
|
||||
self.shortcutsDataSource.items = Settings.gameShortcuts
|
||||
|
||||
// Games
|
||||
let gamesFetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
|
||||
gamesFetchRequest.returnsObjectsAsFaults = false
|
||||
gamesFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Game.type, ascending: true), NSSortDescriptor(key: #keyPath(Game.name), ascending: true)]
|
||||
|
||||
let gamesFetchedResultsController = NSFetchedResultsController(fetchRequest: gamesFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: #keyPath(Game.type), cacheName: nil)
|
||||
self.gamesDataSource.fetchedResultsController = gamesFetchedResultsController
|
||||
self.gamesDataSource.searchController.searchableKeyPaths = [#keyPath(Game.name)]
|
||||
|
||||
// Data Source
|
||||
self.dataSource.proxy = self
|
||||
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, game, indexPath) in
|
||||
if indexPath.section == 0
|
||||
{
|
||||
self.configureModeCell(cell as! SwitchTableViewCell, for: indexPath)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.configureGameCell(cell as! GameTableViewCell, with: game, for: indexPath)
|
||||
}
|
||||
}
|
||||
self.dataSource.prefetchHandler = { (game, indexPath, completionHandler) in
|
||||
guard indexPath.section > 0 else { return nil }
|
||||
|
||||
guard let artworkURL = game.artworkURL else { return nil }
|
||||
|
||||
let imageOperation = LoadImageURLOperation(url: artworkURL)
|
||||
imageOperation.resultHandler = { (image, error) in
|
||||
completionHandler(image, error)
|
||||
}
|
||||
|
||||
return imageOperation
|
||||
}
|
||||
self.dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
|
||||
guard indexPath.section > 0 else { return }
|
||||
|
||||
guard let image = image else { return }
|
||||
|
||||
let cell = cell as! GameTableViewCell
|
||||
|
||||
let artworkDisplaySize = AVMakeRect(aspectRatio: image.size, insideRect: cell.artworkImageView.bounds)
|
||||
let offset = (cell.artworkImageView.bounds.width - artworkDisplaySize.width) / 2
|
||||
|
||||
// Offset artworkImageViewLeadingConstraint and artworkImageViewTrailingConstraint to right-align artworkImageView
|
||||
cell.artworkImageViewLeadingConstraint.constant += offset
|
||||
cell.artworkImageViewTrailingConstraint.constant -= offset
|
||||
|
||||
cell.artworkImageView.image = image
|
||||
cell.artworkImageView.superview?.layoutIfNeeded()
|
||||
}
|
||||
self.dataSource.rowAnimation = .fade
|
||||
}
|
||||
|
||||
func configureModeCell(_ cell: SwitchTableViewCell, for indexPath: IndexPath)
|
||||
{
|
||||
cell.textLabel?.text = NSLocalizedString("Recently Played Games", comment: "")
|
||||
cell.textLabel?.backgroundColor = .clear
|
||||
|
||||
cell.switchView.isOn = (Settings.gameShortcutsMode == .recent)
|
||||
cell.switchView.onTintColor = self.view.tintColor
|
||||
}
|
||||
|
||||
func configureGameCell(_ cell: GameTableViewCell, with game: Game, for indexPath: IndexPath)
|
||||
{
|
||||
cell.nameLabel.textColor = .darkText
|
||||
cell.backgroundColor = .white
|
||||
|
||||
cell.nameLabel.text = game.name
|
||||
cell.artworkImageView.image = #imageLiteral(resourceName: "BoxArt")
|
||||
|
||||
cell.artworkImageViewLeadingConstraint.constant = 15
|
||||
cell.artworkImageViewTrailingConstraint.constant = 15
|
||||
|
||||
cell.separatorInset.left = cell.nameLabel.frame.minX
|
||||
|
||||
cell.selectedBackgroundView = nil
|
||||
|
||||
switch (indexPath.section, Settings.gameShortcutsMode)
|
||||
{
|
||||
case (1, _):
|
||||
cell.selectionStyle = .none
|
||||
cell.contentView.alpha = 1.0
|
||||
|
||||
case (2..., .recent):
|
||||
cell.selectionStyle = .none
|
||||
cell.contentView.alpha = 0.3
|
||||
|
||||
case (2..., .manual):
|
||||
cell.selectionStyle = .gray
|
||||
cell.contentView.alpha = 1.0
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppIconShortcutsViewController
|
||||
{
|
||||
func updateShortcuts()
|
||||
{
|
||||
switch Settings.gameShortcutsMode
|
||||
{
|
||||
case .recent:
|
||||
let fetchRequest = Game.recentlyPlayedFetchRequest
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
|
||||
do
|
||||
{
|
||||
let games = try DatabaseManager.shared.viewContext.fetch(fetchRequest)
|
||||
self.shortcutsDataSource.setItems(games, with: [])
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
self.tableView.setEditing(false, animated: true)
|
||||
|
||||
case .manual: self.tableView.setEditing(true, animated: true)
|
||||
}
|
||||
|
||||
Settings.gameShortcuts = self.shortcutsDataSource.items
|
||||
}
|
||||
|
||||
func addShortcut(for game: Game)
|
||||
{
|
||||
guard self.shortcutsDataSource.items.count < 4 else { return }
|
||||
|
||||
guard !self.shortcutsDataSource.items.contains(game) else { return }
|
||||
|
||||
// No need to adjust destinationIndexPath, since it forwards change directly to table view.
|
||||
let destinationIndexPath = IndexPath(row: self.shortcutsDataSource.items.count, section: 1)
|
||||
|
||||
let insertion = RSTCellContentChange(type: .insert, currentIndexPath: nil, destinationIndexPath: destinationIndexPath)
|
||||
insertion.rowAnimation = .fade
|
||||
|
||||
var shortcuts = self.shortcutsDataSource.items
|
||||
shortcuts.insert(game, at: destinationIndexPath.row)
|
||||
self.shortcutsDataSource.setItems(shortcuts, with: [insertion])
|
||||
|
||||
self.updateShortcuts()
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppIconShortcutsViewController
|
||||
{
|
||||
@IBAction func switchGameShortcutsMode(with sender: UISwitch)
|
||||
{
|
||||
if sender.isOn
|
||||
{
|
||||
Settings.gameShortcutsMode = .recent
|
||||
}
|
||||
else
|
||||
{
|
||||
Settings.gameShortcutsMode = .manual
|
||||
}
|
||||
|
||||
self.tableView.beginUpdates()
|
||||
|
||||
self.updateShortcuts()
|
||||
self.tableView.reloadSections(IndexSet(integersIn: 0 ..< self.tableView.numberOfSections), with: .fade)
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
extension AppIconShortcutsViewController
|
||||
{
|
||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
|
||||
{
|
||||
guard indexPath.section == 0 else { return super.tableView(tableView, heightForRowAt: indexPath) }
|
||||
|
||||
return 44
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
|
||||
{
|
||||
switch section
|
||||
{
|
||||
case 0: return nil
|
||||
case 1: return NSLocalizedString("Shortcuts", comment: "")
|
||||
default:
|
||||
let gameType = GameType(rawValue: self.gamesDataSource.fetchedResultsController.sections![section - 2].name)
|
||||
|
||||
let system = System(gameType: gameType)!
|
||||
return system.localizedName
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String?
|
||||
{
|
||||
switch (section, Settings.gameShortcutsMode)
|
||||
{
|
||||
case (0, .recent): return NSLocalizedString("Your most recently played games will appear as shortcuts when 3D touching the app icon.", comment: "")
|
||||
case (0, .manual): return NSLocalizedString("The games you've selected below will appear as shortcuts when 3D touching the app icon.", comment: "")
|
||||
case (1, .recent): return " " // Return non-empty string since empty string changes vertical offset of section for some reason.
|
||||
case (1, .manual): return NSLocalizedString("You may have up to 4 shortcuts.", comment: "")
|
||||
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||
{
|
||||
guard indexPath.section > 1 else { return }
|
||||
|
||||
guard Settings.gameShortcutsMode == .manual else { return }
|
||||
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let game = self.dataSource.item(at: indexPath)
|
||||
self.addShortcut(for: game)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
|
||||
{
|
||||
switch editingStyle
|
||||
{
|
||||
case .none: break
|
||||
case .delete:
|
||||
let deletion = RSTCellContentChange(type: .delete, currentIndexPath: indexPath, destinationIndexPath: nil)
|
||||
deletion.rowAnimation = .fade
|
||||
|
||||
var shortcuts = self.shortcutsDataSource.items
|
||||
shortcuts.remove(at: indexPath.row) // No need to adjust indexPath, since it forwards change directly to table view.
|
||||
self.shortcutsDataSource.setItems(shortcuts, with: [deletion])
|
||||
|
||||
case .insert:
|
||||
let game = self.dataSource.item(at: indexPath)
|
||||
self.addShortcut(for: game)
|
||||
}
|
||||
|
||||
self.updateShortcuts()
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String?
|
||||
{
|
||||
return NSLocalizedString("Remove", comment: "")
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle
|
||||
{
|
||||
switch indexPath.section
|
||||
{
|
||||
case 1: return .delete
|
||||
case 2...: return .insert
|
||||
default: return .none
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool
|
||||
{
|
||||
return false
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool
|
||||
{
|
||||
return (indexPath.section == 1)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath
|
||||
{
|
||||
let indexPath: IndexPath
|
||||
|
||||
switch proposedDestinationIndexPath.section
|
||||
{
|
||||
case 0: indexPath = IndexPath(row: 0, section: 1)
|
||||
case 1: indexPath = proposedDestinationIndexPath
|
||||
default: indexPath = IndexPath(row: self.shortcutsDataSource.items.count - 1, section: 1)
|
||||
}
|
||||
|
||||
return indexPath
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
|
||||
{
|
||||
var items = self.shortcutsDataSource.items
|
||||
|
||||
let game = items.remove(at: sourceIndexPath.row)
|
||||
items.insert(game, at: destinationIndexPath.row)
|
||||
|
||||
self.shortcutsDataSource.items = items
|
||||
|
||||
self.updateShortcuts()
|
||||
}
|
||||
}
|
||||
@ -35,6 +35,15 @@ extension Settings
|
||||
}
|
||||
}
|
||||
|
||||
extension Settings
|
||||
{
|
||||
enum GameShortcutsMode: String
|
||||
{
|
||||
case recent
|
||||
case manual
|
||||
}
|
||||
}
|
||||
|
||||
struct Settings
|
||||
{
|
||||
/// Controllers
|
||||
@ -66,9 +75,54 @@ struct Settings
|
||||
}
|
||||
}
|
||||
|
||||
static var gameShortcutsMode: GameShortcutsMode {
|
||||
set { UserDefaults.standard.gameShortcutsMode = newValue.rawValue }
|
||||
get {
|
||||
let mode = GameShortcutsMode(rawValue: UserDefaults.standard.gameShortcutsMode) ?? .recent
|
||||
return mode
|
||||
}
|
||||
}
|
||||
|
||||
static var gameShortcuts: [Game] {
|
||||
set {
|
||||
let identifiers = newValue.map { $0.identifier }
|
||||
UserDefaults.standard.gameShortcutIdentifiers = identifiers
|
||||
|
||||
let shortcuts = newValue.map { UIApplicationShortcutItem(localizedTitle: $0.name, action: .launchGame(identifier: $0.identifier)) }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.shortcutItems = shortcuts
|
||||
}
|
||||
}
|
||||
get {
|
||||
let identifiers = UserDefaults.standard.gameShortcutIdentifiers
|
||||
|
||||
do
|
||||
{
|
||||
let fetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "%K IN %@", #keyPath(Game.identifier), identifiers)
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
|
||||
let games = try DatabaseManager.shared.viewContext.fetch(fetchRequest).sorted(by: { (game1, game2) -> Bool in
|
||||
let index1 = identifiers.index(of: game1.identifier)!
|
||||
let index2 = identifiers.index(of: game2.identifier)!
|
||||
return index1 < index2
|
||||
})
|
||||
|
||||
return games
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
static func registerDefaults()
|
||||
{
|
||||
let defaults = [#keyPath(UserDefaults.translucentControllerSkinOpacity): 0.7]
|
||||
let defaults = [#keyPath(UserDefaults.translucentControllerSkinOpacity): 0.7, #keyPath(UserDefaults.gameShortcutsMode): GameShortcutsMode.recent.rawValue] as [String : Any]
|
||||
UserDefaults.standard.register(defaults: defaults)
|
||||
}
|
||||
|
||||
@ -163,4 +217,7 @@ private extension UserDefaults
|
||||
{
|
||||
@NSManaged var translucentControllerSkinOpacity: CGFloat
|
||||
@NSManaged var previousGameCollectionIdentifier: String?
|
||||
|
||||
@NSManaged var gameShortcutsMode: String
|
||||
@NSManaged var gameShortcutIdentifiers: [String]
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ private extension SettingsViewController
|
||||
case controllers
|
||||
case controllerSkins
|
||||
case controllerOpacity
|
||||
case threeDTouch
|
||||
}
|
||||
|
||||
enum Segue: String
|
||||
@ -130,6 +131,15 @@ private extension SettingsViewController
|
||||
let percentage = String(format: "%.f", Settings.translucentControllerSkinOpacity * 100) + "%"
|
||||
self.controllerOpacityLabel.text = percentage
|
||||
}
|
||||
|
||||
func isSectionHidden(_ section: Section) -> Bool
|
||||
{
|
||||
switch section
|
||||
{
|
||||
case .threeDTouch: return self.view.traitCollection.forceTouchCapability != .available
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsViewController
|
||||
@ -183,7 +193,15 @@ extension SettingsViewController
|
||||
{
|
||||
case .controllers: return 1 // Temporarily hide other controller indexes until controller logic is finalized
|
||||
case .controllerSkins: return System.supportedSystems.count
|
||||
default: return super.tableView(tableView, numberOfRowsInSection: sectionIndex)
|
||||
default:
|
||||
if isSectionHidden(section)
|
||||
{
|
||||
return 0
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.tableView(tableView, numberOfRowsInSection: sectionIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,6 +244,64 @@ extension SettingsViewController
|
||||
case Section.controllers: self.performSegue(withIdentifier: Segue.controllers.rawValue, sender: cell)
|
||||
case Section.controllerSkins: self.performSegue(withIdentifier: Segue.controllerSkins.rawValue, sender: cell)
|
||||
case Section.controllerOpacity: break
|
||||
case Section.threeDTouch: break
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
|
||||
{
|
||||
let section = Section(rawValue: section)!
|
||||
|
||||
if isSectionHidden(section)
|
||||
{
|
||||
return nil
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.tableView(tableView, titleForHeaderInSection: section.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String?
|
||||
{
|
||||
let section = Section(rawValue: section)!
|
||||
|
||||
if isSectionHidden(section)
|
||||
{
|
||||
return nil
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.tableView(tableView, titleForFooterInSection: section.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
|
||||
{
|
||||
let section = Section(rawValue: section)!
|
||||
|
||||
if isSectionHidden(section)
|
||||
{
|
||||
return 1
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.tableView(tableView, heightForHeaderInSection: section.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
|
||||
{
|
||||
let section = Section(rawValue: section)!
|
||||
|
||||
if isSectionHidden(section)
|
||||
{
|
||||
return 1
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.tableView(tableView, heightForFooterInSection: section.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
External/Roxas
vendored
2
External/Roxas
vendored
@ -1 +1 @@
|
||||
Subproject commit 97b3a7ab05ee320d3c96eadc3cc69c38d10ec206
|
||||
Subproject commit a903b123e1136d77c8b4f4b0e5f78700160f3e97
|
||||
Loading…
Reference in New Issue
Block a user