Merge branch 'feature/harmony' into develop

This commit is contained in:
Riley Testut 2019-02-06 14:21:47 -08:00
commit 986b329178
79 changed files with 5122 additions and 1985 deletions

3
.gitmodules vendored
View File

@ -13,3 +13,6 @@
[submodule "Cores/GBCDeltaCore"]
path = Cores/GBCDeltaCore
url = git@github.com:rileytestut/GBCDeltaCore.git
[submodule "External/Harmony"]
path = External/Harmony
url = https://github.com/rileytestut/Harmony.git

@ -1 +1 @@
Subproject commit 6da27f9b694e886d6c9805ba54848b0f0a922598
Subproject commit 473474019bd2e3be623cd593da4c1e8c4e51fb80

View File

@ -36,6 +36,10 @@
BF15AF841F54B43B009B6AAB /* ActionInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF15AF831F54B43B009B6AAB /* ActionInput.swift */; };
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */; };
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */; };
BF1F45A421AF274D00EF9895 /* SyncResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1F45A321AF274D00EF9895 /* SyncResultViewController.swift */; };
BF1F45AB21AF4B5800EF9895 /* SyncResultsViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF1F45AA21AF4B5800EF9895 /* SyncResultsViewController.storyboard */; };
BF1F45AD21AF57BA00EF9895 /* HarmonyMetadataKey+Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1F45AC21AF57BA00EF9895 /* HarmonyMetadataKey+Keys.swift */; };
BF1F45BF21AF676F00EF9895 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1F45BE21AF676F00EF9895 /* Box.swift */; };
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; };
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; };
BF31878B1D489AAA00BD020D /* CheatValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF31878A1D489AAA00BD020D /* CheatValidator.swift */; };
@ -48,11 +52,19 @@
BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF353FFE1C5DA3C500C1184C /* PausePresentationControllerContentView.xib */; };
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */; };
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */; };
BF3D6C512202865F0083E05A /* Delta2ToDelta3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF3D6C502202865F0083E05A /* Delta2ToDelta3.xcmappingmodel */; };
BF3D6C53220286750083E05A /* Delta3ToDelta4.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF3D6C52220286750083E05A /* Delta3ToDelta4.xcmappingmodel */; };
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 */; };
BF48F74E219A16DA00BC2FC1 /* SyncingServicesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF48F74D219A16DA00BC2FC1 /* SyncingServicesViewController.swift */; };
BF48F755219A1EF000BC2FC1 /* Harmony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF48F754219A1EEB00BC2FC1 /* Harmony.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
BF48F756219A1EF000BC2FC1 /* Harmony.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF48F754219A1EEB00BC2FC1 /* Harmony.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF48F75B219A1F8A00BC2FC1 /* Harmony_Drive.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF48F75A219A1F8300BC2FC1 /* Harmony_Drive.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
BF48F75C219A1F8A00BC2FC1 /* Harmony_Drive.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF48F75A219A1F8300BC2FC1 /* Harmony_Drive.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF525EE81FF5F370004AA849 /* DeepLinkController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF525EE71FF5F370004AA849 /* DeepLinkController.swift */; };
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF525EE91FF6CD12004AA849 /* DeepLink.swift */; };
BF56450D220239B800A8EA26 /* GameControllerInputMappingMigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF56450C220239B800A8EA26 /* GameControllerInputMappingMigrationPolicy.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 */; };
@ -75,6 +87,8 @@
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942921E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift */; };
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */; };
BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF5E7F451B9A652600AE44F8 /* Settings.storyboard */; };
BF63A1A321A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63A1A221A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift */; };
BF63A1B521A4B76E00EE8F61 /* RecordVersionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63A1B421A4B76E00EE8F61 /* RecordVersionsViewController.swift */; };
BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6424821F5B8F3F00D6AB44 /* ListMenuViewController.swift */; };
BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */; };
BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6866161DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift */; };
@ -93,6 +107,8 @@
BF7AE8081C2E858400B1B5BC /* GridMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8041C2E858400B1B5BC /* GridMenuViewController.swift */; };
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7AE8091C2E8C7600B1B5BC /* UIColor+Delta.swift */; };
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */; };
BF8A333421A484A000A42FD4 /* BadgedTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8A333321A484A000A42FD4 /* BadgedTableViewCell.swift */; };
BF8A334621A4926F00A42FD4 /* GameSyncStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8A334521A4926F00A42FD4 /* GameSyncStatusViewController.swift */; };
BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */; };
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */; };
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF95E2761E4977BF0030E7AD /* GameMetadata.swift */; };
@ -104,14 +120,20 @@
BF9F4FD01AAD7B87004C9500 /* DeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63BDE91D389EEB00FCB040 /* GameViewController.swift */; };
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.swift */; };
BFAB9F7D219A43380080EC7D /* SyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAB9F7C219A43380080EC7D /* SyncManager.swift */; };
BFAB9F88219A4B670080EC7D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = BFAB9F87219A4B670080EC7D /* GoogleService-Info.plist */; };
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */; };
BFC3628021ADE2BA00EF2BE6 /* UIAlertController+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC3627F21ADE2BA00EF2BE6 /* UIAlertController+Error.swift */; };
BFC6F7B81F435BC500221B96 /* Input+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC6F7B71F435BC500221B96 /* Input+Display.swift */; };
BFC9B7391CEFCD34008629BB /* CheatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */; };
BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */; };
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */; };
BFDB3418219E4B1700595A62 /* SyncStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB3417219E4B1700595A62 /* SyncStatusViewController.swift */; };
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */; };
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */; };
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */; };
BFE593CA21F3F8B7003412A6 /* GameSave.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE593C921F3F8B7003412A6 /* GameSave.swift */; };
BFE593CC21F3F8C2003412A6 /* _GameSave.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE593CB21F3F8C2003412A6 /* _GameSave.swift */; };
BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; };
BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */; };
@ -137,6 +159,8 @@
dstSubfolderSpec = 10;
files = (
BF9F4FD01AAD7B87004C9500 /* DeltaCore.framework in Embed Frameworks */,
BF48F75C219A1F8A00BC2FC1 /* Harmony_Drive.framework in Embed Frameworks */,
BF48F756219A1EF000BC2FC1 /* Harmony.framework in Embed Frameworks */,
BFF0742D1E9DC17500ACDF4A /* GBCDeltaCore.framework in Embed Frameworks */,
BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */,
BF0418151D01E93400E85BCF /* GBADeltaCore.framework in Embed Frameworks */,
@ -156,6 +180,7 @@
BF0418131D01E93400E85BCF /* GBADeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GBADeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF04E6FE1DB8625C000F35D3 /* ControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerSkinsViewController.swift; sourceTree = "<group>"; };
BF07200E219A3A9500F05DA4 /* ZIPFoundation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ZIPFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF0758DE2202827C005110F2 /* Delta 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Delta 4.xcdatamodel"; sourceTree = "<group>"; };
BF090CF11B490D8300DCAB45 /* Delta-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Delta-Bridging-Header.h"; sourceTree = "<group>"; };
BF1020E21F95B05B00313182 /* DeltaToDelta2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = DeltaToDelta2.xcmappingmodel; sourceTree = "<group>"; };
BF107EC31BF413F000E0C32C /* GamesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesViewController.swift; sourceTree = "<group>"; };
@ -165,6 +190,10 @@
BF15AF831F54B43B009B6AAB /* ActionInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionInput.swift; sourceTree = "<group>"; };
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Importing.swift"; sourceTree = "<group>"; };
BF1DAD5C1D9F576000E752A7 /* SystemControllerSkinsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemControllerSkinsViewController.swift; sourceTree = "<group>"; };
BF1F45A321AF274D00EF9895 /* SyncResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncResultViewController.swift; sourceTree = "<group>"; };
BF1F45AA21AF4B5800EF9895 /* SyncResultsViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SyncResultsViewController.storyboard; sourceTree = "<group>"; };
BF1F45AC21AF57BA00EF9895 /* HarmonyMetadataKey+Keys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HarmonyMetadataKey+Keys.swift"; sourceTree = "<group>"; };
BF1F45BE21AF676F00EF9895 /* Box.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = "<group>"; };
BF27CC861BC9E3C600A20D89 /* Delta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Delta.entitlements; sourceTree = "<group>"; };
BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pods.framework; path = "Pods/../build/Debug-appletvos/Pods.framework"; sourceTree = "<group>"; };
BF27CC941BCB7B7A00A20D89 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.0.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; };
@ -180,12 +209,19 @@
BF3540011C5DA3D500C1184C /* PauseStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PauseStoryboardSegue.swift; sourceTree = "<group>"; };
BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveStatesViewController.swift; sourceTree = "<group>"; };
BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PauseTransitionCoordinator.swift; sourceTree = "<group>"; };
BF3D6C502202865F0083E05A /* Delta2ToDelta3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = Delta2ToDelta3.xcmappingmodel; sourceTree = "<group>"; };
BF3D6C52220286750083E05A /* Delta3ToDelta4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = Delta3ToDelta4.xcmappingmodel; sourceTree = "<group>"; };
BF4828821F9027B600028B97 /* Delta 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Delta 2.xcdatamodel"; sourceTree = "<group>"; };
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>"; };
BF48F74D219A16DA00BC2FC1 /* SyncingServicesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncingServicesViewController.swift; sourceTree = "<group>"; };
BF48F754219A1EEB00BC2FC1 /* Harmony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Harmony.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF48F75A219A1F8300BC2FC1 /* Harmony_Drive.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Harmony_Drive.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
BF5645092202381000A8EA26 /* Delta 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Delta 3.xcdatamodel"; sourceTree = "<group>"; };
BF56450C220239B800A8EA26 /* GameControllerInputMappingMigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameControllerInputMappingMigrationPolicy.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>"; };
@ -211,6 +247,8 @@
BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
BF5E7F451B9A652600AE44F8 /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Settings.storyboard; path = Delta/Base.lproj/Settings.storyboard; sourceTree = SOURCE_ROOT; };
BF616A121F08184A0077F8B2 /* ControllerInputsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControllerInputsViewController.swift; sourceTree = "<group>"; };
BF63A1A221A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordSyncStatusViewController.swift; sourceTree = "<group>"; };
BF63A1B421A4B76E00EE8F61 /* RecordVersionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordVersionsViewController.swift; sourceTree = "<group>"; };
BF63BDE91D389EEB00FCB040 /* GameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = "<group>"; };
BF6424821F5B8F3F00D6AB44 /* ListMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListMenuViewController.swift; sourceTree = "<group>"; };
BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+ParentViewController.swift"; sourceTree = "<group>"; };
@ -232,6 +270,8 @@
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>"; };
BF8A333321A484A000A42FD4 /* BadgedTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgedTableViewCell.swift; sourceTree = "<group>"; };
BF8A334521A4926F00A42FD4 /* GameSyncStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSyncStatusViewController.swift; sourceTree = "<group>"; };
BF8CA9351F5F651900499FDD /* PopoverMenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverMenuController.swift; sourceTree = "<group>"; };
BF8DDD231F4F6C880088A21B /* InputCalloutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputCalloutView.swift; sourceTree = "<group>"; };
BF95E2761E4977BF0030E7AD /* GameMetadata.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameMetadata.swift; sourceTree = "<group>"; };
@ -239,14 +279,20 @@
BF99A5961DC2F9C400468E9E /* ControllerSkinTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllerSkinTableViewCell.swift; sourceTree = "<group>"; };
BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFAA1FEC1B8AA4FA00495943 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
BFAB9F7C219A43380080EC7D /* SyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncManager.swift; sourceTree = "<group>"; };
BFAB9F87219A4B670080EC7D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
BFBAB2E21EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DeltaCoreProtocol+Delta.swift"; sourceTree = "<group>"; };
BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SNESDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BFC3627F21ADE2BA00EF2BE6 /* UIAlertController+Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Error.swift"; sourceTree = "<group>"; };
BFC6F7B71F435BC500221B96 /* Input+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Input+Display.swift"; sourceTree = "<group>"; };
BFC9B7381CEFCD34008629BB /* CheatsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheatsViewController.swift; sourceTree = "<group>"; };
BFCEA67D1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewControllerContextTransitioning+Conveniences.swift"; sourceTree = "<group>"; };
BFDB3417219E4B1700595A62 /* SyncStatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStatusViewController.swift; sourceTree = "<group>"; };
BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollectionViewController.swift; sourceTree = "<group>"; };
BFE0229F1F5B577D0052D888 /* PopoverMenuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverMenuButton.swift; sourceTree = "<group>"; };
BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveStatesStoryboardSegue.swift; sourceTree = "<group>"; };
BFE593C921F3F8B7003412A6 /* GameSave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameSave.swift; sourceTree = "<group>"; };
BFE593CB21F3F8C2003412A6 /* _GameSave.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _GameSave.swift; sourceTree = "<group>"; };
BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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; };
@ -271,10 +317,12 @@
files = (
BF9F4FCF1AAD7B87004C9500 /* DeltaCore.framework in Frameworks */,
BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */,
BF48F75B219A1F8A00BC2FC1 /* Harmony_Drive.framework in Frameworks */,
BF99C6941D0A9AA600BA92BC /* SNESDeltaCore.framework in Frameworks */,
BF0418141D01E93400E85BCF /* GBADeltaCore.framework in Frameworks */,
BF072010219A3A9D00F05DA4 /* ZIPFoundation.framework in Frameworks */,
BFF0742C1E9DC17500ACDF4A /* GBCDeltaCore.framework in Frameworks */,
BF48F755219A1EF000BC2FC1 /* Harmony.framework in Frameworks */,
4FE8465FD28810191C3E5212 /* Pods_Delta.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -298,6 +346,8 @@
BF18B61E1E2985F900F70067 /* UIAlertController+Importing.swift */,
BFC6F7B71F435BC500221B96 /* Input+Display.swift */,
BF6424841F5CBDC900D6AB44 /* UIView+ParentViewController.swift */,
BFC3627F21ADE2BA00EF2BE6 /* UIAlertController+Error.swift */,
BF1F45AC21AF57BA00EF9895 /* HarmonyMetadataKey+Keys.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -350,6 +400,18 @@
path = "Game Selection";
sourceTree = "<group>";
};
BF48F74C219A16C100BC2FC1 /* Syncing */ = {
isa = PBXGroup;
children = (
BF48F74D219A16DA00BC2FC1 /* SyncingServicesViewController.swift */,
BFDB3417219E4B1700595A62 /* SyncStatusViewController.swift */,
BF8A334521A4926F00A42FD4 /* GameSyncStatusViewController.swift */,
BF63A1A221A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift */,
BF63A1B421A4B76E00EE8F61 /* RecordVersionsViewController.swift */,
);
path = Syncing;
sourceTree = "<group>";
};
BF525EE61FF5F355004AA849 /* Deep Linking */ = {
isa = PBXGroup;
children = (
@ -363,6 +425,7 @@
isa = PBXGroup;
children = (
BF4828871F90290F00028B97 /* Action.swift */,
BF1F45BE21AF676F00EF9895 /* Box.swift */,
BFE0229C1F5B56840052D888 /* Popover Menu */,
BF5942671E09BBB70051894B /* Collection View */,
BF71CF881FE90471001F1613 /* Table View */,
@ -420,6 +483,7 @@
BF5942791E09BC830051894B /* Game.swift */,
BF59427A1E09BC830051894B /* GameCollection.swift */,
BF6EE5EA1F7C5F8F0051AD6C /* GameControllerInputMapping.swift */,
BFE593C921F3F8B7003412A6 /* GameSave.swift */,
BF59427B1E09BC830051894B /* SaveState.swift */,
);
path = Human;
@ -433,6 +497,7 @@
BF5942831E09BC8B0051894B /* _Game.swift */,
BF5942841E09BC8B0051894B /* _GameCollection.swift */,
BF6EE5E81F7C5F860051AD6C /* _GameControllerInputMapping.swift */,
BFE593CB21F3F8C2003412A6 /* _GameSave.swift */,
BF5942851E09BC8B0051894B /* _SaveState.swift */,
);
path = Machine;
@ -496,6 +561,7 @@
children = (
BFFA4C081E8A24D600D87934 /* GameTableViewCell.swift */,
BF71CF891FE904B1001F1613 /* GameTableViewCell.xib */,
BF8A333321A484A000A42FD4 /* BadgedTableViewCell.swift */,
);
path = "Table View";
sourceTree = "<group>";
@ -547,6 +613,8 @@
isa = PBXGroup;
children = (
BF07200E219A3A9500F05DA4 /* ZIPFoundation.framework */,
BF48F754219A1EEB00BC2FC1 /* Harmony.framework */,
BF48F75A219A1F8300BC2FC1 /* Harmony_Drive.framework */,
BFF0742B1E9DC17500ACDF4A /* GBCDeltaCore.framework */,
BF0418131D01E93400E85BCF /* GBADeltaCore.framework */,
BF27CC941BCB7B7A00A20D89 /* GameController.framework */,
@ -570,10 +638,21 @@
BF71CF851FE8FFF1001F1613 /* App Icon Shortcuts */,
BF11734E1DA32CEC00047DF8 /* Controllers */,
BF1DAD5B1D9F574900E752A7 /* Controller Skins */,
BF48F74C219A16C100BC2FC1 /* Syncing */,
);
path = Settings;
sourceTree = "<group>";
};
BFAB9F7B219A43270080EC7D /* Syncing */ = {
isa = PBXGroup;
children = (
BFAB9F7C219A43380080EC7D /* SyncManager.swift */,
BF1F45A321AF274D00EF9895 /* SyncResultViewController.swift */,
BF1F45AA21AF4B5800EF9895 /* SyncResultsViewController.storyboard */,
);
path = Syncing;
sourceTree = "<group>";
};
BFC9B7371CEFCD08008629BB /* Cheats */ = {
isa = PBXGroup;
children = (
@ -609,6 +688,8 @@
isa = PBXGroup;
children = (
BF1020E21F95B05B00313182 /* DeltaToDelta2.xcmappingmodel */,
BF3D6C502202865F0083E05A /* Delta2ToDelta3.xcmappingmodel */,
BF3D6C52220286750083E05A /* Delta3ToDelta4.xcmappingmodel */,
BFEF24F11F7DD4BE00454C62 /* Policies */,
);
path = Migrations;
@ -618,6 +699,7 @@
isa = PBXGroup;
children = (
BFEF24F21F7DD4FB00454C62 /* SaveStateMigrationPolicy.swift */,
BF56450C220239B800A8EA26 /* GameControllerInputMappingMigrationPolicy.swift */,
);
path = Policies;
sourceTree = "<group>";
@ -652,6 +734,7 @@
BF7AE7FA1C2E851F00B1B5BC /* Pause Menu */,
BFAA1FEB1B8AA4E800495943 /* Settings */,
BF59426C1E09BC450051894B /* Database */,
BFAB9F7B219A43270080EC7D /* Syncing */,
BF59428C1E09BCE50051894B /* Importing */,
BF930FFB1EB6D6EC00E8DBA0 /* Systems */,
BF525EE61FF5F355004AA849 /* Deep Linking */,
@ -669,6 +752,7 @@
BF090CF11B490D8300DCAB45 /* Delta-Bridging-Header.h */,
BF27CC861BC9E3C600A20D89 /* Delta.entitlements */,
BFFA71DB1AAC406100EE9DD1 /* Info.plist */,
BFAB9F87219A4B670080EC7D /* GoogleService-Info.plist */,
);
path = "Supporting Files";
sourceTree = "<group>";
@ -725,7 +809,6 @@
BFFA71D51AAC406100EE9DD1 /* Resources */,
BF9F4FCC1AAD7AEE004C9500 /* Embed Frameworks */,
B444B2BB31CBCEE7D86E943D /* [CP] Embed Pods Frameworks */,
3521C8FA1BB6004A6E9FE324 /* [CP] Copy Pods Resources */,
BF6BF3281EB897F6008E83CD /* Fabric */,
);
buildRules = (
@ -791,11 +874,13 @@
files = (
BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */,
BF6BF3211EB82362008E83CD /* GamesDatabase.storyboard in Resources */,
BFAB9F88219A4B670080EC7D /* GoogleService-Info.plist in Resources */,
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 */,
BF1F45AB21AF4B5800EF9895 /* SyncResultsViewController.storyboard in Resources */,
BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */,
BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */,
);
@ -804,30 +889,22 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3521C8FA1BB6004A6E9FE324 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Delta/Pods-Delta-resources.sh\"\n";
showEnvVarsInLog = 0;
};
B444B2BB31CBCEE7D86E943D /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Delta/Pods-Delta-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/SMCalloutView/SMCalloutView.framework",
"${BUILT_PRODUCTS_DIR}/SQLite.swift/SQLite.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SMCalloutView.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLite.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -868,13 +945,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Delta-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@ -892,13 +972,19 @@
BFBAB2E31EB685A2004E0B0E /* DeltaCoreProtocol+Delta.swift in Sources */,
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */,
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */,
BF8A334621A4926F00A42FD4 /* GameSyncStatusViewController.swift in Sources */,
BF59427E1E09BC830051894B /* Game.swift in Sources */,
BFE593CC21F3F8C2003412A6 /* _GameSave.swift in Sources */,
BF63A1A321A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift in Sources */,
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
BFA0D1271D3AE1F600565894 /* GameViewController.swift in Sources */,
BF59428A1E09BC8B0051894B /* _SaveState.swift in Sources */,
BF5942801E09BC830051894B /* SaveState.swift in Sources */,
BF59428E1E09BCFB0051894B /* ImportController.swift in Sources */,
BF13A7581D5D2FD9000BB055 /* EmulatorCore+Cheats.swift in Sources */,
BF48F74E219A16DA00BC2FC1 /* SyncingServicesViewController.swift in Sources */,
BFE593CA21F3F8B7003412A6 /* GameSave.swift in Sources */,
BF63A1B521A4B76E00EE8F61 /* RecordVersionsViewController.swift in Sources */,
BF6424831F5B8F3F00D6AB44 /* ListMenuViewController.swift in Sources */,
BF1020E31F95B05B00313182 /* DeltaToDelta2.xcmappingmodel in Sources */,
BF6BF3131EB7E47F008E83CD /* ImportOption.swift in Sources */,
@ -907,7 +993,9 @@
BF15AF841F54B43B009B6AAB /* ActionInput.swift in Sources */,
BF4828841F9027B600028B97 /* Delta.xcdatamodeld in Sources */,
BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */,
BFC3628021ADE2BA00EF2BE6 /* UIAlertController+Error.swift in Sources */,
BF353FF91C5D870B00C1184C /* MenuItem.swift in Sources */,
BFDB3418219E4B1700595A62 /* SyncStatusViewController.swift in Sources */,
BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */,
BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */,
BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */,
@ -918,6 +1006,7 @@
BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */,
BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */,
BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */,
BF1F45AD21AF57BA00EF9895 /* HarmonyMetadataKey+Keys.swift in Sources */,
BF71CF871FE90006001F1613 /* AppIconShortcutsViewController.swift in Sources */,
BF1DAD5D1D9F576000E752A7 /* SystemControllerSkinsViewController.swift in Sources */,
BFE022A01F5B57FF0052D888 /* PopoverMenuButton.swift in Sources */,
@ -927,7 +1016,10 @@
BF8DDD241F4F6C880088A21B /* InputCalloutView.swift in Sources */,
BF5942701E09BC5D0051894B /* GamesDatabase.swift in Sources */,
BF34FA071CF0F510006624C7 /* EditCheatViewController.swift in Sources */,
BF1F45A421AF274D00EF9895 /* SyncResultViewController.swift in Sources */,
BF3D6C53220286750083E05A /* Delta3ToDelta4.xcmappingmodel in Sources */,
BF5942881E09BC8B0051894B /* _Game.swift in Sources */,
BF56450D220239B800A8EA26 /* GameControllerInputMappingMigrationPolicy.swift in Sources */,
BF95E2771E4977BF0030E7AD /* GameMetadata.swift in Sources */,
BFFC461F1D59823500AF2CC6 /* GamesStoryboardSegue.swift in Sources */,
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */,
@ -935,6 +1027,7 @@
BF353FFF1C5DA3C500C1184C /* PausePresentationController.swift in Sources */,
BFFC464C1D5998D600AF2CC6 /* CheatTableViewCell.swift in Sources */,
BF5942941E09BD1A0051894B /* NSManagedObject+Conveniences.swift in Sources */,
BF1F45BF21AF676F00EF9895 /* Box.swift in Sources */,
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */,
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */,
@ -944,13 +1037,16 @@
BF95E2791E4982A10030E7AD /* GamesDatabaseBrowserViewController.swift in Sources */,
BFD097211D3A01B8005A44C2 /* SaveStatesViewController.swift in Sources */,
BF6EE5EB1F7C5F8F0051AD6C /* GameControllerInputMapping.swift in Sources */,
BF8A333421A484A000A42FD4 /* BadgedTableViewCell.swift in Sources */,
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */,
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */,
BF3D6C512202865F0083E05A /* Delta2ToDelta3.xcmappingmodel in Sources */,
BF59426F1E09BC5D0051894B /* DatabaseManager.swift in Sources */,
BF4828861F9028F500028B97 /* System.swift in Sources */,
BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */,
BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */,
BF59427D1E09BC830051894B /* ControllerSkin.swift in Sources */,
BFAB9F7D219A43380080EC7D /* SyncManager.swift in Sources */,
BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */,
BF1173501DA32CF600047DF8 /* ControllersSettingsViewController.swift in Sources */,
BFFC461E1D59823500AF2CC6 /* GamesPresentationController.swift in Sources */,
@ -1079,6 +1175,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "-DGLES_SILENCE_DEPRECATION";
OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies";
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.Delta;
PRODUCT_NAME = Delta;
@ -1130,6 +1227,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "-DGLES_SILENCE_DEPRECATION";
OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies";
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.Delta;
PRODUCT_NAME = Delta;
@ -1146,8 +1244,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Delta/Supporting Files/Delta.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = 6XVY5G3U44;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "Delta/Supporting Files/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PROVISIONING_PROFILE = "";
@ -1165,8 +1265,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Delta/Supporting Files/Delta.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = 6XVY5G3U44;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = "Delta/Supporting Files/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DIMPACTOR";
@ -1214,10 +1316,12 @@
BF4828811F9027B600028B97 /* Delta.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
BF0758DE2202827C005110F2 /* Delta 4.xcdatamodel */,
BF5645092202381000A8EA26 /* Delta 3.xcdatamodel */,
BF4828821F9027B600028B97 /* Delta 2.xcdatamodel */,
BF4828831F9027B600028B97 /* Delta.xcdatamodel */,
);
currentVersion = BF4828821F9027B600028B97 /* Delta 2.xcdatamodel */;
currentVersion = BF0758DE2202827C005110F2 /* Delta 4.xcdatamodel */;
path = Delta.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

View File

@ -76,6 +76,34 @@
ReferencedContainer = "container:Cores/GBCDeltaCore/GBCDeltaCore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFA1C8D31ECD01C100DEA99D"
BuildableName = "Harmony.framework"
BlueprintName = "Harmony"
ReferencedContainer = "container:External/Harmony/Harmony.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFECF9F82016982D0012B9FC"
BuildableName = "Harmony_Drive.framework"
BlueprintName = "Harmony-Drive"
ReferencedContainer = "container:External/Harmony/Backends/Drive/Harmony-Drive.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
@ -145,7 +173,7 @@
<EnvironmentVariable
key = "OS_ACTIVITY_MODE"
value = "disable"
isEnabled = "YES">
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>

View File

@ -16,6 +16,9 @@
<FileRef
location = "group:Cores/GBCDeltaCore/GBCDeltaCore.xcodeproj">
</FileRef>
<FileRef
location = "group:External/Harmony/Harmony.xcodeproj">
</FileRef>
<FileRef
location = "group:External/Roxas/Roxas.xcodeproj">
</FileRef>

View File

@ -36,10 +36,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate
gestureRecognizer.delaysTouchesBegan = false
}
// Database
DatabaseManager.shared.loadPersistentStores { (description, error) in
}
// Controllers
ExternalGameControllerManager.shared.startMonitoring()

View File

@ -1,11 +1,10 @@
<?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="SPq-Bk-fQl">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="SPq-Bk-fQl">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13526"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -42,11 +41,18 @@
<segue destination="xMK-Cs-fAS" kind="presentation" id="uN5-PN-7FK"/>
</connections>
</barButtonItem>
<barButtonItem key="rightBarButtonItem" systemItem="add" id="FeA-O5-xd2">
<connections>
<action selector="importFiles" destination="jeE-WD-wXO" id="A1s-kE-NkM"/>
</connections>
</barButtonItem>
<rightBarButtonItems>
<barButtonItem systemItem="add" id="FeA-O5-xd2">
<connections>
<action selector="importFiles" destination="jeE-WD-wXO" id="A1s-kE-NkM"/>
</connections>
</barButtonItem>
<barButtonItem title="Sync" id="6Fp-px-Dow">
<connections>
<action selector="sync" destination="jeE-WD-wXO" id="FFs-LZ-GEr"/>
</connections>
</barButtonItem>
</rightBarButtonItems>
</navigationItem>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="JYx-xE-nis" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -238,7 +244,7 @@
</scene>
</scenes>
<resources>
<image name="SettingsButton" width="16" height="16"/>
<image name="SettingsButton" width="22" height="22"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="Tey-6Z-UHp"/>

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<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="ssH-mM-uG6">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ssH-mM-uG6">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -19,7 +18,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="673" width="375" height="44"/>
<rect key="frame" x="0.0" y="817" 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,17 +221,68 @@
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Syncing" id="y6U-7a-bnX" userLabel="Syncing">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="DetailCell" textLabel="4U1-fe-PIb" detailTextLabel="kLY-5g-v8n" style="IBUITableViewCellStyleValue1" id="bwW-PG-BcV">
<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="bwW-PG-BcV" id="RNA-99-evH">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Service" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="4U1-fe-PIb">
<rect key="frame" x="16" y="12" width="54" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Google Drive" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="kLY-5g-v8n">
<rect key="frame" x="246" y="12" width="94" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="R9m-rV-VgV" kind="show" id="MAM-GM-FlH"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="BadgeCell" textLabel="1u0-gh-zP7" style="IBUITableViewCellStyleDefault" id="JPg-6O-DRe" customClass="BadgedTableViewCell" customModule="Delta" customModuleProvider="target">
<rect key="frame" x="0.0" y="655" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="JPg-6O-DRe" id="zcZ-QR-Nno">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Status" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="1u0-gh-zP7">
<rect key="frame" x="16" y="0.0" width="324" 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="vVg-ci-JRa" kind="show" id="Hfy-lJ-4tW"/>
</connections>
</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"/>
<rect key="frame" x="0.0" y="755" 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"/>
<rect key="frame" x="0.0" y="0.0" width="341" 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"/>
<rect key="frame" x="16" y="0.0" width="324" 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"/>
@ -262,6 +312,7 @@
<connections>
<outlet property="controllerOpacityLabel" destination="zaz-yD-CYG" id="eUW-u9-xxx"/>
<outlet property="controllerOpacitySlider" destination="whi-If-wFf" id="6Cx-HY-xLG"/>
<outlet property="syncingServiceLabel" destination="kLY-5g-v8n" id="zzx-qM-q1g"/>
<outlet property="versionLabel" destination="Str-BY-agW" id="gU2-L0-pYt"/>
<segue destination="uBz-mm-mXr" kind="show" identifier="controllersSegue" id="MLY-hF-UB8"/>
<segue destination="56e-ul-z6v" kind="show" identifier="controllerSkinsSegue" id="GNM-Gt-YFf"/>
@ -342,7 +393,7 @@
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="3809" y="471"/>
<point key="canvasLocation" x="4102" y="-244"/>
</scene>
<!--Controllers-->
<scene sceneID="swa-DT-VKS">
@ -391,7 +442,7 @@
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="owG-Kh-rfn" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2221" y="471"/>
<point key="canvasLocation" x="2513" y="-244"/>
</scene>
<!--Game Boy Advance-->
<scene sceneID="pkL-Te-puh">
@ -463,7 +514,7 @@
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Nx1-Ly-oRu" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2220" y="1181.5592203898052"/>
<point key="canvasLocation" x="2513" y="466"/>
</scene>
<!--App Icon Shortcuts-->
<scene sceneID="e9N-fv-yuQ">
@ -482,7 +533,7 @@
<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"/>
<rect key="frame" x="310" y="6.5" width="51" height="31"/>
<connections>
<action selector="switchGameShortcutsModeWith:" destination="yXS-6u-1AN" eventType="valueChanged" id="bX8-gd-h4g"/>
</connections>
@ -508,7 +559,7 @@
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Lzk-4m-LKw" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2220" y="1897"/>
<point key="canvasLocation" x="2513" y="1182"/>
</scene>
<!--Controller Skins-->
<scene sceneID="IN0-an-SWm">
@ -556,7 +607,7 @@
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="54U-JB-wBG" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2976.8000000000002" y="1181.5592203898052"/>
<point key="canvasLocation" x="3270" y="466"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="8qd-VB-Uy5">
@ -574,7 +625,7 @@
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="HMI-Ep-MdI" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="889" y="471"/>
<point key="canvasLocation" x="708" y="471"/>
</scene>
<!--Grid Menu View Controller-->
<scene sceneID="Lgi-Ii-M1W">
@ -608,7 +659,7 @@
</collectionViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="pRg-BA-3KK" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="4566" y="-216"/>
<point key="canvasLocation" x="4858" y="-931"/>
</scene>
<!--Game View Controller-->
<scene sceneID="qAz-yz-iOc">
@ -626,7 +677,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="uQK-ch-9AG" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="4566" y="471"/>
<point key="canvasLocation" x="4858" y="-244"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="bwW-s2-fcE">
@ -645,7 +696,538 @@
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="D4f-Fb-zfa" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2977" y="471"/>
<point key="canvasLocation" x="3270" y="-244"/>
</scene>
<!--Syncing Service-->
<scene sceneID="8nM-uV-t0b">
<objects>
<tableViewController title="Syncing Service" id="R9m-rV-VgV" customClass="SyncingServicesViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="44" sectionHeaderHeight="18" sectionFooterHeight="18" id="Zsb-6q-tLe">
<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"/>
<sections>
<tableViewSection headerTitle="Service" id="mIB-Au-dYz">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="QAp-WA-1g3" style="IBUITableViewCellStyleDefault" id="vkb-8K-t7E">
<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" tableViewCell="vkb-8K-t7E" id="YcK-vq-ABN">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="None" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QAp-WA-1g3">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="4fb-TC-FrG" style="IBUITableViewCellStyleDefault" id="hBZ-Fp-9Kh">
<rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="hBZ-Fp-9Kh" id="rfN-2N-L43">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Google Drive" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="4fb-TC-FrG">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Account" id="1Wk-cG-HDE">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" textLabel="AYq-XK-j5L" style="IBUITableViewCellStyleDefault" id="nrN-mu-0HX">
<rect key="frame" x="0.0" y="199.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="nrN-mu-0HX" id="lHU-qJ-uhj">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="riley@rileytestut.com" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="AYq-XK-j5L">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="" id="Jnq-12-IOu">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" textLabel="4TQ-cm-2sN" style="IBUITableViewCellStyleDefault" id="wRv-En-k1Y">
<rect key="frame" x="0.0" y="279.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="wRv-En-k1Y" id="7QF-ID-Gu2">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Sign Out" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="4TQ-cm-2sN">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.99101024869999998" green="0.27251276369999999" blue="0.0051303170620000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
<connections>
<outlet property="dataSource" destination="R9m-rV-VgV" id="PXt-S6-euC"/>
<outlet property="delegate" destination="R9m-rV-VgV" id="qqu-iI-H9F"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="uqz-XU-aTr" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2513" y="1872"/>
</scene>
<!--Sync Status-->
<scene sceneID="IYe-DI-U7i">
<objects>
<tableViewController title="Sync Status" id="vVg-ci-JRa" customClass="SyncStatusViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="Nza-ON-XbS">
<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" preservesSuperviewLayoutMargins="YES" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="AWh-ik-Gvu" style="IBUITableViewCellStyleDefault" id="wpv-cf-duw" customClass="BadgedTableViewCell" customModule="Delta" customModuleProvider="target">
<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" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="wpv-cf-duw" id="ehC-Qx-Zx4">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="AWh-ik-Gvu">
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="OnX-sX-bHK" kind="show" identifier="showGame" id="vUN-0T-oaK"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PreviousSyncCell" textLabel="Piz-MQ-rK5" style="IBUITableViewCellStyleDefault" id="tdO-nZ-AtU">
<rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="tdO-nZ-AtU" id="Ecj-ul-03q">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Previous Sync" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Piz-MQ-rK5">
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" name="Purple"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="NCG-b5-gRT" kind="show" identifier="showPreviousSyncResults" id="e0p-do-4bJ"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="vVg-ci-JRa" id="Ch3-KN-Vuq"/>
<outlet property="delegate" destination="vVg-ci-JRa" id="WMN-hE-f2P"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="kT7-Ig-nyf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3351" y="1872"/>
</scene>
<!--syncResultViewController-->
<scene sceneID="p0H-JM-YTw">
<objects>
<viewControllerPlaceholder storyboardName="SyncResultsViewController" referencedIdentifier="syncResultViewController" id="NCG-b5-gRT" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="X4J-BU-m3d" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3350" y="2281"/>
</scene>
<!--Game Sync Status View Controller-->
<scene sceneID="iQk-cq-qsQ">
<objects>
<tableViewController id="OnX-sX-bHK" customClass="GameSyncStatusViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="AFt-Hn-fzR">
<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" preservesSuperviewLayoutMargins="YES" selectionStyle="gray" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="Cell" textLabel="RWL-4W-NpH" style="IBUITableViewCellStyleDefault" id="q5G-Db-MXt">
<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" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="q5G-Db-MXt" id="Ric-Km-AWj">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Game" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RWL-4W-NpH">
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="kBh-Lp-rBr" kind="show" identifier="showRecord" id="6lK-rC-DrT"/>
</connections>
</tableViewCell>
</prototypes>
<sections/>
<connections>
<outlet property="dataSource" destination="OnX-sX-bHK" id="TuS-hI-T9X"/>
<outlet property="delegate" destination="OnX-sX-bHK" id="vII-vu-IIw"/>
</connections>
</tableView>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="FHk-WU-IoC" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="4206" y="1872"/>
</scene>
<!--Record Sync Status View Controller-->
<scene sceneID="GG8-80-sV4">
<objects>
<tableViewController storyboardIdentifier="recordSyncStatusViewController" id="kBh-Lp-rBr" customClass="RecordSyncStatusViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="k6O-hT-4zC">
<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"/>
<sections>
<tableViewSection id="6pR-j6-CoP">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="none" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="SwitchCell" textLabel="QBt-p1-jpG" rowHeight="44" style="IBUITableViewCellStyleDefault" id="4jH-oK-0Z2">
<rect key="frame" x="0.0" y="35" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="4jH-oK-0Z2" id="SMG-3T-Z4f">
<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" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="K0v-VD-uUj">
<rect key="frame" x="163" y="6" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="onTintColor" name="Purple"/>
<connections>
<action selector="toggleSyncingEnabled:" destination="kBh-Lp-rBr" eventType="primaryActionTriggered" id="OoQ-XU-uYY"/>
</connections>
</switch>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Syncing Enabled" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QBt-p1-jpG">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</tableViewCellContentView>
<connections>
<outlet property="accessoryView" destination="K0v-VD-uUj" id="7H4-Y4-jCe"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Local" id="vpJ-Pg-nU9">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="Cell" textLabel="npF-wl-PPC" detailTextLabel="SYD-cR-5TY" style="IBUITableViewCellStyleValue1" id="9Dq-cm-tka">
<rect key="frame" x="0.0" y="135" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="9Dq-cm-tka" id="KqI-RZ-PSb">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Status" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="npF-wl-PPC">
<rect key="frame" x="16" y="12" width="49.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Updated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="SYD-cR-5TY">
<rect key="frame" x="292.5" y="12" width="66.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="Cell" textLabel="EFZ-Om-S4W" detailTextLabel="D5a-Na-NTh" style="IBUITableViewCellStyleValue1" id="1tW-nf-sYt">
<rect key="frame" x="0.0" y="179" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="1tW-nf-sYt" id="Unw-0A-JBl">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Date" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="EFZ-Om-S4W">
<rect key="frame" x="16" y="12" width="36" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="October 7, 1995" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="D5a-Na-NTh">
<rect key="frame" x="236.5" y="12" width="122.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="Remote" id="Bct-0y-ptf">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="Cell" textLabel="aeh-me-gZl" detailTextLabel="0Rm-b2-HX5" style="IBUITableViewCellStyleValue1" id="djI-O4-xho">
<rect key="frame" x="0.0" y="279" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="djI-O4-xho" id="tAf-KE-rZV">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Status" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="aeh-me-gZl">
<rect key="frame" x="16" y="12" width="49.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Updated" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="0Rm-b2-HX5">
<rect key="frame" x="292.5" y="12" width="66.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="Cell" textLabel="jzK-yT-69q" detailTextLabel="b43-lW-Enx" style="IBUITableViewCellStyleValue1" id="gAa-cx-WYJ">
<rect key="frame" x="0.0" y="323" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="gAa-cx-WYJ" id="ZsM-y1-TiF">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Date" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jzK-yT-69q">
<rect key="frame" x="16" y="12" width="36" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="October 7, 1995" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="b43-lW-Enx">
<rect key="frame" x="236.5" y="12" width="122.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="Cell" textLabel="r04-kp-xeN" detailTextLabel="VIa-dS-7SR" style="IBUITableViewCellStyleValue1" id="oac-qK-Po9">
<rect key="frame" x="0.0" y="367" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oac-qK-Po9" id="EDS-kC-xOc">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Device" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="r04-kp-xeN">
<rect key="frame" x="16" y="12" width="52.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Riley's iPhone 7" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="VIa-dS-7SR">
<rect key="frame" x="239.5" y="12" width="119.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection id="DP4-vw-Pfm">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="gray" indentationWidth="10" reuseIdentifier="VersionsCell" textLabel="8zd-va-DMf" style="IBUITableViewCellStyleDefault" id="Mzg-ek-npi">
<rect key="frame" x="0.0" y="447" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Mzg-ek-npi" id="Xw1-8U-8dU">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="View Versions" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8zd-va-DMf">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="zfS-39-NF3" kind="presentation" identifier="showVersions" id="xvh-Yg-gc5"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
<connections>
<outlet property="dataSource" destination="kBh-Lp-rBr" id="QaZ-pX-7Yr"/>
<outlet property="delegate" destination="kBh-Lp-rBr" id="Kl3-Sd-SAS"/>
</connections>
</tableView>
<connections>
<outlet property="localDateLabel" destination="D5a-Na-NTh" id="i8q-2d-4hk"/>
<outlet property="localStatusLabel" destination="SYD-cR-5TY" id="GOK-j1-D0y"/>
<outlet property="remoteDateLabel" destination="b43-lW-Enx" id="IYb-bX-oZJ"/>
<outlet property="remoteDeviceLabel" destination="VIa-dS-7SR" id="Cy1-pu-0vk"/>
<outlet property="remoteStatusLabel" destination="0Rm-b2-HX5" id="PUq-1s-SGy"/>
<outlet property="syncingEnabledSwitch" destination="K0v-VD-uUj" id="BSt-cy-p4F"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="fmY-uC-Naa" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="5047" y="1872"/>
</scene>
<!--Versions-->
<scene sceneID="xaH-AW-NqP">
<objects>
<tableViewController id="PGW-Yp-czd" customClass="RecordVersionsViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="9fJ-qb-tfO">
<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" preservesSuperviewLayoutMargins="YES" selectionStyle="gray" indentationWidth="10" reuseIdentifier="Cell" textLabel="Itj-QW-4dw" detailTextLabel="2fS-9A-FKk" style="IBUITableViewCellStyleSubtitle" id="kAh-xQ-U0r">
<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" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="kAh-xQ-U0r" id="Uoy-xs-wjL">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Itj-QW-4dw">
<rect key="frame" x="16" y="5" width="33.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2fS-9A-FKk">
<rect key="frame" x="16" y="25.5" width="44" height="14.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="LoadingCell" id="lkg-MY-0hB">
<rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="lkg-MY-0hB" id="TM3-U4-9sx">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="hJz-RO-Pi8">
<rect key="frame" x="177.5" y="12" width="20" height="20"/>
</activityIndicatorView>
</subviews>
<constraints>
<constraint firstItem="hJz-RO-Pi8" firstAttribute="centerX" secondItem="TM3-U4-9sx" secondAttribute="centerX" id="dwn-pH-e6v"/>
<constraint firstItem="hJz-RO-Pi8" firstAttribute="centerY" secondItem="TM3-U4-9sx" secondAttribute="centerY" id="yIw-Ou-nWN"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="gray" indentationWidth="10" reuseIdentifier="ConfirmCell" textLabel="saf-eQ-eJc" style="IBUITableViewCellStyleDefault" id="x0b-KE-P94">
<rect key="frame" x="0.0" y="143.5" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="x0b-KE-P94" id="ufh-yE-uv2">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Restore Version" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="saf-eQ-eJc">
<rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="PGW-Yp-czd" id="O27-2u-XUE"/>
<outlet property="delegate" destination="PGW-Yp-czd" id="qqj-9a-7id"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Versions" id="GLC-Lc-6VM">
<barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="LMc-KQ-vea">
<connections>
<segue destination="oyk-u7-Dn0" kind="unwind" unwindAction="unwindToRecordSyncStatusViewController:" id="JOO-Bj-i1E"/>
</connections>
</barButtonItem>
</navigationItem>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="p6Y-9f-cL2" userLabel="First Responder" sceneMemberID="firstResponder"/>
<exit id="oyk-u7-Dn0" userLabel="Exit" sceneMemberID="exit"/>
</objects>
<point key="canvasLocation" x="5807" y="1182"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="J41-NY-ZAz">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="zfS-39-NF3" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="iJQ-8h-Ho6">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="PGW-Yp-czd" kind="relationship" relationship="rootViewController" id="ybZ-FQ-HsP"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bJV-tR-yMT" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="5046" y="1182"/>
</scene>
</scenes>
<resources>
<namedColor name="Purple">
<color red="0.54509803921568623" green="0.15686274509803921" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View File

@ -0,0 +1,19 @@
//
// Box.swift
// Delta
//
// Created by Riley Testut on 11/28/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import Foundation
class Box<T>
{
let value: T
init(_ value: T)
{
self.value = value
}
}

View File

@ -0,0 +1,73 @@
//
// BadgedTableViewCell.swift
// Delta
//
// Created by Riley Testut on 11/20/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import UIKit
class BadgedTableViewCell: UITableViewCell
{
let badgeLabel = UILabel()
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.initialize()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
{
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.initialize()
}
private func initialize()
{
self.badgeLabel.clipsToBounds = true
self.badgeLabel.textAlignment = .center
self.badgeLabel.backgroundColor = .red
self.badgeLabel.font = UIFont.boldSystemFont(ofSize: 14)
self.badgeLabel.textColor = .white
self.contentView.addSubview(self.badgeLabel)
}
override func layoutSubviews()
{
super.layoutSubviews()
guard let textLabel = self.textLabel else { return }
let spacing = 8 as CGFloat
var contentSize = self.badgeLabel.intrinsicContentSize
contentSize.width += 10
contentSize.height += 10
contentSize.width = max(contentSize.width, contentSize.height)
var frame = CGRect(x: self.contentView.bounds.maxX - contentSize.width,
y: self.contentView.bounds.midY - contentSize.height / 2,
width: contentSize.width,
height: contentSize.height)
if self.accessoryType == .none
{
frame.origin.x -= spacing
}
self.badgeLabel.frame = frame
self.badgeLabel.layer.cornerRadius = frame.height / 2
self.badgeLabel.backgroundColor = .red
let overlap = textLabel.frame.maxX - (frame.minX - spacing)
if overlap > 0 && !self.badgeLabel.isHidden
{
textLabel.frame.size.width -= overlap
}
}
}

View File

@ -11,11 +11,10 @@ import CoreData
// Workspace
import DeltaCore
import Harmony
import Roxas
import ZIPFoundation
// Pods
import FileMD5Hash
extension DatabaseManager
{
enum ImportError: Error, Hashable
@ -56,10 +55,12 @@ extension DatabaseManager
}
}
final class DatabaseManager: NSPersistentContainer
final class DatabaseManager: RSTPersistentContainer
{
static let shared = DatabaseManager()
private(set) var isStarted = false
private var gamesDatabase: GamesDatabase? = nil
private var validationManagedObjectContext: NSManagedObjectContext?
@ -68,30 +69,41 @@ final class DatabaseManager: NSPersistentContainer
{
guard
let modelURL = Bundle(for: DatabaseManager.self).url(forResource: "Delta", withExtension: "momd"),
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL),
let harmonyModel = NSManagedObjectModel.harmonyModel(byMergingWith: [managedObjectModel])
else { fatalError("Core Data model cannot be found. Aborting.") }
super.init(name: "Delta", managedObjectModel: managedObjectModel)
super.init(name: "Delta", managedObjectModel: harmonyModel)
self.viewContext.automaticallyMergesChangesFromParent = true
self.shouldAddStoresAsynchronously = true
}
}
extension DatabaseManager
{
override func newBackgroundContext() -> NSManagedObjectContext
func start(completionHandler: @escaping (Error?) -> Void)
{
let context = super.newBackgroundContext()
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}
override func loadPersistentStores(completionHandler block: @escaping (NSPersistentStoreDescription, Error?) -> Void)
{
super.loadPersistentStores { (description, error) in
self.prepareDatabase {
block(description, error)
guard !self.isStarted else { return }
do
{
if !FileManager.default.fileExists(atPath: DatabaseManager.backupDirectoryURL.path)
{
try FileManager.default.copyItem(at: DatabaseManager.defaultDirectoryURL(), to: DatabaseManager.backupDirectoryURL)
}
self.loadPersistentStores { (description, error) in
guard error == nil else { return completionHandler(error) }
self.prepareDatabase {
self.isStarted = true
completionHandler(nil)
}
}
}
catch
{
completionHandler(error)
}
}
}
@ -209,7 +221,18 @@ extension DatabaseManager
continue
}
let identifier = FileHash.sha1HashOfFile(atPath: url.path) as String
let identifier: String
do
{
identifier = try RSTHasher.sha1HashOfFile(at: url)
}
catch let error as NSError
{
errors.insert(.unknown(url, error))
continue
}
let filename = identifier + "." + url.pathExtension
let game = Game(context: context)
@ -494,6 +517,12 @@ extension DatabaseManager
let artworkURL = gameURL.deletingPathExtension().appendingPathExtension("jpg")
return artworkURL
}
class var backupDirectoryURL: URL
{
let backupDirectoryURL = FileManager.default.documentsDirectory.appendingPathComponent("Database-Backup")
return backupDirectoryURL
}
}
//MARK: - Notifications -

View File

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Delta 2.xcdatamodel</string>
<string>Delta 4.xcdatamodel</string>
</dict>
</plist>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13772" systemVersion="16G1114" minimumToolsVersion="Xcode 7.0" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="1.0">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="13240" systemVersion="16G29" 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,7 +54,6 @@
</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"/>
@ -115,7 +114,7 @@
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="2" usesScalarValueType="NO" syncable="YES">
<attribute name="type" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="NO" syncable="YES">
<userInfo>
<entry key="attributeValueScalarType" value="SaveStateType"/>
</userInfo>
@ -131,9 +130,9 @@
<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="195"/>
<element name="Game" positionX="-378" positionY="-54" width="128" height="180"/>
<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"/>
</elements>
</model>
</model>

View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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"/>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="isEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="NO" syncable="YES"/>
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="type" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="CheatType"/>
</userInfo>
</attribute>
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="cheats" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="ControllerSkin" representedClassName="ControllerSkin" syncable="YES">
<attribute name="filename" attributeType="String" syncable="YES"/>
<attribute name="gameType" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameType"/>
</userInfo>
</attribute>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="isStandard" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="supportedConfigurations" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES">
<userInfo>
<entry key="attributeValueScalarType" value="ControllerSkinConfigurations"/>
</userInfo>
</attribute>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
<constraint value="gameType"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Game" representedClassName="Game" syncable="YES">
<attribute name="artworkURL" optional="YES" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="URL"/>
</userInfo>
</attribute>
<attribute name="filename" attributeType="String" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="NSURL"/>
</userInfo>
</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"/>
</userInfo>
</attribute>
<relationship name="cheats" toMany="YES" deletionRule="Cascade" destinationEntity="Cheat" inverseName="game" inverseEntity="Cheat" syncable="YES"/>
<relationship name="gameCollections" toMany="YES" deletionRule="Nullify" destinationEntity="GameCollection" inverseName="games" inverseEntity="GameCollection" syncable="YES"/>
<relationship name="previewSaveState" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SaveState" inverseName="previewGame" inverseEntity="SaveState" syncable="YES"/>
<relationship name="saveStates" toMany="YES" deletionRule="Cascade" destinationEntity="SaveState" inverseName="game" inverseEntity="SaveState" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="GameCollection" representedClassName="GameCollection" syncable="YES">
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="index" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<relationship name="games" toMany="YES" deletionRule="Nullify" destinationEntity="Game" inverseName="gameCollections" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="GameControllerInputMapping" representedClassName="GameControllerInputMapping" syncable="YES">
<attribute name="deltaCoreInputMapping" attributeType="Transformable" valueTransformerName="GameControllerInputMappingTransformer" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="Any"/>
</userInfo>
</attribute>
<attribute name="gameControllerInputType" attributeType="Transformable" valueTransformerName="" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameControllerInputType"/>
</userInfo>
</attribute>
<attribute name="gameType" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameType"/>
</userInfo>
</attribute>
<attribute name="playerIndex" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="gameControllerInputType"/>
<constraint value="gameType"/>
<constraint value="playerIndex"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="SaveState" representedClassName="SaveState" versionHashModifier="quick" syncable="YES">
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="filename" attributeType="String" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="NSURL"/>
</userInfo>
</attribute>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="2" usesScalarValueType="NO" syncable="YES">
<userInfo>
<entry key="attributeValueScalarType" value="SaveStateType"/>
</userInfo>
</attribute>
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="saveStates" inverseEntity="Game" syncable="YES"/>
<relationship name="previewGame" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="previewSaveState" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<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="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"/>
</elements>
</model>

View File

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14460.32" systemVersion="18C54" 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"/>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="isEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="NO" syncable="YES"/>
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="type" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="CheatType"/>
</userInfo>
</attribute>
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="cheats" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="ControllerSkin" representedClassName="ControllerSkin" syncable="YES">
<attribute name="filename" attributeType="String" syncable="YES"/>
<attribute name="gameType" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameType"/>
</userInfo>
</attribute>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="isStandard" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="supportedConfigurations" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" syncable="YES">
<userInfo>
<entry key="attributeValueScalarType" value="ControllerSkinConfigurations"/>
</userInfo>
</attribute>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
<constraint value="gameType"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Game" representedClassName="Game" syncable="YES">
<attribute name="artworkURL" optional="YES" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="URL"/>
</userInfo>
</attribute>
<attribute name="filename" attributeType="String" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="NSURL"/>
</userInfo>
</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"/>
</userInfo>
</attribute>
<relationship name="cheats" toMany="YES" deletionRule="Cascade" destinationEntity="Cheat" inverseName="game" inverseEntity="Cheat" syncable="YES"/>
<relationship name="gameCollection" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="GameCollection" inverseName="games" inverseEntity="GameCollection" syncable="YES"/>
<relationship name="gameSave" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="GameSave" inverseName="game" inverseEntity="GameSave" syncable="YES"/>
<relationship name="previewSaveState" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SaveState" inverseName="previewGame" inverseEntity="SaveState" syncable="YES"/>
<relationship name="saveStates" toMany="YES" deletionRule="Cascade" destinationEntity="SaveState" inverseName="game" inverseEntity="SaveState" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="GameCollection" representedClassName="GameCollection" syncable="YES">
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="index" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<relationship name="games" toMany="YES" deletionRule="Nullify" destinationEntity="Game" inverseName="gameCollection" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="GameControllerInputMapping" representedClassName="GameControllerInputMapping" syncable="YES">
<attribute name="deltaCoreInputMapping" attributeType="Transformable" valueTransformerName="GameControllerInputMappingTransformer" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="Any"/>
</userInfo>
</attribute>
<attribute name="gameControllerInputType" attributeType="Transformable" valueTransformerName="" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameControllerInputType"/>
</userInfo>
</attribute>
<attribute name="gameType" attributeType="Transformable" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="GameType"/>
</userInfo>
</attribute>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="playerIndex" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="gameControllerInputType"/>
<constraint value="gameType"/>
<constraint value="playerIndex"/>
</uniquenessConstraint>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="GameSave" representedClassName="GameSave" syncable="YES">
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="gameSave" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="SaveState" representedClassName="SaveState" versionHashModifier="quick" syncable="YES">
<attribute name="creationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="filename" attributeType="String" syncable="YES">
<userInfo>
<entry key="attributeValueClassName" value="NSURL"/>
</userInfo>
</attribute>
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="modifiedDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="2" usesScalarValueType="NO" syncable="YES">
<userInfo>
<entry key="attributeValueScalarType" value="SaveStateType"/>
</userInfo>
</attribute>
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="saveStates" inverseEntity="Game" syncable="YES"/>
<relationship name="previewGame" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="previewSaveState" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<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="30"/>
<element name="GameCollection" positionX="-585" positionY="-27" width="128" height="90"/>
<element name="GameControllerInputMapping" positionX="-387" positionY="90" width="128" height="120"/>
<element name="GameSave" positionX="-387" positionY="90" width="128" height="90"/>
<element name="SaveState" positionX="-198" positionY="113" width="128" height="165"/>
</elements>
</model>

View File

@ -9,6 +9,7 @@
import Foundation
import DeltaCore
import Harmony
@objc(Cheat)
public class Cheat: _Cheat, CheatProtocol
@ -29,3 +30,27 @@ public class Cheat: _Cheat, CheatProtocol
self.primitiveModifiedDate = date
}
}
extension Cheat: Syncable
{
public static var syncablePrimaryKey: AnyKeyPath {
return \Cheat.identifier
}
public var syncableKeys: Set<AnyKeyPath> {
return [\Cheat.code, \Cheat.creationDate, \Cheat.modifiedDate, \Cheat.name, \Cheat.type]
}
public var syncableRelationships: Set<AnyKeyPath> {
return [\Cheat.game as AnyKeyPath]
}
public var syncableMetadata: [HarmonyMetadataKey : String] {
guard let game = self.game else { return [:] }
return [.gameID: game.identifier, .gameName: game.name]
}
public var syncableLocalizedName: String? {
return self.name
}
}

View File

@ -9,6 +9,7 @@
import Foundation
import DeltaCore
import Harmony
extension ControllerSkinConfigurations
{
@ -90,3 +91,26 @@ extension ControllerSkin: ControllerSkinProtocol
return self.controllerSkin?.aspectRatio(for: traits)
}
}
extension ControllerSkin: Syncable
{
public static var syncablePrimaryKey: AnyKeyPath {
return \ControllerSkin.identifier
}
public var syncableKeys: Set<AnyKeyPath> {
return [\ControllerSkin.filename, \ControllerSkin.gameType, \ControllerSkin.name, \ControllerSkin.supportedConfigurations]
}
public var syncableFiles: Set<File> {
return [File(identifier: "skin", fileURL: self.fileURL)]
}
public var isSyncingEnabled: Bool {
return !self.isStandard
}
public var syncableLocalizedName: String? {
return self.name
}
}

View File

@ -9,6 +9,7 @@
import Foundation
import DeltaCore
import Harmony
@objc(Game)
public class Game: _Game, GameProtocol
@ -90,7 +91,7 @@ extension Game
print(error)
}
for collection in self.gameCollections where collection.games.count == 1
if let collection = self.gameCollection, collection.games.count == 1
{
// Once this game is deleted, collection will have 0 games, so we should delete it
managedObjectContext.delete(collection)
@ -109,3 +110,31 @@ extension Game
}
}
}
extension Game: Syncable
{
public static var syncablePrimaryKey: AnyKeyPath {
return \Game.identifier
}
public var syncableKeys: Set<AnyKeyPath> {
return [\Game.artworkURL, \Game.filename, \Game.name, \Game.type]
}
public var syncableFiles: Set<File> {
let gameFile = File(identifier: "game", fileURL: self.fileURL)
let artworkURL = DatabaseManager.artworkURL(for: self)
let artworkFile = File(identifier: "artwork", fileURL: artworkURL)
return [gameFile, artworkFile]
}
public var syncableRelationships: Set<AnyKeyPath> {
return [\Game.gameCollection]
}
public var syncableLocalizedName: String? {
return self.name
}
}

View File

@ -9,15 +9,16 @@
import CoreData
import DeltaCore
import Harmony
@objc(GameCollection)
public class GameCollection: _GameCollection
{
var name: String {
@objc var name: String {
return self.system?.localizedName ?? NSLocalizedString("Unknown", comment: "")
}
var shortName: String {
@objc var shortName: String {
return self.system?.localizedShortName ?? NSLocalizedString("Unknown", comment: "")
}
@ -28,3 +29,18 @@ public class GameCollection: _GameCollection
return system
}
}
extension GameCollection: Syncable
{
public static var syncablePrimaryKey: AnyKeyPath {
return \GameCollection.identifier
}
public var syncableKeys: Set<AnyKeyPath> {
return [\GameCollection.index as AnyKeyPath]
}
public var syncableLocalizedName: String? {
return self.name
}
}

View File

@ -9,6 +9,7 @@
import Foundation
import DeltaCore
import Harmony
@objc(GameControllerInputMapping)
public class GameControllerInputMapping: _GameControllerInputMapping
@ -24,6 +25,13 @@ public class GameControllerInputMapping: _GameControllerInputMapping
self.inputMapping = inputMapping
}
public override func awakeFromInsert()
{
super.awakeFromInsert()
self.identifier = UUID().uuidString
}
}
extension GameControllerInputMapping
@ -75,3 +83,21 @@ extension GameControllerInputMapping: GameControllerInputMappingProtocol
self.inputMapping.set(input, forControllerInput: controllerInput)
}
}
extension GameControllerInputMapping: Syncable
{
public static var syncablePrimaryKey: AnyKeyPath {
return \GameControllerInputMapping.identifier
}
public var syncableKeys: Set<AnyKeyPath> {
return [\GameControllerInputMapping.deltaCoreInputMapping,
\GameControllerInputMapping.gameControllerInputType,
\GameControllerInputMapping.gameType,
\GameControllerInputMapping.playerIndex]
}
public var syncableLocalizedName: String? {
return self.name
}
}

View File

@ -0,0 +1,60 @@
//
// GameSave.swift
// Delta
//
// Created by Riley Testut on 8/30/16.
// Copyright (c) 2016 Riley Testut. All rights reserved.
//
import Foundation
import Harmony
@objc(GameSave)
public class GameSave: _GameSave
{
public override func awakeFromInsert()
{
super.awakeFromInsert()
self.modifiedDate = Date()
}
}
extension GameSave: Syncable
{
public static var syncablePrimaryKey: AnyKeyPath {
return \GameSave.identifier
}
public var syncableKeys: Set<AnyKeyPath> {
return [\GameSave.modifiedDate]
}
public var syncableRelationships: Set<AnyKeyPath> {
return [\GameSave.game]
}
public var syncableFiles: Set<File> {
guard let game = self.game else { return [] }
var files: Set<File> = [File(identifier: "gameSave", fileURL: game.gameSaveURL)]
if game.type == .gbc
{
let gameTimeSaveURL = game.gameSaveURL.deletingPathExtension().appendingPathExtension("rtc")
files.insert(File(identifier: "gameTimeSave", fileURL: gameTimeSaveURL))
}
return files
}
public var syncableMetadata: [HarmonyMetadataKey : String] {
guard let game = self.game else { return [:] }
return [.gameID: game.identifier, .gameName: game.name]
}
public var syncableLocalizedName: String? {
return self.game?.name
}
}

View File

@ -9,6 +9,7 @@
import Foundation
import DeltaCore
import Harmony
@objc public enum SaveStateType: Int16
{
@ -21,6 +22,13 @@ import DeltaCore
@objc(SaveState)
public class SaveState: _SaveState, SaveStateProtocol
{
public static let localizedDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .short
dateFormatter.dateStyle = .short
return dateFormatter
}()
public var fileURL: URL {
let fileURL = DatabaseManager.saveStatesDirectoryURL(for: self.game!).appendingPathComponent(self.filename)
return fileURL
@ -36,6 +44,11 @@ public class SaveState: _SaveState, SaveStateProtocol
return self.game!.type
}
public var localizedName: String {
let localizedName = self.name ?? SaveState.localizedDateFormatter.string(from: self.modifiedDate)
return localizedName
}
@NSManaged private var primitiveFilename: String
@NSManaged private var primitiveIdentifier: String
@NSManaged private var primitiveCreationDate: Date
@ -90,3 +103,35 @@ public class SaveState: _SaveState, SaveStateProtocol
return fetchRequest
}
}
extension SaveState: Syncable
{
public static var syncablePrimaryKey: AnyKeyPath {
return \SaveState.identifier
}
public var syncableKeys: Set<AnyKeyPath> {
return [\SaveState.creationDate, \SaveState.filename, \SaveState.modifiedDate, \SaveState.name, \SaveState.type]
}
public var syncableFiles: Set<File> {
return [File(identifier: "saveState", fileURL: self.fileURL), File(identifier: "thumbnail", fileURL: self.imageFileURL)]
}
public var syncableRelationships: Set<AnyKeyPath> {
return [\SaveState.game]
}
public var isSyncingEnabled: Bool {
return self.type != .auto && self.type != .quick
}
public var syncableMetadata: [HarmonyMetadataKey : String] {
guard let game = self.game else { return [:] }
return [.gameID: game.identifier, .gameName: game.name]
}
public var syncableLocalizedName: String? {
return self.localizedName
}
}

View File

@ -24,7 +24,7 @@ public class _Cheat: NSManagedObject
@NSManaged public var modifiedDate: Date
@NSManaged public var name: String?
@NSManaged public var name: String
@NSManaged public var type: CheatType

View File

@ -30,7 +30,9 @@ public class _Game: NSManagedObject
@NSManaged public var cheats: Set<Cheat>
@NSManaged public var gameCollections: Set<GameCollection>
@NSManaged public var gameCollection: GameCollection?
@NSManaged public var gameSave: GameSave?
@NSManaged public var previewSaveState: SaveState?

View File

@ -20,6 +20,8 @@ public class _GameControllerInputMapping: NSManagedObject
@NSManaged public var gameType: GameType
@NSManaged public var identifier: String
@NSManaged public var playerIndex: Int16
// MARK: - Relationships

View File

@ -0,0 +1,26 @@
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// Make changes to GameSave.swift instead.
import Foundation
import CoreData
import DeltaCore
public class _GameSave: NSManagedObject
{
@nonobjc public class func fetchRequest() -> NSFetchRequest<GameSave> {
return NSFetchRequest<GameSave>(entityName: "GameSave")
}
// MARK: - Properties
@NSManaged public var identifier: String
@NSManaged public var modifiedDate: Date
// MARK: - Relationships
@NSManaged public var game: Game?
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,19 @@
//
// GameControllerInputMappingMigrationPolicy.swift
// Delta
//
// Created by Riley Testut on 1/30/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import CoreData
@objc(GameControllerInputMappingMigrationPolicy)
class GameControllerInputMappingMigrationPolicy: NSEntityMigrationPolicy
{
@objc(migrateIdentifier)
func migrateIdentifier() -> String
{
return UUID().uuidString
}
}

View File

@ -66,6 +66,8 @@ class GameViewController: DeltaCore.GameViewController
let game = self.game as? Game
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.managedObjectContextDidChange(with:)), name: .NSManagedObjectContextObjectsDidChange, object: game?.managedObjectContext)
self.emulatorCore?.saveHandler = { [weak self] _ in self?.updateGameSave() }
self.updateControllerSkin()
self.updateControllers()
}
@ -501,6 +503,40 @@ private extension GameViewController
}
}
//MARK: - Game Saves -
/// Game Saves
private extension GameViewController
{
func updateGameSave()
{
guard let game = self.game as? Game else { return }
DatabaseManager.shared.performBackgroundTask { (context) in
let game = context.object(with: game.objectID) as! Game
if let gameSave = game.gameSave
{
gameSave.modifiedDate = Date()
}
else
{
let gameSave = GameSave(context: context)
gameSave.identifier = game.identifier
game.gameSave = gameSave
}
do
{
try context.save()
}
catch
{
print("Error updating game save.", error)
}
}
}
}
//MARK: - Save States -
/// Save States
extension GameViewController: SaveStatesViewControllerDelegate
@ -906,6 +942,8 @@ private extension GameViewController
}
case .translucentControllerSkinOpacity: self.controllerView.translucentControllerSkinOpacity = Settings.translucentControllerSkinOpacity
case .syncingService: break
}
}

View File

@ -18,11 +18,11 @@ extension EmulatorCore
}
catch EmulatorCore.CheatError.invalid
{
print("Invalid cheat:", cheat.name ?? "Unnamed Cheat", cheat.code)
print("Invalid cheat:", cheat.name, cheat.code)
}
catch
{
print("Unknown Cheat Error:", error, cheat.name ?? "Unnamed Cheat", cheat.code)
print("Unknown Cheat Error:", error, cheat.name, cheat.code)
}
}

View File

@ -0,0 +1,15 @@
//
// HarmonyMetadataKey+Keys.swift
// Harmony
//
// Created by Riley Testut on 11/5/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import Harmony
extension HarmonyMetadataKey
{
static let gameID = HarmonyMetadataKey("gameID")
static let gameName = HarmonyMetadataKey("gameName")
}

View File

@ -0,0 +1,32 @@
//
// UIAlertController+Error.swift
// INLINE
//
// Created by Riley Testut on 11/27/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import Foundation
import Roxas
import Harmony
extension UIAlertController
{
convenience init(title: String, error: Error)
{
let message: String
if let error = error as? HarmonyError, let reason = error.failureReason
{
message = reason
}
else
{
message = error.localizedDescription
}
self.init(title: title, message: message, preferredStyle: .alert)
self.addAction(.ok)
}
}

View File

@ -10,13 +10,6 @@ import UIKit
extension UIColor
{
class var deltaPurple: UIColor
{
return UIColor(red: 139.0/255.0, green: 40.0/255.0, blue: 247.0/255.0, alpha: 1.0)
}
class var deltaDarkGray: UIColor
{
return UIColor(white: 0.15, alpha: 1.0)
}
static let deltaPurple = UIColor(named: "Purple")!
static let deltaDarkGray = UIColor(named: "DarkGray")!
}

View File

@ -12,9 +12,19 @@ import MobileCoreServices
import DeltaCore
import Roxas
import Harmony
import SDWebImage
extension GameCollectionViewController
{
private enum LaunchError: Error
{
case alreadyRunning
case downloadingGameSave
}
}
class GameCollectionViewController: UICollectionViewController
{
var gameCollection: GameCollection? {
@ -189,8 +199,8 @@ private extension GameCollectionViewController
//MARK: - Data Source
func prepareDataSource()
{
self.dataSource.cellConfigurationHandler = { [unowned self] (cell, item, indexPath) in
self.configure(cell as! GridCollectionViewCell, for: indexPath)
self.dataSource.cellConfigurationHandler = { [weak self] (cell, item, indexPath) in
self?.configure(cell as! GridCollectionViewCell, for: indexPath)
}
self.dataSource.prefetchHandler = { (game, indexPath, completionHandler) in
@ -219,7 +229,7 @@ private extension GameCollectionViewController
if let gameCollection = self.gameCollection
{
fetchRequest.predicate = NSPredicate(format: "ANY %K == %@", #keyPath(Game.gameCollections), gameCollection)
fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(Game.gameCollection), gameCollection)
}
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Game.name), ascending: true)]
@ -252,14 +262,113 @@ private extension GameCollectionViewController
}
//MARK: - Emulation
func launchGame(withSender sender: AnyObject?, clearScreen: Bool)
func launchGame(at indexPath: IndexPath, clearScreen: Bool, ignoreAlreadyRunningError: Bool = false)
{
if clearScreen
func launchGame(ignoringErrors ignoredErrors: [Error])
{
self.activeEmulatorCore?.gameViews.forEach { $0.inputImage = nil }
let game = self.dataSource.item(at: indexPath)
do
{
try self.validateLaunchingGame(game, ignoringErrors: ignoredErrors)
if clearScreen
{
self.activeEmulatorCore?.gameViews.forEach { $0.inputImage = nil }
}
let cell = self.collectionView.cellForItem(at: indexPath)
self.performSegue(withIdentifier: "unwindFromGames", sender: cell)
}
catch LaunchError.alreadyRunning
{
let alertController = UIAlertController(title: NSLocalizedString("Game Paused", comment: ""), message: NSLocalizedString("Would you like to resume where you left off, or restart the game?", comment: ""), preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Resume", comment: ""), style: .default, handler: { (action) in
let fetchRequest = SaveState.rst_fetchRequest() as! NSFetchRequest<SaveState>
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K == %d", #keyPath(SaveState.game), game, #keyPath(SaveState.type), SaveStateType.auto.rawValue)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(SaveState.creationDate), ascending: true)]
do
{
let saveStates = try game.managedObjectContext?.fetch(fetchRequest)
self.activeSaveState = saveStates?.last
}
catch
{
print(error)
}
// Disable videoManager to prevent flash of black
self.activeEmulatorCore?.videoManager.isEnabled = false
launchGame(ignoringErrors: [LaunchError.alreadyRunning])
// The game hasn't changed, so the activeEmulatorCore is the same as before, so we need to enable videoManager it again
self.activeEmulatorCore?.videoManager.isEnabled = true
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Restart", comment: ""), style: .destructive, handler: { (action) in
launchGame(ignoringErrors: [LaunchError.alreadyRunning])
}))
self.present(alertController, animated: true)
}
catch LaunchError.downloadingGameSave
{
let alertController = UIAlertController(title: NSLocalizedString("Downloading Save File", comment: ""), message: NSLocalizedString("Please wait until after this game's save file has been downloaded before playing to prevent losing save data.", comment: ""), preferredStyle: .alert)
alertController.addAction(.ok)
self.present(alertController, animated: true, completion: nil)
}
catch
{
let alertController = UIAlertController(title: NSLocalizedString("Unable to Launch Game", comment: ""), error: error)
self.present(alertController, animated: true, completion: nil)
}
}
self.performSegue(withIdentifier: "unwindFromGames", sender: sender)
if ignoreAlreadyRunningError
{
launchGame(ignoringErrors: [LaunchError.alreadyRunning])
}
else
{
launchGame(ignoringErrors: [])
}
}
func validateLaunchingGame(_ game: Game, ignoringErrors ignoredErrors: [Error]) throws
{
let ignoredErrors = ignoredErrors.map { $0 as NSError }
if !ignoredErrors.contains(where: { $0.domain == (LaunchError.alreadyRunning as NSError).domain && $0.code == (LaunchError.alreadyRunning as NSError).code })
{
guard game.fileURL != self.activeEmulatorCore?.game.fileURL else { throw LaunchError.alreadyRunning }
}
if SyncManager.shared.syncCoordinator.isSyncing
{
if let gameSave = game.gameSave
{
do
{
if let record = try SyncManager.shared.recordController.fetchRecords(for: [gameSave]).first
{
if record.isSyncingEnabled && !record.isConflicted && (record.localStatus == nil || record.remoteStatus == .updated)
{
throw LaunchError.downloadingGameSave
}
}
}
catch let error as LaunchError
{
throw error
}
catch
{
print("Error fetching record for game save.", error)
}
}
}
}
}
@ -463,8 +572,7 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
let game = gameViewController.game as! Game
let indexPath = self.dataSource.fetchedResultsController.indexPath(forObject: game)!
let cell = self.collectionView?.cellForItem(at: indexPath)
let fileURL = FileManager.default.uniqueTemporaryURL()
self.activeSaveState = gameViewController.emulatorCore?.saveSaveState(to: fileURL)
@ -472,7 +580,7 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
_performing3DTouchTransition = true
self.launchGame(withSender: cell, clearScreen: true)
self.launchGame(at: indexPath, clearScreen: true, ignoreAlreadyRunningError: true)
do
{
@ -499,9 +607,7 @@ extension GameCollectionViewController: SaveStatesViewControllerDelegate
self.dismiss(animated: true) {
let indexPath = self.dataSource.fetchedResultsController.indexPath(forObject: saveStatesViewController.game)!
let cell = self.collectionView?.cellForItem(at: indexPath)
self.launchGame(withSender: cell, clearScreen: false)
self.launchGame(at: indexPath, clearScreen: false, ignoreAlreadyRunningError: true)
}
}
}
@ -555,14 +661,14 @@ extension GameCollectionViewController: ImportControllerDelegate
if let imageURL = imageURL
{
// Remove previous artwork from cache.
self.dataSource.prefetchItemCache.removeObject(forKey: game)
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)
}
@ -604,46 +710,7 @@ extension GameCollectionViewController
{
guard self.gameCollection?.identifier != GameType.unknown.rawValue else { return }
let cell = collectionView.cellForItem(at: indexPath)
let game = self.dataSource.item(at: indexPath)
if game.fileURL == self.activeEmulatorCore?.game.fileURL
{
let alertController = UIAlertController(title: NSLocalizedString("Game Paused", comment: ""), message: NSLocalizedString("Would you like to resume where you left off, or restart the game?", comment: ""), preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Resume", comment: ""), style: .default, handler: { (action) in
let fetchRequest = SaveState.rst_fetchRequest() as! NSFetchRequest<SaveState>
fetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K == %d", #keyPath(SaveState.game), game, #keyPath(SaveState.type), SaveStateType.auto.rawValue)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(SaveState.creationDate), ascending: true)]
do
{
let saveStates = try game.managedObjectContext?.fetch(fetchRequest)
self.activeSaveState = saveStates?.last
}
catch
{
print(error)
}
// Disable videoManager to prevent flash of black
self.activeEmulatorCore?.videoManager.isEnabled = false
self.launchGame(withSender: cell, clearScreen: false)
// The game hasn't changed, so the activeEmulatorCore is the same as before, so we need to enable videoManager it again
self.activeEmulatorCore?.videoManager.isEnabled = true
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Restart", comment: ""), style: .destructive, handler: { (action) in
self.launchGame(withSender: cell, clearScreen: true)
}))
self.present(alertController, animated: true)
}
else
{
self.launchGame(withSender: cell, clearScreen: true)
}
self.launchGame(at: indexPath, clearScreen: true)
}
}

View File

@ -13,6 +13,7 @@ import MobileCoreServices
import DeltaCore
import Roxas
import Harmony
class GamesViewController: UIViewController
{
@ -61,6 +62,9 @@ class GamesViewController: UIViewController
super.init(coder: aDecoder)
self.fetchedResultsController.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(GamesViewController.syncingDidStart(_:)), name: SyncCoordinator.didStartSyncingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(GamesViewController.syncingDidFinish(_:)), name: SyncCoordinator.didFinishSyncingNotification, object: nil)
}
}
@ -109,6 +113,8 @@ extension GamesViewController
DispatchQueue.global().async {
self.activeEmulatorCore?.stop()
}
self.sync()
}
override func didReceiveMemoryWarning()
@ -251,6 +257,11 @@ private extension GamesViewController
}
if self.pageViewController.viewControllers?.count == 0
{
resetPageViewController = true
}
self.navigationController?.setToolbarHidden(sections < 2, animated: animated)
if sections > 0
@ -351,6 +362,11 @@ extension GamesViewController: ImportControllerDelegate
}
}
}
@IBAction func sync()
{
SyncManager.shared.sync()
}
}
private extension GamesViewController
@ -375,6 +391,42 @@ private extension GamesViewController
}
}
}
@objc func syncingDidStart(_ notification: Notification)
{
DispatchQueue.main.async {
let toastView = RSTToastView(text: NSLocalizedString("Syncing...", comment: ""), detailText: nil)
toastView.activityIndicatorView.startAnimating()
toastView.show(in: self.view)
}
}
@objc func syncingDidFinish(_ notification: Notification)
{
DispatchQueue.main.async {
guard let result = notification.userInfo?[SyncCoordinator.syncResultKey] as? SyncResult else { return }
let toastView: RSTToastView
switch result
{
case .success: toastView = RSTToastView(text: NSLocalizedString("Sync Complete", comment: ""), detailText: nil)
case .failure(let error): toastView = RSTToastView(text: NSLocalizedString("Sync Failed", comment: ""), detailText: error.failureReason)
}
toastView.addTarget(self, action: #selector(GamesViewController.presentSyncResultsViewController), for: .touchUpInside)
toastView.show(in: self.view, duration: 2.0)
}
}
@objc func presentSyncResultsViewController()
{
guard let result = SyncManager.shared.previousSyncResult else { return }
let navigationController = SyncResultViewController.make(result: result)
self.present(navigationController, animated: true, completion: nil)
}
}
//MARK: - UIPageViewController -

View File

@ -9,7 +9,7 @@
import UIKit
import Roxas
class LaunchViewController: UIViewController
class LaunchViewController: RSTLaunchViewController
{
@IBOutlet private var gameViewContainerView: UIView!
private var gameViewController: GameViewController!
@ -18,6 +18,8 @@ class LaunchViewController: UIViewController
private var applicationLaunchDeepLinkGame: Game?
private var didAttemptStartingSyncManager = false
override var preferredStatusBarStyle: UIStatusBarStyle {
return self.gameViewController?.preferredStatusBarStyle ?? .lightContent
}
@ -37,40 +39,6 @@ class LaunchViewController: UIViewController
NotificationCenter.default.addObserver(self, selector: #selector(LaunchViewController.deepLinkControllerLaunchGame(with:)), name: .deepLinkControllerLaunchGame, object: nil)
}
override func viewDidAppear(_ animated: Bool)
{
super.viewDidAppear(animated)
if !self.presentedGameViewController
{
self.presentedGameViewController = true
func showGameViewController()
{
self.view.bringSubviewToFront(self.gameViewContainerView)
self.setNeedsStatusBarAppearanceUpdate()
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()
})
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
guard segue.identifier == "embedGameViewController" else { return }
@ -79,6 +47,76 @@ class LaunchViewController: UIViewController
}
}
extension LaunchViewController
{
override var launchConditions: [RSTLaunchCondition] {
let isDatabaseManagerStarted = RSTLaunchCondition(condition: { DatabaseManager.shared.isStarted }) { (completionHandler) in
DatabaseManager.shared.start(completionHandler: completionHandler)
}
let isSyncingManagerStarted = RSTLaunchCondition(condition: { self.didAttemptStartingSyncManager }) { (completionHandler) in
SyncManager.shared.syncCoordinator.start { (error) in
self.didAttemptStartingSyncManager = true
completionHandler(nil)
}
}
let isRecordControllerSeeded = RSTLaunchCondition(condition: { SyncManager.shared.syncCoordinator.recordController.isSeeded }) { (completionHandler) in
SyncManager.shared.syncCoordinator.recordController.seedFromPersistentContainer() { (result) in
switch result
{
case .success: completionHandler(nil)
case .failure(let error): completionHandler(error.error)
}
}
}
return [isDatabaseManagerStarted, isSyncingManagerStarted, isRecordControllerSeeded]
}
override func handleLaunchError(_ error: Error)
{
let alertController = UIAlertController(title: NSLocalizedString("Unable to Launch Delta", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { (action) in
self.handleLaunchConditions()
}))
self.present(alertController, animated: true, completion: nil)
}
override func finishLaunching()
{
super.finishLaunching()
guard !self.presentedGameViewController else { return }
self.presentedGameViewController = true
func showGameViewController()
{
self.view.bringSubviewToFront(self.gameViewContainerView)
self.setNeedsStatusBarAppearanceUpdate()
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()
})
}
}
}
private extension LaunchViewController
{
@objc func deepLinkControllerLaunchGame(with notification: Notification)

View File

@ -16,6 +16,7 @@ extension CheatValidator
{
case invalidCode
case invalidName
case invalidGame
case duplicateName
case duplicateCode
}
@ -28,7 +29,10 @@ struct CheatValidator
func validate(_ cheat: Cheat) throws
{
guard let name = cheat.name, let game = cheat.game else { throw Error.invalidName }
let name = cheat.name
guard !name.isEmpty else { throw Error.invalidName }
guard let game = cheat.game else { throw Error.invalidGame }
let code = cheat.code

View File

@ -70,16 +70,10 @@ class SaveStatesViewController: UICollectionViewController
private var emulatorCoreSaveState: SaveStateProtocol?
private let dateFormatter: DateFormatter
required init?(coder aDecoder: NSCoder)
{
self.dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<SaveState, UIImage>(fetchedResultsController: NSFetchedResultsController())
self.dateFormatter = DateFormatter()
self.dateFormatter.timeStyle = .short
self.dateFormatter.dateStyle = .short
super.init(coder: aDecoder)
self.prepareDataSource()
@ -255,9 +249,7 @@ private extension SaveStatesViewController
cell.maximumImageSize = CGSize(width: self.prototypeCellWidthConstraint.constant, height: (self.prototypeCellWidthConstraint.constant / dimensions.width) * dimensions.height)
cell.textLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
let name = saveState.name ?? self.dateFormatter.string(from: saveState.modifiedDate)
cell.textLabel.text = name
cell.textLabel.text = saveState.localizedName
}
func configure(_ headerView: SaveStatesCollectionHeaderView, forSection section: Int)

View File

@ -32,6 +32,7 @@ extension Settings
case localControllerPlayerIndex
case translucentControllerSkinOpacity
case preferredControllerSkin
case syncingService
}
}
@ -45,6 +46,15 @@ extension Settings
}
struct Settings
{
static func registerDefaults()
{
let defaults = [#keyPath(UserDefaults.translucentControllerSkinOpacity): 0.7, #keyPath(UserDefaults.gameShortcutsMode): GameShortcutsMode.recent.rawValue] as [String : Any]
UserDefaults.standard.register(defaults: defaults)
}
}
extension Settings
{
/// Controllers
static var localControllerPlayerIndex: Int? = 0 {
@ -120,10 +130,12 @@ struct Settings
}
}
static func registerDefaults()
{
let defaults = [#keyPath(UserDefaults.translucentControllerSkinOpacity): 0.7, #keyPath(UserDefaults.gameShortcutsMode): GameShortcutsMode.recent.rawValue] as [String : Any]
UserDefaults.standard.register(defaults: defaults)
static var syncingService: SyncingService {
get { return SyncingService(rawValue: UserDefaults.standard.syncingService) ?? .none }
set {
UserDefaults.standard.syncingService = newValue.rawValue
NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [NotificationUserInfoKey.name: Name.syncingService])
}
}
static func preferredControllerSkin(for system: System, traits: DeltaCore.ControllerSkin.Traits) -> ControllerSkin?
@ -220,4 +232,6 @@ private extension UserDefaults
@NSManaged var gameShortcutsMode: String
@NSManaged var gameShortcutIdentifiers: [String]
@NSManaged var syncingService: String
}

View File

@ -19,6 +19,7 @@ private extension SettingsViewController
case controllers
case controllerSkins
case controllerOpacity
case syncing
case threeDTouch
}
@ -34,22 +35,34 @@ private extension SettingsViewController
case gba
case gbc
}
enum SyncingRow: Int, CaseIterable
{
case service
case status
}
}
class SettingsViewController: UITableViewController
{
@IBOutlet private var controllerOpacityLabel: UILabel!
@IBOutlet private var controllerOpacitySlider: UISlider!
@IBOutlet private var versionLabel: UILabel!
@IBOutlet private var syncingServiceLabel: UILabel!
private var selectionFeedbackGenerator: UISelectionFeedbackGenerator?
private var previousSelectedRowIndexPath: IndexPath?
private var syncingConflictsCount = 0
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.settingsDidChange(with:)), name: .settingsDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidConnect(_:)), name: .externalGameControllerDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.externalGameControllerDidDisconnect(_:)), name: .externalGameControllerDidDisconnect, object: nil)
}
@ -58,9 +71,6 @@ class SettingsViewController: UITableViewController
{
super.viewDidLoad()
self.controllerOpacitySlider.value = Float(Settings.translucentControllerSkinOpacity)
self.updateControllerOpacityLabel()
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
{
self.versionLabel.text = NSLocalizedString(String(format: "Delta %@", version), comment: "Delta Version")
@ -86,6 +96,8 @@ class SettingsViewController: UITableViewController
self.tableView.deselectRow(at: indexPath, animated: true)
}
self.update()
}
override func didReceiveMemoryWarning()
@ -126,6 +138,26 @@ class SettingsViewController: UITableViewController
private extension SettingsViewController
{
func update()
{
self.controllerOpacitySlider.value = Float(Settings.translucentControllerSkinOpacity)
self.updateControllerOpacityLabel()
self.syncingServiceLabel.text = Settings.syncingService.localizedName
do
{
let records = try SyncManager.shared.recordController.fetchConflictedRecords()
self.syncingConflictsCount = records.count
}
catch
{
print(error)
}
self.tableView.reloadData()
}
func updateControllerOpacityLabel()
{
let percentage = String(format: "%.f", Settings.translucentControllerSkinOpacity * 100) + "%"
@ -173,6 +205,26 @@ private extension SettingsViewController
private extension SettingsViewController
{
@objc func settingsDidChange(with notification: Notification)
{
guard let settingsName = notification.userInfo?[Settings.NotificationUserInfoKey.name] as? Settings.Name else { return }
switch settingsName
{
case .localControllerPlayerIndex, .preferredControllerSkin, .translucentControllerSkinOpacity: break
case .syncingService:
let selectedIndexPath = self.tableView.indexPathForSelectedRow
let indexPath = IndexPath(row: SyncingRow.service.rawValue, section: Section.syncing.rawValue)
self.tableView.reloadRows(at: [indexPath], with: .none)
if indexPath == selectedIndexPath
{
self.tableView.selectRow(at: selectedIndexPath, animated: true, scrollPosition: .none)
}
}
}
@objc func externalGameControllerDidConnect(_ notification: Notification)
{
self.tableView.reloadSections(IndexSet(integer: Section.controllers.rawValue), with: .none)
@ -228,7 +280,18 @@ extension SettingsViewController
}
case .controllerSkins: cell.textLabel?.text = System.supportedSystems[indexPath.row].localizedName
default: break
case .syncing:
switch SyncingRow.allCases[indexPath.row]
{
case .status:
let cell = cell as! BadgedTableViewCell
cell.badgeLabel.text = self.syncingConflictsCount.description
cell.badgeLabel.isHidden = (self.syncingConflictsCount == 0)
case .service: break
}
case .controllerOpacity, .threeDTouch: break
}
return cell
@ -241,10 +304,9 @@ extension SettingsViewController
switch section
{
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
case .controllers: self.performSegue(withIdentifier: Segue.controllers.rawValue, sender: cell)
case .controllerSkins: self.performSegue(withIdentifier: Segue.controllerSkins.rawValue, sender: cell)
case .controllerOpacity, .threeDTouch, .syncing: break
}
}

View File

@ -0,0 +1,171 @@
//
// GameSyncStatusViewController.swift
// Delta
//
// Created by Riley Testut on 11/20/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import UIKit
import Roxas
import Harmony
extension GameSyncStatusViewController
{
private enum Section: Int, CaseIterable
{
case game
case saveStates
case cheats
}
}
class GameSyncStatusViewController: UITableViewController
{
var game: Game!
private lazy var dataSource = self.makeDataSource()
private var recordsByObjectURI = [URL: Record<NSManagedObject>]()
override func viewDidLoad()
{
super.viewDidLoad()
self.title = self.game.name
self.tableView.dataSource = self.dataSource
}
override func viewWillAppear(_ animated: Bool)
{
self.fetchRecords()
super.viewWillAppear(animated)
self.tableView.reloadData()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
guard segue.identifier == "showRecord" else { return }
guard let cell = sender as? UITableViewCell, let indexPath = self.tableView.indexPath(for: cell) else { return }
let recordedObject = self.dataSource.item(at: indexPath) as! SyncableManagedObject
do
{
let records = try SyncManager.shared.recordController.fetchRecords(for: [recordedObject])
let recordSyncStatusViewController = segue.destination as! RecordSyncStatusViewController
recordSyncStatusViewController.record = records.first
}
catch
{
print(error)
}
}
}
private extension GameSyncStatusViewController
{
private func makeDataSource() -> RSTCompositeTableViewDataSource<NSManagedObject>
{
func configure(_ cell: UITableViewCell, recordedObject: NSManagedObject)
{
if let record = self.recordsByObjectURI[recordedObject.objectID.uriRepresentation()], record.isConflicted
{
cell.textLabel?.textColor = .red
}
else
{
cell.textLabel?.textColor = .darkText
}
}
let gameDataSource = RSTArrayTableViewDataSource<NSManagedObject>(items: [self.game, self.game.gameSave].compactMap { $0 })
gameDataSource.cellConfigurationHandler = { (cell, item, indexPath) in
if item is Game
{
cell.textLabel?.text = NSLocalizedString("Game", comment: "")
}
else
{
cell.textLabel?.text = NSLocalizedString("Game Save", comment: "")
}
configure(cell, recordedObject: item)
}
let saveStatesFetchRequest = SaveState.fetchRequest() as NSFetchRequest<SaveState>
saveStatesFetchRequest.predicate = NSPredicate(format: "%K == %@ AND %K != %@ AND %K != %@",
#keyPath(SaveState.game), self.game,
#keyPath(SaveState.type), NSNumber(value: SaveStateType.auto.rawValue),
#keyPath(SaveState.type), NSNumber(value: SaveStateType.quick.rawValue))
saveStatesFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \SaveState.creationDate, ascending: true)]
let saveStatesDataSource = RSTFetchedResultsTableViewDataSource(fetchRequest: saveStatesFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
saveStatesDataSource.cellConfigurationHandler = { (cell, saveState, indexPath) in
cell.textLabel?.text = saveState.localizedName
configure(cell, recordedObject: saveState)
}
let cheatsFetchRequest = Cheat.fetchRequest() as NSFetchRequest<Cheat>
cheatsFetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(Cheat.game), self.game)
cheatsFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Cheat.name, ascending: true)]
let cheatsDataSource = RSTFetchedResultsTableViewDataSource(fetchRequest: cheatsFetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
cheatsDataSource.cellConfigurationHandler = { (cell, cheat, indexPath) in
cell.textLabel?.text = cheat.name
configure(cell, recordedObject: cheat)
}
let dataSources = [gameDataSource, saveStatesDataSource, cheatsDataSource] as! [RSTArrayTableViewDataSource<NSManagedObject>]
let dataSource = RSTCompositeTableViewDataSource(dataSources: dataSources)
dataSource.proxy = self
return dataSource
}
func fetchRecords()
{
var recordsByObjectURI = [URL: Record<NSManagedObject>]()
do
{
let recordedObjects = ([self.game, self.game.gameSave].compactMap { $0 } + Array(self.game.saveStates) + Array(self.game.cheats)) as! [SyncableManagedObject]
let records = try SyncManager.shared.recordController.fetchRecords(for: recordedObjects)
for record in records
{
guard let recordedObject = record.recordedObject else { continue }
recordsByObjectURI[recordedObject.objectID.uriRepresentation()] = record
}
}
catch
{
print(error)
}
self.recordsByObjectURI = recordsByObjectURI
}
}
extension GameSyncStatusViewController
{
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
guard self.dataSource.tableView(self.tableView, numberOfRowsInSection: section) > 0 else { return nil }
switch Section.allCases[section]
{
case .game: return nil
case .saveStates: return NSLocalizedString("Save States", comment: "")
case .cheats: return NSLocalizedString("Cheats", comment: "")
}
}
}

View File

@ -0,0 +1,174 @@
//
// RecordSyncStatusViewController.swift
// Delta
//
// Created by Riley Testut on 11/20/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import UIKit
import Harmony
extension RecordStatus
{
fileprivate var localizedDescription: String {
switch self
{
case .normal: return NSLocalizedString("Normal", comment: "")
case .updated: return NSLocalizedString("Updated", comment: "")
case .deleted: return NSLocalizedString("Deleted", comment: "")
}
}
}
extension RecordSyncStatusViewController
{
private enum Section: Int, CaseIterable
{
case syncingEnabled
case localStatus
case remoteStatus
case versions
}
}
class RecordSyncStatusViewController: UITableViewController
{
var record: Record<NSManagedObject>?
private let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .short
dateFormatter.dateStyle = .short
return dateFormatter
}()
@IBOutlet private var syncingEnabledSwitch: UISwitch!
@IBOutlet private var localStatusLabel: UILabel!
@IBOutlet private var localDateLabel: UILabel!
@IBOutlet private var remoteStatusLabel: UILabel!
@IBOutlet private var remoteDateLabel: UILabel!
@IBOutlet private var remoteDeviceLabel: UILabel!
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.update()
self.tableView.reloadData()
}
}
extension RecordSyncStatusViewController
{
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
guard segue.identifier == "showVersions" else { return }
let navigationController = segue.destination as! UINavigationController
let recordVersionsViewController = navigationController.viewControllers[0] as! RecordVersionsViewController
recordVersionsViewController.record = self.record
}
@IBAction private func unwindToRecordSyncStatusViewController(_ segue: UIStoryboardSegue)
{
}
}
private extension RecordSyncStatusViewController
{
func update()
{
if let record = self.record
{
self.syncingEnabledSwitch.isEnabled = !record.isConflicted
self.syncingEnabledSwitch.isOn = record.isSyncingEnabled
self.localStatusLabel.text = record.localStatus?.localizedDescription ?? "-"
self.remoteStatusLabel.text = record.remoteStatus?.localizedDescription ?? "-"
self.remoteDeviceLabel.text = record.remoteAuthor ?? "-"
if let version = record.remoteVersion
{
self.remoteDateLabel.text = self.dateFormatter.string(from: version.date)
}
else
{
self.remoteDateLabel.text = "-"
}
if let date = record.localModificationDate
{
self.localDateLabel.text = self.dateFormatter.string(from: date)
}
else
{
self.localDateLabel.text = "-"
}
}
else
{
self.syncingEnabledSwitch.isEnabled = false
self.syncingEnabledSwitch.isOn = false
self.localStatusLabel.text = "-"
self.localDateLabel.text = "-"
self.remoteStatusLabel.text = "-"
self.remoteDateLabel.text = "-"
self.remoteDeviceLabel.text = "-"
}
}
@IBAction func toggleSyncingEnabled(_ sender: UISwitch)
{
do
{
try self.record?.setSyncingEnabled(sender.isOn)
}
catch
{
let title = sender.isOn ? NSLocalizedString("Failed to Enable Syncing", comment: "") : NSLocalizedString("Failed to Disable Syncing", comment: "")
let alertController = UIAlertController(title: title, error: error)
self.present(alertController, animated: true, completion: nil)
}
self.update()
}
}
extension RecordSyncStatusViewController
{
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = super.tableView(tableView, cellForRowAt: indexPath)
switch Section.allCases[indexPath.section]
{
case .versions:
cell.textLabel?.alpha = (self.record != nil) ? 1.0 : 0.33
if self.record?.isConflicted == true
{
cell.textLabel?.text = NSLocalizedString("Resolve Conflict", comment: "")
cell.textLabel?.textColor = .red
}
else
{
cell.textLabel?.text = NSLocalizedString("View Versions", comment: "")
cell.textLabel?.textColor = .deltaPurple
}
default: break
}
return cell
}
}

View File

@ -0,0 +1,394 @@
//
// RecordVersionsViewController.swift
// Delta
//
// Created by Riley Testut on 11/20/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import UIKit
import Roxas
import Harmony
extension RecordVersionsViewController
{
private enum Section: Int, CaseIterable
{
case local
case remote
case confirm
}
private enum Mode
{
case restoreVersion
case resolveConflict
}
}
private class Version
{
let version: Harmony.Version
init(_ version: Harmony.Version)
{
self.version = version
}
}
class RecordVersionsViewController: UITableViewController
{
var record: Record<NSManagedObject>! {
didSet {
self.mode = self.record.isConflicted ? .resolveConflict : .restoreVersion
}
}
private var mode = Mode.restoreVersion {
didSet {
switch self.mode
{
case .restoreVersion: self._selectedVersionIndexPath = IndexPath(item: 0, section: Section.local.rawValue)
case .resolveConflict: self._selectedVersionIndexPath = nil
}
}
}
private var versions: [Version]?
private lazy var dataSource = self.makeDataSource()
private var remoteVersionsDataSource: RSTArrayTableViewDataSource<Version> {
let compositeDataSource = self.dataSource.dataSources[1] as! RSTCompositeTableViewDataSource
let dataSource = compositeDataSource.dataSources[1] as! RSTArrayTableViewDataSource<Version>
return dataSource
}
private let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .short
dateFormatter.dateStyle = .short
return dateFormatter
}()
private var isSyncingRecord = false
private var _selectedVersionIndexPath: IndexPath?
private var progressView: UIProgressView!
private var _progressObservation: NSKeyValueObservation?
override func viewDidLoad()
{
super.viewDidLoad()
self.progressView = UIProgressView(progressViewStyle: .bar)
self.progressView.translatesAutoresizingMaskIntoConstraints = false
self.progressView.progress = 0
if let navigationBar = self.navigationController?.navigationBar
{
navigationBar.addSubview(self.progressView)
NSLayoutConstraint.activate([self.progressView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor),
self.progressView.trailingAnchor.constraint(equalTo: navigationBar.trailingAnchor),
self.progressView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor)])
}
self.tableView.dataSource = self.dataSource
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.fetchVersions()
}
}
private extension RecordVersionsViewController
{
func makeDataSource() -> RSTCompositeTableViewDataSource<Version>
{
func configure(_ cell: UITableViewCell, isSelected: Bool, isEnabled: Bool)
{
cell.accessoryType = isSelected ? .checkmark : .none
if isEnabled
{
cell.textLabel?.alpha = 1.0
cell.detailTextLabel?.alpha = 1.0
cell.selectionStyle = .gray
}
else
{
cell.textLabel?.alpha = 0.33
cell.detailTextLabel?.alpha = 0.33
cell.selectionStyle = .none
}
}
let localVersionsDataSource = RSTDynamicTableViewDataSource<Version>()
localVersionsDataSource.numberOfSectionsHandler = { 1 }
localVersionsDataSource.numberOfItemsHandler = { _ in self.record.localModificationDate != nil ? 1 : 0 }
localVersionsDataSource.cellConfigurationHandler = { [weak self] (cell, _, indexPath) in
guard let `self` = self else { return }
let date = self.record.localModificationDate!
cell.textLabel?.text = self.dateFormatter.string(from: date)
cell.detailTextLabel?.text = nil
let isSelected = (indexPath == self._selectedVersionIndexPath)
configure(cell, isSelected: isSelected, isEnabled: !self.isSyncingRecord)
}
let remoteVersionsDataSource = RSTArrayTableViewDataSource<Version>(items: [])
remoteVersionsDataSource.cellConfigurationHandler = { [weak self] (cell, version, indexPath) in
guard let `self` = self else { return }
cell.textLabel?.text = self.dateFormatter.string(from: version.version.date)
cell.detailTextLabel?.text = (version.version.identifier == self.record.remoteVersion?.identifier) ? self.record.remoteAuthor : nil
let isSelected = (self._selectedVersionIndexPath?.section == Section.remote.rawValue && self._selectedVersionIndexPath?.row == indexPath.row)
configure(cell, isSelected: isSelected, isEnabled: !self.isSyncingRecord)
}
let loadingDataSource = RSTDynamicTableViewDataSource<Version>()
loadingDataSource.numberOfSectionsHandler = { 1 }
loadingDataSource.numberOfItemsHandler = { _ in (self.versions == nil) ? 1 : 0 }
loadingDataSource.cellIdentifierHandler = { _ in "LoadingCell" }
loadingDataSource.cellConfigurationHandler = { (_, _, _) in }
let remoteVersionsLoadingDataSource = RSTCompositeTableViewDataSource(dataSources: [loadingDataSource, remoteVersionsDataSource])
remoteVersionsLoadingDataSource.shouldFlattenSections = true
let restoreVersionDataSource = RSTDynamicTableViewDataSource<Version>()
restoreVersionDataSource.numberOfSectionsHandler = { 1 }
restoreVersionDataSource.numberOfItemsHandler = { _ in 1}
restoreVersionDataSource.cellIdentifierHandler = { _ in "ConfirmCell" }
restoreVersionDataSource.cellConfigurationHandler = { [weak self] (cell, _, indexPath) in
guard let `self` = self else { return }
switch self.mode
{
case .restoreVersion:
cell.textLabel?.text = NSLocalizedString("Restore Version", comment: "")
cell.textLabel?.textColor = .deltaPurple
let isEnabled = (self._selectedVersionIndexPath?.section == Section.remote.rawValue && !self.isSyncingRecord)
configure(cell, isSelected: false, isEnabled: isEnabled)
case .resolveConflict:
cell.textLabel?.text = NSLocalizedString("Resolve Conflict", comment: "")
cell.textLabel?.textColor = .red
let isEnabled = (self._selectedVersionIndexPath != nil && !self.isSyncingRecord)
configure(cell, isSelected: false, isEnabled: isEnabled)
}
}
let dataSource = RSTCompositeTableViewDataSource(dataSources: [localVersionsDataSource, remoteVersionsLoadingDataSource, restoreVersionDataSource])
dataSource.proxy = self
return dataSource
}
func fetchVersions()
{
SyncManager.shared.syncCoordinator.fetchVersions(for: self.record) { (result) in
do
{
let versions = try result.value().map(Version.init)
self.versions = versions
DispatchQueue.main.async {
let count = self.tableView.numberOfRows(inSection: Section.remote.rawValue)
let deletions = (0 ..< count).map { (row) -> RSTCellContentChange in
let change = RSTCellContentChange(type: .delete,
currentIndexPath: IndexPath(row: row, section: 0),
destinationIndexPath: nil)
change.rowAnimation = .fade
return change
}
let inserts = (0 ..< versions.count).map { (row) -> RSTCellContentChange in
let change = RSTCellContentChange(type: .insert,
currentIndexPath: nil,
destinationIndexPath: IndexPath(row: row, section: 0))
change.rowAnimation = .fade
return change
}
let changes = deletions + inserts
self.remoteVersionsDataSource.setItems(versions, with: changes)
}
}
catch
{
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Failed to Fetch Record Versions", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
alertController.addAction(.ok)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
func restoreVersion()
{
guard !self.isSyncingRecord else { return }
guard let indexPath = self._selectedVersionIndexPath else { return }
func finish<T: Error>(_ result: Result<AnyRecord, T>)
{
DispatchQueue.main.async {
CATransaction.begin()
CATransaction.setCompletionBlock {
self.isSyncingRecord = false
self._progressObservation = nil
self.progressView.setHidden(true, animated: true)
self.tableView.reloadData()
self.navigationItem.rightBarButtonItem?.isIndicatingActivity = false
switch result
{
case .success: self.fetchVersions()
case .failure: break
}
}
do
{
let record = try result.value()
self.record = record
self.progressView.setProgress(1.0, animated: true)
}
catch
{
let title: String
switch self.mode
{
case .restoreVersion: title = NSLocalizedString("Failed to Restore Version", comment: "")
case .resolveConflict: title = NSLocalizedString("Failed to Resolve Conflict", comment: "")
}
let alertController = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert)
alertController.addAction(.ok)
self.present(alertController, animated: true, completion: nil)
}
CATransaction.commit()
}
}
let progress: Progress
switch (self.mode, Section.allCases[indexPath.section])
{
case (.restoreVersion, _):
let version = self.dataSource.item(at: indexPath)
progress = SyncManager.shared.syncCoordinator.restore(self.record, to: version.version) { (result) in
finish(result)
}
case (.resolveConflict, .local):
progress = SyncManager.shared.syncCoordinator.resolveConflictedRecord(self.record, resolution: .local) { (result) in
finish(result)
}
case (.resolveConflict, .remote):
let version = self.dataSource.item(at: indexPath)
progress = SyncManager.shared.syncCoordinator.resolveConflictedRecord(self.record, resolution: .remote(version.version)) { (result) in
finish(result)
}
case (.resolveConflict, .confirm): return
}
self.isSyncingRecord = true
self.navigationItem.rightBarButtonItem?.isIndicatingActivity = true
self.progressView.progress = 0
self.progressView.isHidden = false
self._progressObservation = progress.observe(\.fractionCompleted) { [weak progressView] (_, change) in
DispatchQueue.main.async {
progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
}
}
self.tableView.reloadData()
}
}
extension RecordVersionsViewController
{
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
switch Section.allCases[section]
{
case .local: return NSLocalizedString("Local", comment: "")
case .remote: return NSLocalizedString("Remote", comment: "")
case .confirm: return nil
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
tableView.deselectRow(at: indexPath, animated: true)
guard let cell = tableView.cellForRow(at: indexPath), cell.selectionStyle != .none else { return }
switch Section.allCases[indexPath.section]
{
case .local, .remote:
let indexPaths = [indexPath, self._selectedVersionIndexPath, IndexPath(item: 0, section: Section.confirm.rawValue)].compactMap { $0 }
self._selectedVersionIndexPath = indexPath
tableView.reloadRows(at: indexPaths, with: .none)
case .confirm:
let message: String
let actionTitle: String
switch self.mode
{
case .restoreVersion:
message = NSLocalizedString("Restoring a remote version will cause any local changes to be lost.", comment: "")
actionTitle = NSLocalizedString("Restore Version", comment: "")
case .resolveConflict:
if self._selectedVersionIndexPath?.section == Section.local.rawValue
{
message = NSLocalizedString("The local version will be uploaded and synced to your other devices.", comment: "")
}
else
{
message = NSLocalizedString("Selecting a remote version will cause any local changes to be lost.", comment: "")
}
actionTitle = NSLocalizedString("Resolve Conflict", comment: "")
}
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: actionTitle, style: .destructive) { (action) in
self.restoreVersion()
})
self.present(alertController, animated: true, completion: nil)
}
}
}

View File

@ -0,0 +1,184 @@
//
// SyncStatusViewController.swift
// Delta
//
// Created by Riley Testut on 11/15/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import UIKit
import Roxas
class SyncStatusViewController: UITableViewController
{
private lazy var dataSource = self.makeDataSource()
private var gameConflictsCount: [URL: Int]?
override func viewDidLoad()
{
super.viewDidLoad()
self.tableView.dataSource = self.dataSource
self.navigationItem.searchController = self.dataSource.searchController
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
self.fetchConflictedRecords()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
guard let identifier = segue.identifier else { return }
switch identifier
{
case "showGame":
guard let cell = sender as? UITableViewCell, let indexPath = self.tableView.indexPath(for: cell) else { return }
let game = self.dataSource.item(at: indexPath)
let gameSyncStatusViewController = segue.destination as! GameSyncStatusViewController
gameSyncStatusViewController.game = game
case "showPreviousSyncResults":
let syncResultViewController = segue.destination as! SyncResultViewController
syncResultViewController.result = SyncManager.shared.previousSyncResult
default: break
}
}
}
private extension SyncStatusViewController
{
func makeDataSource() -> RSTCompositeTableViewDataSource<Game>
{
let fetchRequest = Game.fetchRequest() as NSFetchRequest<Game>
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Game.gameCollection?.index, ascending: true), NSSortDescriptor(key: #keyPath(Game.name), ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext, sectionNameKeyPath: #keyPath(Game.gameCollection.name), cacheName: nil)
let fetchedDataSource = RSTFetchedResultsTableViewDataSource(fetchedResultsController: fetchedResultsController)
fetchedDataSource.searchController.searchableKeyPaths = [#keyPath(Game.name)]
fetchedDataSource.cellConfigurationHandler = { (cell, game, indexPath) in
let cell = cell as! BadgedTableViewCell
cell.textLabel?.text = game.name
cell.textLabel?.numberOfLines = 0
if let gameConflictsCount = self.gameConflictsCount
{
if let count = gameConflictsCount[game.objectID.uriRepresentation()], count > 0
{
cell.badgeLabel.text = String(describing: count)
cell.badgeLabel.isHidden = false
}
else
{
cell.badgeLabel.isHidden = true
}
cell.accessoryType = .disclosureIndicator
cell.accessoryView = nil
}
else
{
let activityIndicatorView = UIActivityIndicatorView(style: .gray)
activityIndicatorView.startAnimating()
cell.accessoryType = .none
cell.accessoryView = activityIndicatorView
cell.badgeLabel.isHidden = true
}
}
let dynamicDataSource = RSTDynamicTableViewDataSource<Game>()
dynamicDataSource.numberOfSectionsHandler = { (SyncManager.shared.previousSyncResult != nil) ? 1 : 0 }
dynamicDataSource.numberOfItemsHandler = { _ in 1 }
dynamicDataSource.cellIdentifierHandler = { _ in "PreviousSyncCell" }
dynamicDataSource.cellConfigurationHandler = { (cell, _, indexPath) in }
let dataSource = RSTCompositeTableViewDataSource(dataSources: [dynamicDataSource, fetchedDataSource])
dataSource.proxy = self
return dataSource
}
func fetchConflictedRecords()
{
DispatchQueue.global().async {
do
{
var gameConflictsCount = [URL: Int]()
let records = try SyncManager.shared.recordController.fetchConflictedRecords()
for record in records
{
guard let recordedObject = record.recordedObject else { continue }
recordedObject.managedObjectContext?.performAndWait {
let conflictedGame: Game?
switch recordedObject
{
case let game as Game: conflictedGame = game
case let saveState as SaveState: conflictedGame = saveState.game
case let cheat as Cheat: conflictedGame = cheat.game
case let gameSave as GameSave: conflictedGame = gameSave.game
default: conflictedGame = nil
}
guard let game = conflictedGame else { return }
gameConflictsCount[game.objectID.uriRepresentation(), default: 0] += 1
}
}
self.gameConflictsCount = gameConflictsCount
}
catch
{
print(error)
self.gameConflictsCount = [:]
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Failed to Get Sync Status", comment: ""),
message: error.localizedDescription,
preferredStyle: .alert)
alertController.addAction(.ok)
self.present(alertController, animated: true, completion: nil)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
extension SyncStatusViewController
{
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
var section = section
if SyncManager.shared.previousSyncResult != nil
{
guard section > 0 else { return nil }
section -= 1
}
let dataSource = self.dataSource.dataSources[1] as! RSTFetchedResultsTableViewDataSource
let sectionInfo = dataSource.fetchedResultsController.sections?[section]
return sectionInfo?.name
}
}

View File

@ -0,0 +1,204 @@
//
// SyncingServicesViewController.swift
// Delta
//
// Created by Riley Testut on 6/27/17.
// Copyright © 2017 Riley Testut. All rights reserved.
//
import UIKit
import Harmony
import Harmony_Drive
import Roxas
enum SyncingService: String, CaseIterable
{
case none
case googleDrive
var localizedName: String {
switch self
{
case .none: return NSLocalizedString("None", comment: "")
case .googleDrive: return NSLocalizedString("Google Drive", comment: "")
}
}
}
extension SyncingServicesViewController
{
enum Section: Int, CaseIterable
{
case service
case account
case authenticate
}
}
class SyncingServicesViewController: UITableViewController
{
func isSectionHidden(_ section: Section) -> Bool
{
switch section
{
case .account: return SyncManager.shared.syncCoordinator.account == nil
default: return false
}
}
}
extension SyncingServicesViewController
{
override func numberOfSections(in tableView: UITableView) -> Int
{
guard Settings.syncingService != .none else { return 1 }
return super.numberOfSections(in: tableView)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = super.tableView(tableView, cellForRowAt: indexPath)
switch Section.allCases[indexPath.section]
{
case .service:
let service = SyncingService.allCases[indexPath.row]
cell.accessoryType = (service == Settings.syncingService) ? .checkmark : .none
case .account:
cell.textLabel?.text = SyncManager.shared.syncCoordinator.account?.name ?? NSLocalizedString("Unknown Account", comment: "")
case .authenticate:
if SyncManager.shared.syncCoordinator.isAuthenticated
{
cell.textLabel?.textColor = .red
cell.textLabel?.text = NSLocalizedString("Sign Out", comment: "")
}
else
{
cell.textLabel?.textColor = .deltaPurple
cell.textLabel?.text = NSLocalizedString("Sign In", comment: "")
}
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
switch Section.allCases[indexPath.section]
{
case .service:
Settings.syncingService = SyncingService.allCases[indexPath.row]
if Settings.syncingService == .none && self.tableView.numberOfSections > 1
{
self.tableView.deleteSections(IndexSet(integersIn: Section.account.rawValue ... Section.authenticate.rawValue), with: .fade)
}
else if Settings.syncingService != .none && self.tableView.numberOfSections == 1
{
self.tableView.insertSections(IndexSet(integersIn: Section.account.rawValue ... Section.authenticate.rawValue), with: .fade)
}
self.tableView.reloadSections(IndexSet(integer: Section.service.rawValue), with: .none)
case .account: break
case .authenticate:
if SyncManager.shared.syncCoordinator.isAuthenticated
{
SyncManager.shared.syncCoordinator.deauthenticate { (result) in
DispatchQueue.main.async {
do
{
try result.verify()
self.tableView.reloadData()
}
catch
{
let alertController = UIAlertController(title: NSLocalizedString("Failed to Sign Out", comment: ""), error: error)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
else
{
SyncManager.shared.syncCoordinator.authenticate(presentingViewController: self) { (result) in
DispatchQueue.main.async {
do
{
try result.verify()
self.tableView.reloadData()
}
catch
{
let alertController = UIAlertController(title: NSLocalizedString("Failed to Sign In", comment: ""), error: error)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let section = Section.allCases[section]
if self.isSectionHidden(section)
{
return 0
}
else
{
return super.tableView(tableView, numberOfRowsInSection: section.rawValue)
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
let section = Section.allCases[section]
if self.isSectionHidden(section)
{
return nil
}
else
{
return super.tableView(tableView, titleForHeaderInSection: section.rawValue)
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
let section = Section.allCases[section]
if self.isSectionHidden(section)
{
return 1
}
else
{
return super.tableView(tableView, heightForHeaderInSection: section.rawValue)
}
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
{
let section = Section.allCases[section]
if self.isSectionHidden(section)
{
return 1
}
else
{
return super.tableView(tableView, heightForFooterInSection: section.rawValue)
}
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>457607414709-5puj6lcv779gpu3ql43e6k3smjj40dmu.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.457607414709-5puj6lcv779gpu3ql43e6k3smjj40dmu</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.rileytestut.Delta</string>
</dict>
</plist>

View File

@ -89,6 +89,17 @@
<string>0.6.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.457607414709-5puj6lcv779gpu3ql43e6k3smjj40dmu</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>6</string>
<key>Fabric</key>

View File

@ -0,0 +1,88 @@
//
// SyncManager.swift
// Delta
//
// Created by Riley Testut on 11/12/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import Harmony
import Harmony_Drive
extension SyncManager
{
enum RecordType: String, Hashable
{
case game = "Game"
case gameCollection = "GameCollection"
case cheat = "Cheat"
case saveState = "SaveState"
case controllerSkin = "ControllerSkin"
case gameControllerInputMapping = "GameControllerInputMapping"
case gameSave = "GameSave"
var localizedName: String {
switch self
{
case .game: return NSLocalizedString("Game", comment: "")
case .gameCollection: return NSLocalizedString("Game Collection", comment: "")
case .cheat: return NSLocalizedString("Cheat", comment: "")
case .saveState: return NSLocalizedString("Save State", comment: "")
case .controllerSkin: return NSLocalizedString("Controller Skin", comment: "")
case .gameControllerInputMapping: return NSLocalizedString("Game Controller Input Mapping", comment: "")
case .gameSave: return NSLocalizedString("Game Save", comment: "")
}
}
}
}
extension Syncable where Self: NSManagedObject
{
var recordType: SyncManager.RecordType {
let recordType = SyncManager.RecordType(rawValue: self.syncableType)!
return recordType
}
}
final class SyncManager
{
static let shared = SyncManager()
var service: Service {
return self.syncCoordinator.service
}
var recordController: RecordController {
return self.syncCoordinator.recordController
}
private(set) var previousSyncResult: SyncResult?
let syncCoordinator = SyncCoordinator(service: DriveService.shared, persistentContainer: DatabaseManager.shared)
private init()
{
DriveService.shared.clientID = "457607414709-5puj6lcv779gpu3ql43e6k3smjj40dmu.apps.googleusercontent.com"
NotificationCenter.default.addObserver(self, selector: #selector(SyncManager.syncingDidFinish(_:)), name: SyncCoordinator.didFinishSyncingNotification, object: nil)
}
}
extension SyncManager
{
func sync()
{
guard Settings.syncingService != .none else { return }
self.syncCoordinator.sync()
}
}
private extension SyncManager
{
@objc func syncingDidFinish(_ notification: Notification)
{
guard let result = notification.userInfo?[SyncCoordinator.syncResultKey] as? SyncResult else { return }
self.previousSyncResult = result
}
}

View File

@ -0,0 +1,354 @@
//
// SyncResultViewController.swift
// Delta
//
// Created by Riley Testut on 11/28/18.
// Copyright © 2018 Riley Testut. All rights reserved.
//
import UIKit
import Roxas
import Harmony
@objc(SyncResultTableViewCell)
private class SyncResultTableViewCell: UITableViewCell
{
@IBOutlet var nameLabel: UILabel!
@IBOutlet var errorLabel: UILabel!
}
extension SyncResultViewController
{
private enum Group: Hashable
{
case game(RecordID)
case saveState(gameID: RecordID)
case cheat(gameID: RecordID)
case controllerSkin
case gameControllerInputMapping
case gameCollection
case other
var sortIndex: Int {
switch self
{
case .game: return 0
case .saveState: return 1
case .cheat: return 2
case .controllerSkin: return 3
case .gameControllerInputMapping: return 4
case .gameCollection: return 5
case .other: return 6
}
}
}
}
class SyncResultViewController: UITableViewController
{
var result: Result<[Record<NSManagedObject>: Result<Void, RecordError>], SyncError>!
private lazy var dataSource = self.makeDataSource()
private lazy var sortedErrors = self.processResults()
private lazy var gameNamesByRecordID = self.fetchGameNames()
private init()
{
super.init(style: .grouped)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func viewDidLoad()
{
super.viewDidLoad()
self.tableView.dataSource = self.dataSource
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
guard segue.identifier == "showRecordStatus" else { return }
guard let cell = sender as? UITableViewCell, let indexPath = self.tableView.indexPath(for: cell) else { return }
guard let recordError = self.dataSource.item(at: indexPath).value as? RecordError else { return }
let recordSyncStatusViewController = segue.destination as! RecordSyncStatusViewController
recordSyncStatusViewController.record = recordError.record
}
}
extension SyncResultViewController
{
class func make(result: Result<[Record<NSManagedObject>: Result<Void, RecordError>], SyncError>) -> UINavigationController
{
let storyboard = UIStoryboard(name: "SyncResultsViewController", bundle: nil)
let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
let syncResultViewController = navigationController.viewControllers[0] as! SyncResultViewController
syncResultViewController.result = result
return navigationController
}
}
private extension SyncResultViewController
{
func makeDataSource() -> RSTCompositeTableViewDataSource<Box<Error>>
{
let dataSources = self.sortedErrors.map { (_, errors) -> RSTArrayTableViewDataSource<Box<Error>> in
let dataSource = RSTArrayTableViewDataSource<Box<Error>>(items: errors.map(Box.init))
dataSource.cellConfigurationHandler = { (cell, error, indexPath) in
let cell = cell as! SyncResultTableViewCell
let title: String?
let errorMessage: String?
switch error.value
{
case let error as RecordError:
guard let recordType = SyncManager.RecordType(rawValue: error.record.recordID.type) else { return }
switch recordType
{
case .game: title = NSLocalizedString("Game", comment: "")
case .gameSave: title = NSLocalizedString("Game Save", comment: "")
case .saveState, .cheat, .controllerSkin, .gameCollection, .gameControllerInputMapping: title = error.record.localizedName ?? recordType.localizedName
}
switch error
{
case .filesFailed(_, let errors):
var messages = [String]()
for error in errors
{
messages.append(error.localizedDescription)
}
errorMessage = messages.joined(separator: "\n")
default: errorMessage = error.failureReason
}
case let error as HarmonyError:
title = error.failureDescription
errorMessage = error.failureReason
case let error:
assertionFailure("Only HarmonyErrors should be thrown by syncing logic.")
title = nil
errorMessage = error.localizedDescription
}
cell.nameLabel.text = title
cell.errorLabel.text = errorMessage
}
return dataSource
}
let placeholderView = RSTPlaceholderView()
placeholderView.textLabel.text = NSLocalizedString("Sync Successful", comment: "")
placeholderView.detailTextLabel.text = NSLocalizedString("There were no errors during last sync.", comment: "")
let compositeDataSource = RSTCompositeTableViewDataSource(dataSources: dataSources)
compositeDataSource.proxy = self
compositeDataSource.placeholderView = placeholderView
return compositeDataSource
}
private func processResults() -> [(group: Group, errors: [Error])]
{
var errors = [Error]()
do
{
try self.result.verify()
}
catch SyncError.partial(let recordResults)
{
for (_, result) in recordResults
{
guard case .failure(let error) = result else { continue }
errors.append(error)
}
}
catch SyncError.other(.cancelled)
{
// Do nothing
}
catch let error as SyncError
{
let error = error.underlyingError ?? error
errors.append(error)
}
catch
{
assertionFailure("Non-SyncError thrown by sync result.")
errors.append(error)
}
var errorsByGroup = [Group: [Error]]()
for error in errors
{
let group: Group
switch error
{
case let error as RecordError:
guard let recordType = SyncManager.RecordType(rawValue: error.record.recordID.type) else { continue }
switch recordType
{
case .game: group = .game(error.record.recordID)
case .gameSave: group = .game(error.record.recordID)
case .gameCollection: group = .gameCollection
case .controllerSkin: group = .controllerSkin
case .gameControllerInputMapping: group = .gameControllerInputMapping
case .saveState:
guard let gameID = error.record.metadata?[.gameID] else { continue }
let recordID = RecordID(type: SyncManager.RecordType.game.rawValue, identifier: gameID)
group = .saveState(gameID: recordID)
case .cheat:
guard let gameID = error.record.metadata?[.gameID] else { continue }
let recordID = RecordID(type: SyncManager.RecordType.game.rawValue, identifier: gameID)
group = .cheat(gameID: recordID)
}
default: group = .other
}
errorsByGroup[group, default: []].append(error)
}
let sortedErrors = errorsByGroup.sorted { (a, b) in
let groupA = a.key
let groupB = b.key
// Sort initially by game, then sort by type.
// This way games and their associated records (such as save states) are visually grouped together.
switch (groupA, groupB)
{
// Game-related records, but different game identifiers, so sort by game identifiers (implicitly grouping related game records together).
// Using `fallthrough` for these cases seg faults the compiler (as of Swift 4.2.1), so we just duplicate the return expression.
case (.game(let a), .game(let b)) where a != b: return a.identifier < b.identifier
case (.game(let a), .saveState(let b)) where a != b: return a.identifier < b.identifier
case (.game(let a), .cheat(let b)) where a != b: return a.identifier < b.identifier
case (.saveState(let a), .game(let b)) where a != b: return a.identifier < b.identifier
case (.saveState(let a), .saveState(let b)) where a != b: return a.identifier < b.identifier
case (.saveState(let a), .cheat(let b)) where a != b: return a.identifier < b.identifier
case (.cheat(let a), .game(let b)) where a != b: return a.identifier < b.identifier
case (.cheat(let a), .saveState(let b)) where a != b: return a.identifier < b.identifier
case (.cheat(let a), .cheat(let b)) where a != b: return a.identifier < b.identifier
// Otherwise, just return their relative ordering.
case (.game, _): fallthrough
case (.saveState, _): fallthrough
case (.cheat, _): fallthrough
case (.controllerSkin, _): fallthrough
case (.gameControllerInputMapping, _): fallthrough
case (.gameCollection, _): fallthrough
case (.other, _): return groupA.sortIndex < groupB.sortIndex
}
}
return sortedErrors.map { (group: $0.key, errors: $0.value) }
}
func fetchGameNames() -> [RecordID: String]
{
let fetchRequest = Game.fetchRequest() as NSFetchRequest<Game>
fetchRequest.propertiesToFetch = [#keyPath(Game.name), #keyPath(Game.identifier)]
do
{
let games = try DatabaseManager.shared.viewContext.fetch(fetchRequest)
let gameNames = Dictionary(uniqueKeysWithValues: games.map { (RecordID(type: SyncManager.RecordType.game.rawValue, identifier: $0.identifier), $0.name) })
return gameNames
}
catch
{
print("Failed to fetch game names.", error)
return [:]
}
}
}
private extension SyncResultViewController
{
@IBAction func dismiss()
{
self.presentingViewController?.dismiss(animated: true, completion: nil)
}
}
extension SyncResultViewController
{
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
let section = self.sortedErrors[section]
switch section.group
{
case .controllerSkin: return NSLocalizedString("Controller Skins", comment: "")
case .gameCollection: return NSLocalizedString("Game Collections", comment: "")
case .gameControllerInputMapping: return NSLocalizedString("Input Mappings", comment: "")
case .other: return NSLocalizedString("Misc.", comment: "")
case .game:
guard let error = section.errors.first as? RecordError else { return nil }
return error.record.localizedName
case .saveState(let gameID):
guard let error = section.errors.first as? RecordError else { return nil }
if let gameName = self.gameNamesByRecordID[gameID] ?? error.record.metadata?[.gameName]
{
return gameName + " - " + NSLocalizedString("Save States", comment: "")
}
else
{
return NSLocalizedString("Save States", comment: "")
}
case .cheat(let gameID):
guard let error = section.errors.first as? RecordError else { return nil }
if let gameName = self.gameNamesByRecordID[gameID] ?? error.record.metadata?[.gameName]
{
return gameName + " - " + NSLocalizedString("Cheats", comment: "")
}
else
{
return NSLocalizedString("Cheats", comment: "")
}
}
}
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath?
{
let section = self.sortedErrors[indexPath.section]
switch section.group
{
case .other: return nil
default: return indexPath
}
}
}

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="PiK-Yl-5r8">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Results-->
<scene sceneID="bU4-NU-gHn">
<objects>
<tableViewController storyboardIdentifier="syncResultViewController" id="Vv7-67-y3h" customClass="SyncResultViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="Mge-4Z-RnG">
<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" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" rowHeight="81" id="LBH-gN-Qjn" customClass="SyncResultTableViewCell">
<rect key="frame" x="0.0" y="55.5" width="375" height="81"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="LBH-gN-Qjn" id="qeU-Pt-UrC">
<rect key="frame" x="0.0" y="0.0" width="375" height="80.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4.5" translatesAutoresizingMaskIntoConstraints="NO" id="sqc-Zb-cJa">
<rect key="frame" x="16" y="11" width="343" height="59"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title Screen" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="E5a-nn-ak9">
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="The record could not be uploaded because an error occured." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gzf-2D-v9F">
<rect key="frame" x="0.0" y="25" width="343" height="34"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="sqc-Zb-cJa" secondAttribute="trailing" id="uaR-cD-kNU"/>
<constraint firstItem="sqc-Zb-cJa" firstAttribute="leading" secondItem="qeU-Pt-UrC" secondAttribute="leadingMargin" id="xp4-eH-qd4"/>
<constraint firstItem="sqc-Zb-cJa" firstAttribute="top" secondItem="qeU-Pt-UrC" secondAttribute="topMargin" id="z2y-9Y-d1U"/>
<constraint firstItem="sqc-Zb-cJa" firstAttribute="bottom" secondItem="qeU-Pt-UrC" secondAttribute="bottomMargin" id="zT9-af-Xcc"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="errorLabel" destination="gzf-2D-v9F" id="QL8-uA-FvI"/>
<outlet property="nameLabel" destination="E5a-nn-ak9" id="iBv-cv-b6G"/>
<segue destination="Cj8-UD-irg" kind="show" identifier="showRecordStatus" id="bKJ-Fa-4lQ"/>
</connections>
</tableViewCell>
</prototypes>
<sections/>
<connections>
<outlet property="dataSource" destination="Vv7-67-y3h" id="9Yf-Hl-bIM"/>
<outlet property="delegate" destination="Vv7-67-y3h" id="fDj-g9-Whw"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Results" id="Qzj-eH-8qF">
<barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="s6x-aF-rco">
<connections>
<action selector="dismiss" destination="Vv7-67-y3h" id="gW2-WJ-4ii"/>
</connections>
</barButtonItem>
</navigationItem>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x5W-HZ-Oy6" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="365.60000000000002" y="-179.46026986506749"/>
</scene>
<!--recordSyncStatusViewController-->
<scene sceneID="rqP-Wm-wlP">
<objects>
<viewControllerPlaceholder storyboardName="Settings" referencedIdentifier="recordSyncStatusViewController" id="Cj8-UD-irg" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="xWn-zn-1t0" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1177" y="-179"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="d6h-g9-dij">
<objects>
<navigationController id="PiK-Yl-5r8" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EvV-dw-xfY">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="Vv7-67-y3h" kind="relationship" relationship="rootViewController" id="pdH-jQ-yc1"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="ems-7x-mVT" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-457" y="-178"/>
</scene>
</scenes>
</document>

1
External/Harmony vendored Submodule

@ -0,0 +1 @@
Subproject commit 2c8225d359528177eaf698cb01afaeb399bd37ce

2
External/Roxas vendored

@ -1 +1 @@
Subproject commit 8b544e7328de5f8481b5571ebf8e0a5312d1b74a
Subproject commit 0c1e8f6948c2c99844d05db79e8bf4e55d3211ba

View File

@ -4,7 +4,6 @@ use_frameworks!
inhibit_all_warnings!
target 'Delta' do
pod 'FileMD5Hash', '~> 2.0.0'
pod 'SQLite.swift', '~> 0.11.0'
pod 'SDWebImage', '~> 3.8'
pod 'Fabric', '~> 1.6.0'

View File

@ -2,7 +2,6 @@ PODS:
- Crashlytics (3.8.6):
- Fabric (~> 1.6.3)
- Fabric (1.6.13)
- FileMD5Hash (2.0.0)
- SDWebImage (3.8.2):
- SDWebImage/Core (= 3.8.2)
- SDWebImage/Core (3.8.2)
@ -14,19 +13,25 @@ PODS:
DEPENDENCIES:
- Crashlytics (~> 3.8.0)
- Fabric (~> 1.6.0)
- FileMD5Hash (~> 2.0.0)
- SDWebImage (~> 3.8)
- SMCalloutView
- SQLite.swift (~> 0.11.0)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- Crashlytics
- Fabric
- SDWebImage
- SMCalloutView
- SQLite.swift
SPEC CHECKSUMS:
Crashlytics: 95d05f4e4c19a771250c4bd9ce344d996de32bbf
Fabric: 2fb5676bc811af011a04513451f463dac6803206
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c'
SDWebImage: 098e97e6176540799c27e804c96653ee0833d13c
SMCalloutView: 5c0ee363dc8e7204b2fda17dfad38c93e9e23481
SQLite.swift: 3e3bee21da701b5b9f87c4a672cb54f233505692
PODFILE CHECKSUM: a1fb0ce1f1bb0e73380460cc4f946d297a4d5ff1
PODFILE CHECKSUM: 1d7f9ff69da571c7991621312e07aa4b16a0a074
COCOAPODS: 1.2.1
COCOAPODS: 1.5.3

202
Pods/FileMD5Hash/LICENSE generated
View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,29 +0,0 @@
/*
* FileHash.h
* FileMD5Hash
*
* Copyright © 2010-2014 Joel Lopes Da Silva. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#import <Foundation/Foundation.h>
@interface FileHash : NSObject
+ (NSString *)md5HashOfFileAtPath:(NSString *)filePath;
+ (NSString *)sha1HashOfFileAtPath:(NSString *)filePath;
+ (NSString *)sha512HashOfFileAtPath:(NSString *)filePath;
@end

View File

@ -1,127 +0,0 @@
/*
* FileHash.c
* FileMD5Hash
*
* Copyright © 2010-2014 Joel Lopes Da Silva. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Header file
#import "FileHash.h"
// System framework and libraries
#include <CommonCrypto/CommonDigest.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdint.h>
#include <stdio.h>
// Constants
static const size_t FileHashDefaultChunkSizeForReadingData = 4096;
// Function pointer types for functions used in the computation
// of a cryptographic hash.
typedef int (*FileHashInitFunction) (uint8_t *hashObjectPointer[]);
typedef int (*FileHashUpdateFunction) (uint8_t *hashObjectPointer[], const void *data, CC_LONG len);
typedef int (*FileHashFinalFunction) (unsigned char *md, uint8_t *hashObjectPointer[]);
// Structure used to describe a hash computation context.
typedef struct _FileHashComputationContext {
FileHashInitFunction initFunction;
FileHashUpdateFunction updateFunction;
FileHashFinalFunction finalFunction;
size_t digestLength;
uint8_t **hashObjectPointer;
} FileHashComputationContext;
#define FileHashComputationContextInitialize(context, hashAlgorithmName) \
CC_##hashAlgorithmName##_CTX hashObjectFor##hashAlgorithmName; \
context.initFunction = (FileHashInitFunction)&CC_##hashAlgorithmName##_Init; \
context.updateFunction = (FileHashUpdateFunction)&CC_##hashAlgorithmName##_Update; \
context.finalFunction = (FileHashFinalFunction)&CC_##hashAlgorithmName##_Final; \
context.digestLength = CC_##hashAlgorithmName##_DIGEST_LENGTH; \
context.hashObjectPointer = (uint8_t **)&hashObjectFor##hashAlgorithmName
@implementation FileHash
+ (NSString *)hashOfFileAtPath:(NSString *)filePath withComputationContext:(FileHashComputationContext *)context {
NSString *result = nil;
CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)filePath, kCFURLPOSIXPathStyle, (Boolean)false);
CFReadStreamRef readStream = fileURL ? CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL) : NULL;
BOOL didSucceed = readStream ? (BOOL)CFReadStreamOpen(readStream) : NO;
if (didSucceed) {
// Use default value for the chunk size for reading data.
const size_t chunkSizeForReadingData = FileHashDefaultChunkSizeForReadingData;
// Initialize the hash object
(*context->initFunction)(context->hashObjectPointer);
// Feed the data to the hash object.
BOOL hasMoreData = YES;
while (hasMoreData) {
uint8_t buffer[chunkSizeForReadingData];
CFIndex readBytesCount = CFReadStreamRead(readStream, (UInt8 *)buffer, (CFIndex)sizeof(buffer));
if (readBytesCount == -1) {
break;
} else if (readBytesCount == 0) {
hasMoreData = NO;
} else {
(*context->updateFunction)(context->hashObjectPointer, (const void *)buffer, (CC_LONG)readBytesCount);
}
}
// Compute the hash digest
unsigned char digest[context->digestLength];
(*context->finalFunction)(digest, context->hashObjectPointer);
// Close the read stream.
CFReadStreamClose(readStream);
// Proceed if the read operation succeeded.
didSucceed = !hasMoreData;
if (didSucceed) {
char hash[2 * sizeof(digest) + 1];
for (size_t i = 0; i < sizeof(digest); ++i) {
snprintf(hash + (2 * i), 3, "%02x", (int)(digest[i]));
}
result = [NSString stringWithUTF8String:hash];
}
}
if (readStream) CFRelease(readStream);
if (fileURL) CFRelease(fileURL);
return result;
}
+ (NSString *)md5HashOfFileAtPath:(NSString *)filePath {
FileHashComputationContext context;
FileHashComputationContextInitialize(context, MD5);
return [self hashOfFileAtPath:filePath withComputationContext:&context];
}
+ (NSString *)sha1HashOfFileAtPath:(NSString *)filePath {
FileHashComputationContext context;
FileHashComputationContextInitialize(context, SHA1);
return [self hashOfFileAtPath:filePath withComputationContext:&context];
}
+ (NSString *)sha512HashOfFileAtPath:(NSString *)filePath {
FileHashComputationContext context;
FileHashComputationContextInitialize(context, SHA512);
return [self hashOfFileAtPath:filePath withComputationContext:&context];
}
@end

View File

@ -1,3 +0,0 @@
Taken from [Joel's Writings](http://www.joel.lopes-da-silva.com/2010/09/07/compute-md5-or-sha-hash-of-large-file-efficiently-on-ios-and-mac-os-x/).
For more information and instructions on how to use, please refer to the [original blog post describing FileMD5Hash](http://www.joel.lopes-da-silva.com/2010/09/07/compute-md5-or-sha-hash-of-large-file-efficiently-on-ios-and-mac-os-x/).

17
Pods/Manifest.lock generated
View File

@ -2,7 +2,6 @@ PODS:
- Crashlytics (3.8.6):
- Fabric (~> 1.6.3)
- Fabric (1.6.13)
- FileMD5Hash (2.0.0)
- SDWebImage (3.8.2):
- SDWebImage/Core (= 3.8.2)
- SDWebImage/Core (3.8.2)
@ -14,19 +13,25 @@ PODS:
DEPENDENCIES:
- Crashlytics (~> 3.8.0)
- Fabric (~> 1.6.0)
- FileMD5Hash (~> 2.0.0)
- SDWebImage (~> 3.8)
- SMCalloutView
- SQLite.swift (~> 0.11.0)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- Crashlytics
- Fabric
- SDWebImage
- SMCalloutView
- SQLite.swift
SPEC CHECKSUMS:
Crashlytics: 95d05f4e4c19a771250c4bd9ce344d996de32bbf
Fabric: 2fb5676bc811af011a04513451f463dac6803206
FileMD5Hash: 3ed69cc19a21ff4d30ae8833fc104275ad2c7de0
SDWebImage: '098e97e6176540799c27e804c96653ee0833d13c'
SDWebImage: 098e97e6176540799c27e804c96653ee0833d13c
SMCalloutView: 5c0ee363dc8e7204b2fda17dfad38c93e9e23481
SQLite.swift: 3e3bee21da701b5b9f87c4a672cb54f233505692
PODFILE CHECKSUM: a1fb0ce1f1bb0e73380460cc4f946d297a4d5ff1
PODFILE CHECKSUM: 1d7f9ff69da571c7991621312e07aa4b16a0a074
COCOAPODS: 1.2.1
COCOAPODS: 1.5.3

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_FileMD5Hash : NSObject
@end
@implementation PodsDummy_FileMD5Hash
@end

View File

@ -1,12 +0,0 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif

View File

@ -1,17 +0,0 @@
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#else
#ifndef FOUNDATION_EXPORT
#if defined(__cplusplus)
#define FOUNDATION_EXPORT extern "C"
#else
#define FOUNDATION_EXPORT extern
#endif
#endif
#endif
#import "FileHash.h"
FOUNDATION_EXPORT double FileMD5HashVersionNumber;
FOUNDATION_EXPORT const unsigned char FileMD5HashVersionString[];

View File

@ -1,6 +0,0 @@
framework module FileMD5Hash {
umbrella header "FileMD5Hash-umbrella.h"
export *
module * { export * }
}

View File

@ -1,9 +0,0 @@
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/FileMD5Hash
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${CURRENT_PROJECT_VERSION}</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -9,212 +9,6 @@ Fabric: Copyright 2017 Google, Inc. All Rights Reserved. Use of this software is
Fabric: Copyright 2017 Google, Inc. All Rights Reserved. Use of this software is subject to the terms and conditions of the Fabric Software and Services Agreement located at https://fabric.io/terms. OSS: http://get.fabric.io/terms/opensource.txt
## FileMD5Hash
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## SDWebImage
Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com

View File

@ -32,218 +32,6 @@
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
</string>
<key>License</key>
<string>Apache License, Version 2.0</string>
<key>Title</key>
<string>FileMD5Hash</string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com

View File

@ -1,11 +1,28 @@
#!/bin/sh
set -e
set -u
set -o pipefail
if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
# frameworks to, so exit 0 (signalling the script phase was successful).
exit 0
fi
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
# Used as a return value for each invocation of `strip_invalid_archs` function.
STRIP_BINARY_RETVAL=0
# This protects against multiple targets copying the same framework dependency at the same time. The solution
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
# Copies and strips a vendored framework
install_framework()
{
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
@ -23,9 +40,9 @@ install_framework()
source="$(readlink "${source}")"
fi
# use filter instead of exclude so missing patterns dont' throw errors
echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
# Use filter instead of exclude so missing patterns don't throw errors.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
local basename
basename="$(basename -s .framework "$1")"
@ -54,12 +71,40 @@ install_framework()
fi
}
# Copies and strips a vendored dSYM
install_dsym() {
local source="$1"
if [ -r "$source" ]; then
# Copy the dSYM into a the targets temp dir.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
local basename
basename="$(basename -s .framework.dSYM "$source")"
binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
# Strip invalid architectures so "fat" simulator / device frameworks work on device
if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
strip_invalid_archs "$binary"
fi
if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
# Move the stripped file into its final destination.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
else
# The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
fi
fi
}
# Signs a framework with the provided identity
code_sign_if_enabled() {
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
# Use the current code_sign_identitiy
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'"
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
code_sign_cmd="$code_sign_cmd &"
@ -72,11 +117,19 @@ code_sign_if_enabled() {
# Strip invalid architectures
strip_invalid_archs() {
binary="$1"
# Get architectures for current file
archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
# Get architectures for current target binary
binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
# Intersect them with the architectures we are building for
intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
# If there are no archs supported by this binary then warn the user
if [[ -z "$intersected_archs" ]]; then
echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
STRIP_BINARY_RETVAL=0
return
fi
stripped=""
for arch in $archs; do
if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
for arch in $binary_archs; do
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
# Strip non-valid architectures in-place
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
stripped="$stripped $arch"
@ -85,20 +138,19 @@ strip_invalid_archs() {
if [[ "$stripped" ]]; then
echo "Stripped $binary of architectures:$stripped"
fi
STRIP_BINARY_RETVAL=1
}
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_framework "$BUILT_PRODUCTS_DIR/FileMD5Hash/FileMD5Hash.framework"
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.framework"
install_framework "$BUILT_PRODUCTS_DIR/SMCalloutView/SMCalloutView.framework"
install_framework "$BUILT_PRODUCTS_DIR/SQLite.swift/SQLite.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SMCalloutView/SMCalloutView.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SQLite.swift/SQLite.framework"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_framework "$BUILT_PRODUCTS_DIR/FileMD5Hash/FileMD5Hash.framework"
install_framework "$BUILT_PRODUCTS_DIR/SDWebImage/SDWebImage.framework"
install_framework "$BUILT_PRODUCTS_DIR/SMCalloutView/SMCalloutView.framework"
install_framework "$BUILT_PRODUCTS_DIR/SQLite.swift/SQLite.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SMCalloutView/SMCalloutView.framework"
install_framework "${BUILT_PRODUCTS_DIR}/SQLite.swift/SQLite.framework"
fi
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
wait

View File

@ -1,5 +1,13 @@
#!/bin/sh
set -e
set -u
set -o pipefail
if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
# If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
# resources to, so exit 0 (signalling the script phase was successful).
exit 0
fi
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
@ -8,7 +16,11 @@ RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
XCASSET_FILES=()
case "${TARGETED_DEVICE_FAMILY}" in
# This protects against multiple targets copying the same framework dependency at the same time. The solution
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
case "${TARGETED_DEVICE_FAMILY:-}" in
1,2)
TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
;;
@ -44,29 +56,29 @@ EOM
fi
case $RESOURCE_PATH in
*.storyboard)
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
;;
*.xib)
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
;;
*.framework)
echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
;;
*.xcdatamodel)
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
;;
*.xcdatamodeld)
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
;;
*.xcmappingmodel)
echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
;;
*.xcassets)
@ -74,7 +86,7 @@ EOM
XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
;;
*)
echo "$RESOURCE_PATH"
echo "$RESOURCE_PATH" || true
echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
;;
esac
@ -88,7 +100,7 @@ if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
fi
rm -f "$RESOURCES_TO_COPY"
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
then
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
@ -98,5 +110,9 @@ then
fi
done <<<"$OTHER_XCASSETS"
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
else
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
fi
fi

View File

@ -1,12 +1,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/SMCalloutView" "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift" "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SMCalloutView" "${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift" "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash/FileMD5Hash.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SMCalloutView/SMCalloutView.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift/SQLite.framework/Headers" -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/Crashlytics" -isystem "${PODS_ROOT}/Headers/Public/Fabric"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Crashlytics" -framework "Fabric" -framework "FileMD5Hash" -framework "SDWebImage" -framework "SMCalloutView" -framework "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SMCalloutView/SMCalloutView.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.framework/Headers"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Crashlytics" -framework "Fabric" -framework "SDWebImage" -framework "SMCalloutView" -framework "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods

View File

@ -1,12 +1,11 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash" "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage" "$PODS_CONFIGURATION_BUILD_DIR/SMCalloutView" "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift" "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SMCalloutView" "${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift" "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/FileMD5Hash/FileMD5Hash.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SDWebImage/SDWebImage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SMCalloutView/SMCalloutView.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/SQLite.swift/SQLite.framework/Headers" -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/Crashlytics" -isystem "${PODS_ROOT}/Headers/Public/Fabric"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Crashlytics" -framework "Fabric" -framework "FileMD5Hash" -framework "SDWebImage" -framework "SMCalloutView" -framework "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SMCalloutView/SMCalloutView.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift/SQLite.framework/Headers"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "Crashlytics" -framework "Fabric" -framework "SDWebImage" -framework "SMCalloutView" -framework "SQLite" -framework "Security" -framework "SystemConfiguration" -framework "UIKit"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods

View File

@ -1,9 +1,8 @@
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/SDWebImage
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
OTHER_LDFLAGS = -framework "ImageIO"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SDWebImage
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}

View File

@ -1,8 +1,7 @@
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/SMCalloutView
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SMCalloutView
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SMCalloutView
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}

View File

@ -1,10 +1,9 @@
CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/SQLite.swift
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SQLite.swift
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Crashlytics" "${PODS_ROOT}/Headers/Public/Fabric"
OTHER_LDFLAGS = -l"sqlite3"
OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
PODS_BUILD_DIR = $BUILD_DIR
PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/SQLite.swift
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.150",
"alpha" : "1.000",
"blue" : "0.150",
"green" : "0.150"
}
}
}
]
}

View File

@ -0,0 +1,20 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "139",
"alpha" : "1.000",
"blue" : "247",
"green" : "40"
}
}
}
]
}