[AltStore] Adds redesigned AppViewController to view/download AltStore apps
@@ -22,6 +22,12 @@
|
|||||||
BF1E315A22A0620000370A3C /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
|
BF1E315A22A0620000370A3C /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
|
||||||
BF1E315F22A0635900370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
|
BF1E315F22A0635900370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
|
||||||
BF1E316022A0636400370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
|
BF1E316022A0636400370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
|
||||||
|
BF3D648822E79A3700E9056B /* AppPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D648722E79A3700E9056B /* AppPermission.swift */; };
|
||||||
|
BF3D648D22E79AC800E9056B /* ALTAppPermission.m in Sources */ = {isa = PBXBuildFile; fileRef = BF3D648C22E79AC800E9056B /* ALTAppPermission.m */; };
|
||||||
|
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D649C22E7AC1B00E9056B /* PermissionPopoverViewController.swift */; };
|
||||||
|
BF3D649F22E7B24C00E9056B /* CollapsingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D649E22E7B24C00E9056B /* CollapsingTextView.swift */; };
|
||||||
|
BF3D64A222E8031100E9056B /* MergePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D64A122E8031100E9056B /* MergePolicy.swift */; };
|
||||||
|
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */; };
|
||||||
BF3F786422CAA41E008FBD20 /* ALTDeviceManager+Installation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */; };
|
BF3F786422CAA41E008FBD20 /* ALTDeviceManager+Installation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */; };
|
||||||
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002D22A714AF0051E2BC /* Keychain.swift */; };
|
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002D22A714AF0051E2BC /* Keychain.swift */; };
|
||||||
BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */; };
|
BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */; };
|
||||||
@@ -103,6 +109,8 @@
|
|||||||
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */; };
|
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */; };
|
||||||
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */ = {isa = PBXBuildFile; fileRef = BF770E6822BD57DD002A40FE /* Silence.m4a */; };
|
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */ = {isa = PBXBuildFile; fileRef = BF770E6822BD57DD002A40FE /* Silence.m4a */; };
|
||||||
BF7B9EF322B82B1F0042C873 /* FetchAppsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7B9EF222B82B1F0042C873 /* FetchAppsOperation.swift */; };
|
BF7B9EF322B82B1F0042C873 /* FetchAppsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7B9EF222B82B1F0042C873 /* FetchAppsOperation.swift */; };
|
||||||
|
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C122E659F700049BA1 /* AppContentViewController.swift */; };
|
||||||
|
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C322E662D300049BA1 /* AppViewController.swift */; };
|
||||||
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */; };
|
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */; };
|
||||||
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.swift */; };
|
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.swift */; };
|
||||||
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4822DD0742008935CF /* ScreenshotCollectionViewCell.swift */; };
|
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4822DD0742008935CF /* ScreenshotCollectionViewCell.swift */; };
|
||||||
@@ -125,7 +133,6 @@
|
|||||||
BFD247882284BB4200981D42 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFD247862284BB3B00981D42 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
BFD247882284BB4200981D42 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFD247862284BB3B00981D42 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478B2284C4C300981D42 /* AppIconImageView.swift */; };
|
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478B2284C4C300981D42 /* AppIconImageView.swift */; };
|
||||||
BFD2478F2284C8F900981D42 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478E2284C8F900981D42 /* Button.swift */; };
|
BFD2478F2284C8F900981D42 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478E2284C8F900981D42 /* Button.swift */; };
|
||||||
BFD2479C2284E19A00981D42 /* AppDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2479B2284E19A00981D42 /* AppDetailViewController.swift */; };
|
|
||||||
BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */; };
|
BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */; };
|
||||||
BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BD322A0800A000B7ED1 /* ServerManager.swift */; };
|
BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BD322A0800A000B7ED1 /* ServerManager.swift */; };
|
||||||
BFD52C0122A1A9CB000B7ED1 /* ptrarray.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE522A1A9CA000B7ED1 /* ptrarray.c */; };
|
BFD52C0122A1A9CB000B7ED1 /* ptrarray.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE522A1A9CA000B7ED1 /* ptrarray.c */; };
|
||||||
@@ -252,6 +259,13 @@
|
|||||||
BF1E314922A060F400370A3C /* NSError+ALTServerError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+ALTServerError.m"; sourceTree = "<group>"; };
|
BF1E314922A060F400370A3C /* NSError+ALTServerError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+ALTServerError.m"; sourceTree = "<group>"; };
|
||||||
BF1E315022A0616100370A3C /* libAltKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAltKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
BF1E315022A0616100370A3C /* libAltKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAltKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
BF219A7E22CAC431007676A6 /* AltStore.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltStore.entitlements; sourceTree = "<group>"; };
|
BF219A7E22CAC431007676A6 /* AltStore.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltStore.entitlements; sourceTree = "<group>"; };
|
||||||
|
BF3D648722E79A3700E9056B /* AppPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermission.swift; sourceTree = "<group>"; };
|
||||||
|
BF3D648B22E79AC800E9056B /* ALTAppPermission.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTAppPermission.h; sourceTree = "<group>"; };
|
||||||
|
BF3D648C22E79AC800E9056B /* ALTAppPermission.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTAppPermission.m; sourceTree = "<group>"; };
|
||||||
|
BF3D649C22E7AC1B00E9056B /* PermissionPopoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionPopoverViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BF3D649E22E7B24C00E9056B /* CollapsingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsingTextView.swift; sourceTree = "<group>"; };
|
||||||
|
BF3D64A122E8031100E9056B /* MergePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergePolicy.swift; sourceTree = "<group>"; };
|
||||||
|
BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewControllerCells.swift; sourceTree = "<group>"; };
|
||||||
BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTDeviceManager+Installation.swift"; sourceTree = "<group>"; };
|
BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTDeviceManager+Installation.swift"; sourceTree = "<group>"; };
|
||||||
BF43002D22A714AF0051E2BC /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
BF43002D22A714AF0051E2BC /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
||||||
BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AltStore.swift"; sourceTree = "<group>"; };
|
BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AltStore.swift"; sourceTree = "<group>"; };
|
||||||
@@ -338,6 +352,8 @@
|
|||||||
BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = "<group>"; };
|
BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = "<group>"; };
|
||||||
BF770E6822BD57DD002A40FE /* Silence.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Silence.m4a; sourceTree = "<group>"; };
|
BF770E6822BD57DD002A40FE /* Silence.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Silence.m4a; sourceTree = "<group>"; };
|
||||||
BF7B9EF222B82B1F0042C873 /* FetchAppsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAppsOperation.swift; sourceTree = "<group>"; };
|
BF7B9EF222B82B1F0042C873 /* FetchAppsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAppsOperation.swift; sourceTree = "<group>"; };
|
||||||
|
BF8F69C122E659F700049BA1 /* AppContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BF8F69C322E662D300049BA1 /* AppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewController.swift; sourceTree = "<group>"; };
|
||||||
BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseViewController.swift; sourceTree = "<group>"; };
|
BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseViewController.swift; sourceTree = "<group>"; };
|
||||||
BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseCollectionViewCell.swift; sourceTree = "<group>"; };
|
BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
BF9ABA4822DD0742008935CF /* ScreenshotCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotCollectionViewCell.swift; sourceTree = "<group>"; };
|
BF9ABA4822DD0742008935CF /* ScreenshotCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
@@ -363,7 +379,6 @@
|
|||||||
BFD247862284BB3B00981D42 /* Roxas.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
BFD247862284BB3B00981D42 /* Roxas.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
BFD2478B2284C4C300981D42 /* AppIconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconImageView.swift; sourceTree = "<group>"; };
|
BFD2478B2284C4C300981D42 /* AppIconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconImageView.swift; sourceTree = "<group>"; };
|
||||||
BFD2478E2284C8F900981D42 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
BFD2478E2284C8F900981D42 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
||||||
BFD2479B2284E19A00981D42 /* AppDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailViewController.swift; sourceTree = "<group>"; };
|
|
||||||
BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
||||||
BFD52BD222A06EFB000B7ED1 /* AltKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AltKit.h; sourceTree = "<group>"; };
|
BFD52BD222A06EFB000B7ED1 /* AltKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AltKit.h; sourceTree = "<group>"; };
|
||||||
BFD52BD322A0800A000B7ED1 /* ServerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerManager.swift; sourceTree = "<group>"; };
|
BFD52BD322A0800A000B7ED1 /* ServerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerManager.swift; sourceTree = "<group>"; };
|
||||||
@@ -479,6 +494,26 @@
|
|||||||
path = AltKit;
|
path = AltKit;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
BF3D648922E79A7700E9056B /* Types */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF3D648B22E79AC800E9056B /* ALTAppPermission.h */,
|
||||||
|
BF3D648C22E79AC800E9056B /* ALTAppPermission.m */,
|
||||||
|
);
|
||||||
|
path = Types;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
BF3D64A022E7FAD800E9056B /* App Detail */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF8F69C322E662D300049BA1 /* AppViewController.swift */,
|
||||||
|
BF8F69C122E659F700049BA1 /* AppContentViewController.swift */,
|
||||||
|
BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */,
|
||||||
|
BF3D649C22E7AC1B00E9056B /* PermissionPopoverViewController.swift */,
|
||||||
|
);
|
||||||
|
path = "App Detail";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
BF45868E229872EA00BD7491 /* AltServer */ = {
|
BF45868E229872EA00BD7491 /* AltServer */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -721,13 +756,15 @@
|
|||||||
BFD247732284B9A500981D42 /* Main.storyboard */,
|
BFD247732284B9A500981D42 /* Main.storyboard */,
|
||||||
BFE6325822A83BA800F30809 /* Authentication */,
|
BFE6325822A83BA800F30809 /* Authentication */,
|
||||||
BF9ABA4322DCFF33008935CF /* Browse */,
|
BF9ABA4322DCFF33008935CF /* Browse */,
|
||||||
BFD2478A2284C49000981D42 /* Apps */,
|
BF3D64A022E7FAD800E9056B /* App Detail */,
|
||||||
BFBBE2E2229320A2002097FA /* My Apps */,
|
BFBBE2E2229320A2002097FA /* My Apps */,
|
||||||
BFDB69FB22A9A7A6007EA6D6 /* Account */,
|
BFDB69FB22A9A7A6007EA6D6 /* Account */,
|
||||||
|
BFD2478A2284C49000981D42 /* Managing Apps */,
|
||||||
BFC51D7922972F1F00388324 /* Server */,
|
BFC51D7922972F1F00388324 /* Server */,
|
||||||
BFD247982284D7FC00981D42 /* Model */,
|
BFD247982284D7FC00981D42 /* Model */,
|
||||||
BFDB6A0922AAEDA1007EA6D6 /* Operations */,
|
BFDB6A0922AAEDA1007EA6D6 /* Operations */,
|
||||||
BFD2478D2284C4C700981D42 /* Components */,
|
BFD2478D2284C4C700981D42 /* Components */,
|
||||||
|
BF3D648922E79A7700E9056B /* Types */,
|
||||||
BFDB6A0622A9B114007EA6D6 /* Protocols */,
|
BFDB6A0622A9B114007EA6D6 /* Protocols */,
|
||||||
BFD2479D2284FBBD00981D42 /* Extensions */,
|
BFD2479D2284FBBD00981D42 /* Extensions */,
|
||||||
BFD247962284D7C100981D42 /* Resources */,
|
BFD247962284D7C100981D42 /* Resources */,
|
||||||
@@ -751,13 +788,12 @@
|
|||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
BFD2478A2284C49000981D42 /* Apps */ = {
|
BFD2478A2284C49000981D42 /* Managing Apps */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BFD2479B2284E19A00981D42 /* AppDetailViewController.swift */,
|
|
||||||
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */,
|
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */,
|
||||||
);
|
);
|
||||||
path = Apps;
|
path = "Managing Apps";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
BFD2478D2284C4C700981D42 /* Components */ = {
|
BFD2478D2284C4C700981D42 /* Components */ = {
|
||||||
@@ -770,6 +806,7 @@
|
|||||||
BF9ABA4A22DD137F008935CF /* NavigationBar.swift */,
|
BF9ABA4A22DD137F008935CF /* NavigationBar.swift */,
|
||||||
BF9ABA4C22DD16DE008935CF /* PillButton.swift */,
|
BF9ABA4C22DD16DE008935CF /* PillButton.swift */,
|
||||||
BF18B0F022E25DF9005C4CF5 /* ToastView.swift */,
|
BF18B0F022E25DF9005C4CF5 /* ToastView.swift */,
|
||||||
|
BF3D649E22E7B24C00E9056B /* CollapsingTextView.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -799,8 +836,10 @@
|
|||||||
children = (
|
children = (
|
||||||
BFBBE2DB22931B20002097FA /* AltStore.xcdatamodeld */,
|
BFBBE2DB22931B20002097FA /* AltStore.xcdatamodeld */,
|
||||||
BFB11691229322E400BB457C /* DatabaseManager.swift */,
|
BFB11691229322E400BB457C /* DatabaseManager.swift */,
|
||||||
|
BF3D64A122E8031100E9056B /* MergePolicy.swift */,
|
||||||
BFE6326722A858F300F30809 /* Account.swift */,
|
BFE6326722A858F300F30809 /* Account.swift */,
|
||||||
BFBBE2DE22931F73002097FA /* App.swift */,
|
BFBBE2DE22931F73002097FA /* App.swift */,
|
||||||
|
BF3D648722E79A3700E9056B /* AppPermission.swift */,
|
||||||
BFBBE2E022931F81002097FA /* InstalledApp.swift */,
|
BFBBE2E022931F81002097FA /* InstalledApp.swift */,
|
||||||
BFE6326522A857C100F30809 /* Team.swift */,
|
BFE6326522A857C100F30809 /* Team.swift */,
|
||||||
);
|
);
|
||||||
@@ -1223,6 +1262,7 @@
|
|||||||
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */,
|
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */,
|
||||||
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
|
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
|
||||||
BF7B9EF322B82B1F0042C873 /* FetchAppsOperation.swift in Sources */,
|
BF7B9EF322B82B1F0042C873 /* FetchAppsOperation.swift in Sources */,
|
||||||
|
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */,
|
||||||
BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */,
|
BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */,
|
||||||
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
|
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
|
||||||
BFDB69FD22A9A7B7007EA6D6 /* AccountViewController.swift in Sources */,
|
BFDB69FD22A9A7B7007EA6D6 /* AccountViewController.swift in Sources */,
|
||||||
@@ -1235,27 +1275,33 @@
|
|||||||
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */,
|
BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */,
|
||||||
BF770E5422BC044E002A40FE /* AppOperationContext.swift in Sources */,
|
BF770E5422BC044E002A40FE /* AppOperationContext.swift in Sources */,
|
||||||
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
|
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */,
|
||||||
|
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
||||||
BFE6326822A858F300F30809 /* Account.swift in Sources */,
|
BFE6326822A858F300F30809 /* Account.swift in Sources */,
|
||||||
BFE6326622A857C200F30809 /* Team.swift in Sources */,
|
BFE6326622A857C200F30809 /* Team.swift in Sources */,
|
||||||
BFD2479C2284E19A00981D42 /* AppDetailViewController.swift in Sources */,
|
|
||||||
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
||||||
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */,
|
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */,
|
||||||
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */,
|
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */,
|
||||||
|
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */,
|
||||||
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */,
|
BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */,
|
||||||
BFE6325C22A83C0100F30809 /* AuthenticationViewController.swift in Sources */,
|
BFE6325C22A83C0100F30809 /* AuthenticationViewController.swift in Sources */,
|
||||||
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */,
|
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */,
|
||||||
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */,
|
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */,
|
||||||
|
BF3D648822E79A3700E9056B /* AppPermission.swift in Sources */,
|
||||||
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
||||||
|
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
|
||||||
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */,
|
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */,
|
||||||
BF770E5822BC3D0F002A40FE /* OperationGroup.swift in Sources */,
|
BF770E5822BC3D0F002A40FE /* OperationGroup.swift in Sources */,
|
||||||
BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */,
|
BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */,
|
||||||
|
BF3D649F22E7B24C00E9056B /* CollapsingTextView.swift in Sources */,
|
||||||
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */,
|
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */,
|
||||||
BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */,
|
BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */,
|
||||||
BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */,
|
BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */,
|
||||||
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */,
|
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */,
|
||||||
|
BF3D64A222E8031100E9056B /* MergePolicy.swift in Sources */,
|
||||||
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */,
|
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */,
|
||||||
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */,
|
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */,
|
||||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||||
|
BF3D648D22E79AC800E9056B /* ALTAppPermission.m in Sources */,
|
||||||
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
||||||
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
||||||
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */,
|
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */,
|
||||||
@@ -1646,6 +1692,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltStore;
|
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltStore;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
@@ -1671,6 +1718,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltStore;
|
PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltStore;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,3 +3,4 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "NSError+ALTServerError.h"
|
#import "NSError+ALTServerError.h"
|
||||||
|
#import "ALTAppPermission.h"
|
||||||
|
|||||||
178
AltStore/App Detail/AppContentViewController.swift
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
//
|
||||||
|
// AppContentViewController.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/22/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
import Roxas
|
||||||
|
|
||||||
|
extension AppContentViewController
|
||||||
|
{
|
||||||
|
private enum Row: Int, CaseIterable
|
||||||
|
{
|
||||||
|
case subtitle
|
||||||
|
case screenshots
|
||||||
|
case description
|
||||||
|
case versionDescription
|
||||||
|
case permissions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppContentViewController: UITableViewController
|
||||||
|
{
|
||||||
|
var app: App!
|
||||||
|
|
||||||
|
private lazy var screenshotsDataSource = self.makeScreenshotsDataSource()
|
||||||
|
private lazy var permissionsDataSource = self.makePermissionsDataSource()
|
||||||
|
|
||||||
|
@IBOutlet private var subtitleLabel: UILabel!
|
||||||
|
@IBOutlet private var descriptionTextView: CollapsingTextView!
|
||||||
|
@IBOutlet private var versionDescriptionTextView: CollapsingTextView!
|
||||||
|
|
||||||
|
@IBOutlet private var screenshotsCollectionView: UICollectionView!
|
||||||
|
@IBOutlet private var permissionsCollectionView: UICollectionView!
|
||||||
|
|
||||||
|
var preferredScreenshotSize: CGSize? {
|
||||||
|
guard let image = self.screenshotsDataSource.items.first else { return nil }
|
||||||
|
|
||||||
|
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
|
||||||
|
|
||||||
|
let aspectRatio = image.size.height / image.size.width
|
||||||
|
|
||||||
|
let width = self.screenshotsCollectionView.bounds.width - (layout.minimumInteritemSpacing * 2)
|
||||||
|
|
||||||
|
let itemWidth = width / 1.5
|
||||||
|
let itemHeight = itemWidth * aspectRatio
|
||||||
|
|
||||||
|
return CGSize(width: itemWidth, height: itemHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
self.tableView.contentInset.bottom = 20
|
||||||
|
|
||||||
|
self.screenshotsCollectionView.dataSource = self.screenshotsDataSource
|
||||||
|
self.permissionsCollectionView.dataSource = self.permissionsDataSource
|
||||||
|
|
||||||
|
self.subtitleLabel.text = self.app.subtitle
|
||||||
|
self.descriptionTextView.text = self.app.localizedDescription
|
||||||
|
self.versionDescriptionTextView.text = self.app.versionDescription
|
||||||
|
|
||||||
|
self.descriptionTextView.maximumNumberOfLines = 5
|
||||||
|
self.descriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||||
|
|
||||||
|
self.versionDescriptionTextView.maximumNumberOfLines = 3
|
||||||
|
self.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLayoutSubviews()
|
||||||
|
{
|
||||||
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
|
guard var size = self.preferredScreenshotSize else { return }
|
||||||
|
size.height = min(size.height, self.screenshotsCollectionView.bounds.height) // Silence temporary "item too tall" warning.
|
||||||
|
|
||||||
|
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
|
||||||
|
layout.itemSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
|
{
|
||||||
|
guard segue.identifier == "showPermission" else { return }
|
||||||
|
|
||||||
|
guard let cell = sender as? UICollectionViewCell, let indexPath = self.permissionsCollectionView.indexPath(for: cell) else { return }
|
||||||
|
|
||||||
|
let permission = self.permissionsDataSource.item(at: indexPath)
|
||||||
|
|
||||||
|
let maximumWidth = self.view.bounds.width - 20
|
||||||
|
|
||||||
|
let permissionPopoverViewController = segue.destination as! PermissionPopoverViewController
|
||||||
|
permissionPopoverViewController.permission = permission
|
||||||
|
permissionPopoverViewController.view.widthAnchor.constraint(lessThanOrEqualToConstant: maximumWidth).isActive = true
|
||||||
|
|
||||||
|
let size = permissionPopoverViewController.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
||||||
|
permissionPopoverViewController.preferredContentSize = size
|
||||||
|
|
||||||
|
permissionPopoverViewController.popoverPresentationController?.delegate = self
|
||||||
|
permissionPopoverViewController.popoverPresentationController?.sourceRect = cell.frame
|
||||||
|
permissionPopoverViewController.popoverPresentationController?.sourceView = self.permissionsCollectionView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension AppContentViewController
|
||||||
|
{
|
||||||
|
func makeScreenshotsDataSource() -> RSTArrayCollectionViewDataSource<UIImage>
|
||||||
|
{
|
||||||
|
let screenshots = self.app.screenshotNames.compactMap(UIImage.init(named:))
|
||||||
|
|
||||||
|
let dataSource = RSTArrayCollectionViewDataSource(items: screenshots)
|
||||||
|
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
|
||||||
|
let cell = cell as! ScreenshotCollectionViewCell
|
||||||
|
cell.imageView.image = screenshot
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataSource
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePermissionsDataSource() -> RSTArrayCollectionViewDataSource<AppPermission>
|
||||||
|
{
|
||||||
|
let dataSource = RSTArrayCollectionViewDataSource(items: self.app.permissions)
|
||||||
|
dataSource.cellConfigurationHandler = { (cell, permission, indexPath) in
|
||||||
|
let cell = cell as! PermissionCollectionViewCell
|
||||||
|
cell.button.setImage(permission.type.icon, for: .normal)
|
||||||
|
cell.textLabel.text = permission.type.localizedShortName
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension AppContentViewController
|
||||||
|
{
|
||||||
|
@objc func toggleCollapsingSection(_ sender: UIButton)
|
||||||
|
{
|
||||||
|
let indexPath: IndexPath
|
||||||
|
|
||||||
|
switch sender
|
||||||
|
{
|
||||||
|
case self.descriptionTextView.moreButton: indexPath = IndexPath(row: Row.description.rawValue, section: 0)
|
||||||
|
case self.versionDescriptionTextView.moreButton: indexPath = IndexPath(row: Row.versionDescription.rawValue, section: 0)
|
||||||
|
default: return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable animations to prevent some potentially strange ones.
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.tableView.reloadRows(at: [indexPath], with: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppContentViewController
|
||||||
|
{
|
||||||
|
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
|
||||||
|
{
|
||||||
|
cell.tintColor = self.app.tintColor
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
|
||||||
|
{
|
||||||
|
guard indexPath.row == Row.screenshots.rawValue else { return super.tableView(tableView, heightForRowAt: indexPath) }
|
||||||
|
|
||||||
|
guard let size = self.preferredScreenshotSize else { return 0.0 }
|
||||||
|
return size.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppContentViewController: UIPopoverPresentationControllerDelegate
|
||||||
|
{
|
||||||
|
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
|
||||||
|
{
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
}
|
||||||
43
AltStore/App Detail/AppContentViewControllerCells.swift
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// AppContentViewControllerCells.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/24/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class PermissionCollectionViewCell: UICollectionViewCell
|
||||||
|
{
|
||||||
|
@IBOutlet var button: UIButton!
|
||||||
|
@IBOutlet var textLabel: UILabel!
|
||||||
|
|
||||||
|
override func layoutSubviews()
|
||||||
|
{
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
self.button.layer.cornerRadius = self.button.bounds.midY
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tintColorDidChange()
|
||||||
|
{
|
||||||
|
super.tintColorDidChange()
|
||||||
|
|
||||||
|
self.button.backgroundColor = self.tintColor.withAlphaComponent(0.15)
|
||||||
|
self.textLabel.textColor = self.tintColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppContentTableViewCell: UITableViewCell
|
||||||
|
{
|
||||||
|
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
||||||
|
{
|
||||||
|
// Ensure cell is laid out so it will report correct size.
|
||||||
|
self.layoutIfNeeded()
|
||||||
|
|
||||||
|
let size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
406
AltStore/App Detail/AppViewController.swift
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
//
|
||||||
|
// AppViewController.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/22/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
import Roxas
|
||||||
|
|
||||||
|
class AppViewController: UIViewController
|
||||||
|
{
|
||||||
|
var app: App!
|
||||||
|
|
||||||
|
private var contentViewController: AppContentViewController!
|
||||||
|
private var contentViewControllerShadowView: UIView!
|
||||||
|
|
||||||
|
private var blurAnimator: UIViewPropertyAnimator?
|
||||||
|
|
||||||
|
private var contentSizeObservation: NSKeyValueObservation?
|
||||||
|
|
||||||
|
@IBOutlet private var scrollView: UIScrollView!
|
||||||
|
@IBOutlet private var contentView: UIView!
|
||||||
|
|
||||||
|
@IBOutlet private var headerView: UIView!
|
||||||
|
@IBOutlet private var headerContentView: UIView!
|
||||||
|
|
||||||
|
@IBOutlet private var backButton: UIButton!
|
||||||
|
@IBOutlet private var backButtonContainerView: UIVisualEffectView!
|
||||||
|
|
||||||
|
@IBOutlet private var nameLabel: UILabel!
|
||||||
|
@IBOutlet private var developerLabel: UILabel!
|
||||||
|
@IBOutlet private var downloadButton: PillButton!
|
||||||
|
@IBOutlet private var appIconImageView: UIImageView!
|
||||||
|
|
||||||
|
@IBOutlet private var backgroundAppIconImageView: UIImageView!
|
||||||
|
@IBOutlet private var backgroundBlurView: UIVisualEffectView!
|
||||||
|
|
||||||
|
private var _isEnteringForeground = false
|
||||||
|
private var _backgroundBlurEffect: UIBlurEffect?
|
||||||
|
private var _backgroundBlurTintColor: UIColor?
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
self.contentViewControllerShadowView = UIView()
|
||||||
|
self.contentViewControllerShadowView.backgroundColor = .white
|
||||||
|
self.contentViewControllerShadowView.layer.cornerRadius = 38
|
||||||
|
self.contentViewControllerShadowView.layer.shadowColor = UIColor.black.cgColor
|
||||||
|
self.contentViewControllerShadowView.layer.shadowOffset = CGSize(width: 0, height: -1)
|
||||||
|
self.contentViewControllerShadowView.layer.shadowRadius = 10
|
||||||
|
self.contentViewControllerShadowView.layer.shadowOpacity = 0.3
|
||||||
|
self.contentViewController.view.superview?.insertSubview(self.contentViewControllerShadowView, at: 0)
|
||||||
|
|
||||||
|
self.contentView.addGestureRecognizer(self.scrollView.panGestureRecognizer)
|
||||||
|
|
||||||
|
self.contentViewController.view.layer.cornerRadius = 38
|
||||||
|
self.contentViewController.view.layer.masksToBounds = true
|
||||||
|
|
||||||
|
self.contentViewController.tableView.panGestureRecognizer.require(toFail: self.scrollView.panGestureRecognizer)
|
||||||
|
self.contentViewController.tableView.showsVerticalScrollIndicator = false
|
||||||
|
|
||||||
|
self.headerView.frame = CGRect(x: 0, y: 0, width: 300, height: 93)
|
||||||
|
self.headerView.layer.cornerRadius = 24
|
||||||
|
self.headerView.layer.masksToBounds = true
|
||||||
|
|
||||||
|
// Bring to front so the scroll indicators are visible.
|
||||||
|
self.view.bringSubviewToFront(self.scrollView)
|
||||||
|
self.scrollView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.nameLabel.text = self.app.name
|
||||||
|
self.developerLabel.text = self.app.developerName
|
||||||
|
self.developerLabel.textColor = self.app.tintColor
|
||||||
|
self.appIconImageView.image = UIImage(named: self.app.iconName)
|
||||||
|
self.downloadButton.tintColor = self.app.tintColor
|
||||||
|
self.backgroundAppIconImageView.image = UIImage(named: self.app.iconName)
|
||||||
|
|
||||||
|
self.backButtonContainerView.tintColor = self.app.tintColor
|
||||||
|
|
||||||
|
self.contentSizeObservation = self.contentViewController.tableView.observe(\.contentSize) { (tableView, change) in
|
||||||
|
self.view.setNeedsLayout()
|
||||||
|
self.view.layoutIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(AppViewController.didChangeApp(_:)), name: .NSManagedObjectContextObjectsDidChange, object: DatabaseManager.shared.viewContext)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(AppViewController.willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||||
|
|
||||||
|
self._backgroundBlurEffect = self.backgroundBlurView.effect as? UIBlurEffect
|
||||||
|
self._backgroundBlurTintColor = self.backgroundBlurView.contentView.backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool)
|
||||||
|
{
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
self.prepareBlur()
|
||||||
|
|
||||||
|
// Update blur immediately.
|
||||||
|
self.view.setNeedsLayout()
|
||||||
|
self.view.layoutIfNeeded()
|
||||||
|
|
||||||
|
self.transitionCoordinator?.animate(alongsideTransition: { (context) in
|
||||||
|
self.hideNavigationBar()
|
||||||
|
}, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool)
|
||||||
|
{
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
self.hideNavigationBar()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool)
|
||||||
|
{
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
// Guard against "dismissing" when presenting via 3D Touch pop.
|
||||||
|
guard self.navigationController != nil else { return }
|
||||||
|
|
||||||
|
// Store reference since self.navigationController will be nil after disappearing.
|
||||||
|
let navigationController = self.navigationController
|
||||||
|
navigationController?.navigationBar.barStyle = .default // Don't animate, or else status bar might appear messed-up.
|
||||||
|
|
||||||
|
self.transitionCoordinator?.animate(alongsideTransition: { (context) in
|
||||||
|
self.showNavigationBar(for: navigationController)
|
||||||
|
}, completion: { (context) in
|
||||||
|
if context.isCancelled
|
||||||
|
{
|
||||||
|
self.hideNavigationBar(for: navigationController)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.showNavigationBar(for: navigationController)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
|
{
|
||||||
|
guard segue.identifier == "embedAppContentViewController" else { return }
|
||||||
|
|
||||||
|
self.contentViewController = segue.destination as? AppContentViewController
|
||||||
|
self.contentViewController.app = self.app
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLayoutSubviews()
|
||||||
|
{
|
||||||
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
|
if self._isEnteringForeground
|
||||||
|
{
|
||||||
|
// Returning from background messes up some of our UI, so reset affected components now.
|
||||||
|
|
||||||
|
if self.navigationController?.topViewController == self
|
||||||
|
{
|
||||||
|
self.hideNavigationBar()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.prepareBlur()
|
||||||
|
|
||||||
|
self._isEnteringForeground = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusBarHeight = UIApplication.shared.statusBarFrame.height
|
||||||
|
|
||||||
|
let inset = 12 as CGFloat
|
||||||
|
let padding = 20 as CGFloat
|
||||||
|
|
||||||
|
let backButtonSize = self.backButton.sizeThatFits(CGSize(width: 1000, height: 1000))
|
||||||
|
let backButtonFrame = CGRect(x: inset, y: statusBarHeight,
|
||||||
|
width: backButtonSize.width + 20, height: backButtonSize.height + 20)
|
||||||
|
|
||||||
|
var headerFrame = CGRect(x: inset, y: 0, width: self.view.bounds.width - inset * 2, height: self.headerView.bounds.height)
|
||||||
|
var contentFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)
|
||||||
|
var backgroundIconFrame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.width)
|
||||||
|
|
||||||
|
let minimumHeaderY = backButtonFrame.maxY + 8
|
||||||
|
|
||||||
|
let minimumContentY = minimumHeaderY + headerFrame.height + padding
|
||||||
|
let maximumContentY = self.view.bounds.width * 0.75
|
||||||
|
|
||||||
|
// A full blur is too much, so we reduce the visible blur by 0.3, resulting in 70% blur.
|
||||||
|
let minimumBlurFraction = 0.3 as CGFloat
|
||||||
|
|
||||||
|
let difference = (maximumContentY - minimumContentY)
|
||||||
|
|
||||||
|
if self.scrollView.contentOffset.y > difference
|
||||||
|
{
|
||||||
|
// Full screen
|
||||||
|
|
||||||
|
headerFrame.origin.y = minimumHeaderY
|
||||||
|
contentFrame.origin.y = minimumContentY
|
||||||
|
backgroundIconFrame.origin.y = 0
|
||||||
|
|
||||||
|
self.contentViewController.tableView.contentOffset.y = self.scrollView.contentOffset.y - difference
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Partial screen
|
||||||
|
|
||||||
|
contentFrame.origin.y = maximumContentY - self.scrollView.contentOffset.y
|
||||||
|
headerFrame.origin.y = contentFrame.origin.y - padding - headerFrame.height
|
||||||
|
|
||||||
|
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
|
||||||
|
|
||||||
|
// Stretch the app icon image to fill additional vertical space if necessary.
|
||||||
|
let height = max(contentFrame.origin.y + cornerRadius * 2, backgroundIconFrame.height)
|
||||||
|
backgroundIconFrame.size.height = height
|
||||||
|
|
||||||
|
// Keep content table view's content offset at the top.
|
||||||
|
self.contentViewController.tableView.contentOffset.y = 0
|
||||||
|
|
||||||
|
if self.scrollView.contentOffset.y < 0
|
||||||
|
{
|
||||||
|
// Determine how much to lessen blur by.
|
||||||
|
|
||||||
|
let range = 75 as CGFloat
|
||||||
|
|
||||||
|
let fraction = min(-self.scrollView.contentOffset.y, range) / range
|
||||||
|
|
||||||
|
let fractionComplete = (fraction * (1.0 - minimumBlurFraction)) + minimumBlurFraction
|
||||||
|
self.blurAnimator?.fractionComplete = fractionComplete
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Set blur to default.
|
||||||
|
|
||||||
|
self.blurAnimator?.fractionComplete = minimumBlurFraction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep background app icon centered in gap between top of content and top of screen.
|
||||||
|
backgroundIconFrame.origin.y = (contentFrame.origin.y / 2) - backgroundIconFrame.height / 2
|
||||||
|
|
||||||
|
// Set frames.
|
||||||
|
self.contentViewController.view.superview?.frame = contentFrame
|
||||||
|
self.headerView.frame = headerFrame
|
||||||
|
self.backgroundAppIconImageView.frame = backgroundIconFrame
|
||||||
|
self.backgroundBlurView.frame = backgroundIconFrame
|
||||||
|
self.backButtonContainerView.frame = backButtonFrame
|
||||||
|
|
||||||
|
self.headerContentView.frame = CGRect(x: 0, y: 0, width: self.headerView.bounds.width, height: self.headerView.bounds.height)
|
||||||
|
self.contentViewControllerShadowView.frame = self.contentViewController.view.frame
|
||||||
|
|
||||||
|
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
|
||||||
|
|
||||||
|
self.scrollView.scrollIndicatorInsets.top = statusBarHeight
|
||||||
|
|
||||||
|
// Adjust content offset + size.
|
||||||
|
let contentOffset = self.scrollView.contentOffset
|
||||||
|
|
||||||
|
var contentSize = self.contentViewController.tableView.contentSize
|
||||||
|
contentSize.height += minimumContentY + self.view.safeAreaInsets.bottom + self.contentViewController.tableView.contentInset.bottom
|
||||||
|
|
||||||
|
self.scrollView.contentSize = contentSize
|
||||||
|
self.scrollView.contentOffset = contentOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit
|
||||||
|
{
|
||||||
|
self.blurAnimator?.stopAnimation(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension AppViewController
|
||||||
|
{
|
||||||
|
func update()
|
||||||
|
{
|
||||||
|
self.downloadButton.isIndicatingActivity = false
|
||||||
|
|
||||||
|
if self.app.installedApp == nil
|
||||||
|
{
|
||||||
|
self.downloadButton.setTitle(NSLocalizedString("FREE", comment: ""), for: .normal)
|
||||||
|
self.downloadButton.isInverted = false
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.downloadButton.setTitle(NSLocalizedString("OPEN", comment: ""), for: .normal)
|
||||||
|
self.downloadButton.isInverted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let progress = AppManager.shared.installationProgress(for: self.app)
|
||||||
|
self.downloadButton.progress = progress
|
||||||
|
}
|
||||||
|
|
||||||
|
func showNavigationBar(for navigationController: UINavigationController? = nil)
|
||||||
|
{
|
||||||
|
let navigationController = navigationController ?? self.navigationController
|
||||||
|
navigationController?.navigationBar.barStyle = .default
|
||||||
|
navigationController?.navigationBar.alpha = 1.0
|
||||||
|
navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hideNavigationBar(for navigationController: UINavigationController? = nil)
|
||||||
|
{
|
||||||
|
let navigationController = navigationController ?? self.navigationController
|
||||||
|
navigationController?.navigationBar.barStyle = .black
|
||||||
|
navigationController?.navigationBar.alpha = 0.0
|
||||||
|
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareBlur()
|
||||||
|
{
|
||||||
|
if let animator = self.blurAnimator
|
||||||
|
{
|
||||||
|
animator.stopAnimation(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backgroundBlurView.effect = self._backgroundBlurEffect
|
||||||
|
self.backgroundBlurView.contentView.backgroundColor = self._backgroundBlurTintColor
|
||||||
|
|
||||||
|
self.blurAnimator = UIViewPropertyAnimator(duration: 1.0, curve: .linear) {
|
||||||
|
self.backgroundBlurView.effect = nil
|
||||||
|
self.backgroundBlurView.contentView.backgroundColor = .clear
|
||||||
|
}
|
||||||
|
|
||||||
|
self.blurAnimator?.startAnimation()
|
||||||
|
self.blurAnimator?.pauseAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppViewController
|
||||||
|
{
|
||||||
|
@IBAction func popViewController(_ sender: UIButton)
|
||||||
|
{
|
||||||
|
self.navigationController?.popViewController(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func performAppAction(_ sender: PillButton)
|
||||||
|
{
|
||||||
|
if let installedApp = self.app.installedApp
|
||||||
|
{
|
||||||
|
self.open(installedApp)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.downloadApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadApp()
|
||||||
|
{
|
||||||
|
guard self.app.installedApp == nil else { return }
|
||||||
|
|
||||||
|
let progress = AppManager.shared.install(self.app, presentingViewController: self) { (result) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
_ = try result.get()
|
||||||
|
}
|
||||||
|
catch OperationError.cancelled
|
||||||
|
{
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let toastView = ToastView(text: error.localizedDescription, detailText: nil)
|
||||||
|
toastView.show(in: self.navigationController!.view, duration: 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.downloadButton.progress = nil
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.downloadButton.progress = progress
|
||||||
|
}
|
||||||
|
|
||||||
|
func open(_ installedApp: InstalledApp)
|
||||||
|
{
|
||||||
|
UIApplication.shared.open(installedApp.openAppURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension AppViewController
|
||||||
|
{
|
||||||
|
@objc func didChangeApp(_ notification: Notification)
|
||||||
|
{
|
||||||
|
self.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func willEnterForeground(_ notification: Notification)
|
||||||
|
{
|
||||||
|
guard let navigationController = self.navigationController, navigationController.topViewController == self else { return }
|
||||||
|
|
||||||
|
self._isEnteringForeground = true
|
||||||
|
self.view.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppViewController: UIScrollViewDelegate
|
||||||
|
{
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView)
|
||||||
|
{
|
||||||
|
self.view.setNeedsLayout()
|
||||||
|
self.view.layoutIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
25
AltStore/App Detail/PermissionPopoverViewController.swift
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// PermissionPopoverViewController.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/23/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class PermissionPopoverViewController: UIViewController
|
||||||
|
{
|
||||||
|
var permission: AppPermission!
|
||||||
|
|
||||||
|
@IBOutlet private var nameLabel: UILabel!
|
||||||
|
@IBOutlet private var descriptionLabel: UILabel!
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
self.nameLabel.text = self.permission.type.localizedName
|
||||||
|
self.descriptionLabel.text = self.permission.usageDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
{
|
{
|
||||||
self.isLaunching = true
|
self.isLaunching = true
|
||||||
|
|
||||||
|
self.setTintColor()
|
||||||
|
|
||||||
ServerManager.shared.startDiscovering()
|
ServerManager.shared.startDiscovering()
|
||||||
|
|
||||||
DatabaseManager.shared.start { (error) in
|
DatabaseManager.shared.start { (error) in
|
||||||
@@ -93,6 +95,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension AppDelegate
|
||||||
|
{
|
||||||
|
func setTintColor()
|
||||||
|
{
|
||||||
|
self.window?.tintColor = .altGreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension AppDelegate
|
extension AppDelegate
|
||||||
{
|
{
|
||||||
private func prepareForBackgroundFetch()
|
private func prepareForBackgroundFetch()
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
//
|
|
||||||
// AppDetailViewController.swift
|
|
||||||
// AltStore
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 5/9/19.
|
|
||||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Roxas
|
|
||||||
|
|
||||||
extension AppDetailViewController
|
|
||||||
{
|
|
||||||
private enum Row: Int
|
|
||||||
{
|
|
||||||
case general
|
|
||||||
case screenshots
|
|
||||||
case description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppDetailViewController: UITableViewController
|
|
||||||
{
|
|
||||||
var app: App!
|
|
||||||
|
|
||||||
private lazy var screenshotsDataSource = self.makeScreenshotsDataSource()
|
|
||||||
|
|
||||||
@IBOutlet private var nameLabel: UILabel!
|
|
||||||
@IBOutlet private var developerButton: UIButton!
|
|
||||||
@IBOutlet private var appIconImageView: UIImageView!
|
|
||||||
|
|
||||||
@IBOutlet private var downloadButton: UIButton!
|
|
||||||
|
|
||||||
@IBOutlet private var screenshotsCollectionView: UICollectionView!
|
|
||||||
|
|
||||||
@IBOutlet private var descriptionLabel: UILabel!
|
|
||||||
|
|
||||||
override func viewDidLoad()
|
|
||||||
{
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
self.tableView.delegate = self
|
|
||||||
self.screenshotsCollectionView.dataSource = self.screenshotsDataSource
|
|
||||||
|
|
||||||
self.downloadButton.activityIndicatorView.style = .white
|
|
||||||
|
|
||||||
self.update()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool)
|
|
||||||
{
|
|
||||||
super.viewWillAppear(animated)
|
|
||||||
|
|
||||||
self.update()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidLayoutSubviews()
|
|
||||||
{
|
|
||||||
super.viewDidLayoutSubviews()
|
|
||||||
|
|
||||||
guard let image = self.screenshotsDataSource.items.first else { return }
|
|
||||||
|
|
||||||
let aspectRatio = image.size.width / image.size.height
|
|
||||||
|
|
||||||
let height = self.screenshotsCollectionView.bounds.height
|
|
||||||
let width = self.screenshotsCollectionView.bounds.height * aspectRatio
|
|
||||||
|
|
||||||
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
|
|
||||||
layout.itemSize = CGSize(width: width, height: height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension AppDetailViewController
|
|
||||||
{
|
|
||||||
func update()
|
|
||||||
{
|
|
||||||
self.nameLabel.text = self.app.name
|
|
||||||
self.developerButton.setTitle(self.app.developerName, for: .normal)
|
|
||||||
self.appIconImageView.image = UIImage(named: self.app.iconName)
|
|
||||||
|
|
||||||
self.descriptionLabel.text = self.app.localizedDescription
|
|
||||||
|
|
||||||
if !self.downloadButton.isIndicatingActivity
|
|
||||||
{
|
|
||||||
if self.app.installedApp == nil
|
|
||||||
{
|
|
||||||
let text = String(format: NSLocalizedString("Download %@", comment: ""), self.app.name)
|
|
||||||
self.downloadButton.setTitle(text, for: .normal)
|
|
||||||
self.downloadButton.isEnabled = true
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.downloadButton.setTitle(NSLocalizedString("Installed", comment: ""), for: .normal)
|
|
||||||
self.downloadButton.isEnabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeScreenshotsDataSource() -> RSTArrayCollectionViewDataSource<UIImage>
|
|
||||||
{
|
|
||||||
let screenshots = self.app.screenshotNames.compactMap(UIImage.init(named:))
|
|
||||||
|
|
||||||
let dataSource = RSTArrayCollectionViewDataSource(items: screenshots)
|
|
||||||
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
|
|
||||||
let cell = cell as! ScreenshotCollectionViewCell
|
|
||||||
cell.imageView.image = screenshot
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataSource
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension AppDetailViewController
|
|
||||||
{
|
|
||||||
@IBAction func downloadApp(_ sender: UIButton)
|
|
||||||
{
|
|
||||||
guard self.app.installedApp == nil else { return }
|
|
||||||
|
|
||||||
sender.isIndicatingActivity = true
|
|
||||||
|
|
||||||
let progressView = UIProgressView(progressViewStyle: .bar)
|
|
||||||
progressView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
progressView.progress = 0.0
|
|
||||||
|
|
||||||
let progress = AppManager.shared.install(self.app, presentingViewController: self) { (result) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
_ = try result.get()
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let toastView = RSTToastView(text: "Installed \(self.app.name)!", detailText: nil)
|
|
||||||
toastView.tintColor = .altPurple
|
|
||||||
toastView.show(in: self.navigationController!.view, duration: 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch OperationError.cancelled
|
|
||||||
{
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let toastView = RSTToastView(text: "Failed to install \(self.app.name)", detailText: error.localizedDescription)
|
|
||||||
toastView.tintColor = .altPurple
|
|
||||||
toastView.show(in: self.navigationController!.view, duration: 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
UIView.animate(withDuration: 0.4, animations: {
|
|
||||||
progressView.alpha = 0.0
|
|
||||||
}) { _ in
|
|
||||||
progressView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
|
|
||||||
sender.isIndicatingActivity = false
|
|
||||||
self.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
progressView.observedProgress = progress
|
|
||||||
|
|
||||||
if let navigationBar = self.navigationController?.navigationBar
|
|
||||||
{
|
|
||||||
navigationBar.addSubview(progressView)
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([progressView.widthAnchor.constraint(equalTo: navigationBar.widthAnchor),
|
|
||||||
progressView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppDetailViewController
|
|
||||||
{
|
|
||||||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
|
|
||||||
{
|
|
||||||
guard indexPath.row == Row.screenshots.rawValue else { return super.tableView(tableView, heightForRowAt: indexPath) }
|
|
||||||
guard !self.screenshotsDataSource.items.isEmpty else { return 0.0 }
|
|
||||||
|
|
||||||
let height = self.view.bounds.height * 0.67
|
|
||||||
return height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
@@ -105,7 +106,7 @@
|
|||||||
<color key="textColor" red="1" green="0.14901960780000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="textColor" red="1" green="0.14901960780000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" contentInsetAdjustmentBehavior="never" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="wnA-ZZ-fwF">
|
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" contentInsetAdjustmentBehavior="never" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="wnA-ZZ-fwF">
|
||||||
<rect key="frame" x="20" y="47" width="305" height="185"/>
|
<rect key="frame" x="20" y="47" width="305" height="185"/>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
@@ -158,6 +159,10 @@
|
|||||||
<outlet property="screenshotsCollectionView" destination="wnA-ZZ-fwF" id="dcp-DC-GtH"/>
|
<outlet property="screenshotsCollectionView" destination="wnA-ZZ-fwF" id="dcp-DC-GtH"/>
|
||||||
<outlet property="screenshotsContentView" destination="P9V-7N-kV7" id="7mg-7o-ckH"/>
|
<outlet property="screenshotsContentView" destination="P9V-7N-kV7" id="7mg-7o-ckH"/>
|
||||||
<outlet property="subtitleLabel" destination="L3d-OX-f9e" id="ERX-nn-g4g"/>
|
<outlet property="subtitleLabel" destination="L3d-OX-f9e" id="ERX-nn-g4g"/>
|
||||||
|
<segue destination="0V6-N4-hTO" kind="show" identifier="showApp" id="SY2-HI-heA">
|
||||||
|
<segue key="commit" inheritsFrom="parent" id="dNL-ZX-8vh"/>
|
||||||
|
<segue key="preview" inheritsFrom="commit" id="gjN-bg-1G5"/>
|
||||||
|
</segue>
|
||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
@@ -172,177 +177,370 @@
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1517.5999999999999" y="-1013.3433283358322"/>
|
<point key="canvasLocation" x="1517.5999999999999" y="-1013.3433283358322"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--App Detail View Controller-->
|
<!--App View Controller-->
|
||||||
<scene sceneID="XfG-lM-QRu">
|
<scene sceneID="TgT-LO-3Er">
|
||||||
<objects>
|
<objects>
|
||||||
<tableViewController id="hR3-go-2DG" customClass="AppDetailViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController id="0V6-N4-hTO" customClass="AppViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="YzM-8a-RIS">
|
<view key="view" contentMode="scaleToFill" id="0cR-li-tCB">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="CUB-SN-zdM">
|
||||||
|
<rect key="frame" x="67" y="113" width="240" height="128"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
</imageView>
|
||||||
|
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8Tg-wk-r0u">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" insetsLayoutMarginsFromSafeArea="NO" id="oNk-OQ-r4M">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</view>
|
||||||
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<blurEffect style="light"/>
|
||||||
|
</visualEffectView>
|
||||||
|
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" contentInsetAdjustmentBehavior="never" translatesAutoresizingMaskIntoConstraints="NO" id="Ci9-Iw-aR2">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="618"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="0V6-N4-hTO" id="N2a-us-PgY"/>
|
||||||
|
</connections>
|
||||||
|
</scrollView>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Qlg-m3-lXg">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<subviews>
|
||||||
|
<visualEffectView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mgO-eN-SxQ">
|
||||||
|
<rect key="frame" x="38" y="287" width="300" height="93"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" insetsLayoutMarginsFromSafeArea="NO" id="yIo-bR-OBC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="300" height="93"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" alignment="center" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="LZw-eU-5SO" userLabel="App Info">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="270" height="93"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="3Ey-6S-HJx" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="14" y="14" width="65" height="65"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="65" id="AIz-49-Wuj"/>
|
||||||
|
<constraint firstAttribute="width" secondItem="3Ey-6S-HJx" secondAttribute="height" multiplier="1:1" id="GCk-a1-dDk"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="100" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="bR7-SO-m8f">
|
||||||
|
<rect key="frame" x="90" y="26.5" width="85" height="40.5"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="App Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dNE-IO-y3o">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="85" height="21.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="18"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="100" verticalHuggingPriority="251" text="Developer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NKT-el-rRF">
|
||||||
|
<rect key="frame" x="0.0" y="23.5" width="85" height="17"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
|
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mgB-Gs-bik" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="186" y="31" width="72" height="31"/>
|
||||||
|
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="72" id="j44-T1-0dc"/>
|
||||||
|
<constraint firstAttribute="height" constant="31" id="qY2-Ng-KJy"/>
|
||||||
|
</constraints>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
||||||
|
<state key="normal" title="FREE"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="performAppAction:" destination="0V6-N4-hTO" eventType="primaryActionTriggered" id="wPd-Kn-6fI"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<edgeInsets key="layoutMargins" top="14" left="14" bottom="14" right="12"/>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<blurEffect style="extraLight"/>
|
||||||
|
</visualEffectView>
|
||||||
|
<containerView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FIv-I9-5uW">
|
||||||
|
<rect key="frame" x="0.0" y="450" width="375" height="217"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
|
<connections>
|
||||||
|
<segue destination="kBq-V8-3XC" kind="embed" identifier="embedAppContentViewController" id="des-bZ-2AE"/>
|
||||||
|
</connections>
|
||||||
|
</containerView>
|
||||||
|
<visualEffectView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tUK-0J-07U">
|
||||||
|
<rect key="frame" x="58" y="117" width="58" height="32"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" insetsLayoutMarginsFromSafeArea="NO" id="yyn-wP-xk4">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="58" height="32"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mkD-3C-WMV">
|
||||||
|
<rect key="frame" x="3" y="7" width="52" height="18"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
|
||||||
|
<state key="normal" title="Back" image="Back"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="popViewController:" destination="0V6-N4-hTO" eventType="primaryActionTriggered" id="F6Z-xz-qCk"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<blurEffect style="extraLight"/>
|
||||||
|
</visualEffectView>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="Ci9-Iw-aR2" firstAttribute="top" secondItem="0cR-li-tCB" secondAttribute="top" id="015-fz-v3B"/>
|
||||||
|
<constraint firstAttribute="top" secondItem="Qlg-m3-lXg" secondAttribute="top" id="8tb-sY-MOu"/>
|
||||||
|
<constraint firstItem="Ci9-Iw-aR2" firstAttribute="bottom" secondItem="wiR-52-nwg" secondAttribute="bottom" id="HIl-QB-3dp"/>
|
||||||
|
<constraint firstItem="Ci9-Iw-aR2" firstAttribute="trailing" secondItem="wiR-52-nwg" secondAttribute="trailing" id="HzI-dj-SfC"/>
|
||||||
|
<constraint firstItem="Ci9-Iw-aR2" firstAttribute="leading" secondItem="wiR-52-nwg" secondAttribute="leading" id="LZb-jp-SMp"/>
|
||||||
|
<constraint firstItem="Qlg-m3-lXg" firstAttribute="leading" secondItem="0cR-li-tCB" secondAttribute="leading" id="TzU-Ob-YYA"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="Qlg-m3-lXg" secondAttribute="trailing" id="UrQ-oK-TKQ"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="Qlg-m3-lXg" secondAttribute="bottom" id="Vf7-Fg-88c"/>
|
||||||
|
</constraints>
|
||||||
|
<viewLayoutGuide key="safeArea" id="wiR-52-nwg"/>
|
||||||
|
</view>
|
||||||
|
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="maq-gT-QcS"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="appIconImageView" destination="3Ey-6S-HJx" id="5FB-mn-E29"/>
|
||||||
|
<outlet property="backButton" destination="mkD-3C-WMV" id="3m8-P7-yvT"/>
|
||||||
|
<outlet property="backButtonContainerView" destination="tUK-0J-07U" id="POZ-dP-f12"/>
|
||||||
|
<outlet property="backgroundAppIconImageView" destination="CUB-SN-zdM" id="dFx-py-yMm"/>
|
||||||
|
<outlet property="backgroundBlurView" destination="8Tg-wk-r0u" id="B8c-ng-nI5"/>
|
||||||
|
<outlet property="contentView" destination="Qlg-m3-lXg" id="JhH-hh-vBN"/>
|
||||||
|
<outlet property="developerLabel" destination="NKT-el-rRF" id="GUc-jy-kvv"/>
|
||||||
|
<outlet property="downloadButton" destination="mgB-Gs-bik" id="x95-gu-NBy"/>
|
||||||
|
<outlet property="headerContentView" destination="LZw-eU-5SO" id="hk1-xG-2kJ"/>
|
||||||
|
<outlet property="headerView" destination="mgO-eN-SxQ" id="iIi-D7-XRt"/>
|
||||||
|
<outlet property="nameLabel" destination="dNE-IO-y3o" id="tp1-IT-ByH"/>
|
||||||
|
<outlet property="scrollView" destination="Ci9-Iw-aR2" id="6XJ-gn-ByV"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="C9o-C3-sMK" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="2312.8000000000002" y="-1013.3433283358322"/>
|
||||||
|
</scene>
|
||||||
|
<!--App-->
|
||||||
|
<scene sceneID="CgX-7h-sRI">
|
||||||
|
<objects>
|
||||||
|
<tableViewController id="kBq-V8-3XC" customClass="AppContentViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="w5c-Q3-FcU">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<sections>
|
<sections>
|
||||||
<tableViewSection id="0FU-lm-58W">
|
<tableViewSection id="rfR-32-T0h">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="185" id="hHN-iH-vV1">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="57" id="xef-ko-Qp1">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="185"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="57"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="hHN-iH-vV1" id="iVa-h4-KoK">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xef-ko-Qp1" id="8PX-jQ-nHd">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="184.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="57"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="6L4-Bb-DUF">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Classic games in your pocket" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="BsL-O2-UjD">
|
||||||
<rect key="frame" x="16" y="11" width="343" height="163"/>
|
<rect key="frame" x="20" y="20" width="335" height="17.5"/>
|
||||||
<subviews>
|
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="top" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="uQy-Sy-Cgx">
|
<nil key="textColor"/>
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="129"/>
|
<nil key="highlightedColor"/>
|
||||||
<subviews>
|
</label>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="DeltaIcon" translatesAutoresizingMaskIntoConstraints="NO" id="PVH-lp-hGl" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="110" height="110"/>
|
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="110" id="2sr-xS-Nyd"/>
|
|
||||||
<constraint firstAttribute="width" constant="110" id="HwO-7w-7K9"/>
|
|
||||||
</constraints>
|
|
||||||
</imageView>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="F4R-0I-ucL">
|
|
||||||
<rect key="frame" x="125" y="0.0" width="218" height="46"/>
|
|
||||||
<subviews>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="1000" text="Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="O5s-oz-KYW">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="55" height="24"/>
|
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rMJ-KT-YRw">
|
|
||||||
<rect key="frame" x="0.0" y="28" width="70" height="18"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="18" id="2hT-PA-EjP"/>
|
|
||||||
</constraints>
|
|
||||||
<state key="normal" title="Developer"/>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i1B-Mu-s1h" customClass="Button" customModule="AltStore" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="129" width="343" height="34"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
|
|
||||||
<state key="normal" title="Download"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="downloadApp:" destination="hR3-go-2DG" eventType="primaryActionTriggered" id="TZ5-aD-2Bp"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="6L4-Bb-DUF" secondAttribute="trailing" id="99m-HR-lHd"/>
|
<constraint firstAttribute="bottom" secondItem="BsL-O2-UjD" secondAttribute="bottom" constant="19.5" id="3P3-f5-FYb"/>
|
||||||
<constraint firstItem="6L4-Bb-DUF" firstAttribute="leading" secondItem="iVa-h4-KoK" secondAttribute="leadingMargin" id="SGU-Gl-foG"/>
|
<constraint firstAttribute="trailing" secondItem="BsL-O2-UjD" secondAttribute="trailing" constant="20" id="DgP-ef-q7S"/>
|
||||||
<constraint firstItem="6L4-Bb-DUF" firstAttribute="top" secondItem="iVa-h4-KoK" secondAttribute="topMargin" id="edE-G4-bkR"/>
|
<constraint firstItem="BsL-O2-UjD" firstAttribute="leading" secondItem="8PX-jQ-nHd" secondAttribute="leading" constant="20" id="RBK-8Y-K72"/>
|
||||||
<constraint firstAttribute="bottomMargin" secondItem="6L4-Bb-DUF" secondAttribute="bottom" id="uo9-J8-mW7"/>
|
<constraint firstItem="BsL-O2-UjD" firstAttribute="top" secondItem="8PX-jQ-nHd" secondAttribute="top" constant="20" id="dRc-WY-Jbk"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</tableViewCellContentView>
|
</tableViewCellContentView>
|
||||||
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="300" id="eY1-pC-LnW">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="nI6-wC-H2d">
|
||||||
<rect key="frame" x="0.0" y="185" width="375" height="300"/>
|
<rect key="frame" x="0.0" y="57" width="375" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eY1-pC-LnW" id="fNc-tm-ceI">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nI6-wC-H2d" id="Z4y-vb-Z4Q">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="299.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="NVu-cR-q8f">
|
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="ppk-lL-at8">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="299.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="15" id="ace-Ns-Jd2">
|
||||||
|
<size key="itemSize" width="189" height="406"/>
|
||||||
|
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||||
|
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||||
|
<inset key="sectionInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
||||||
|
</collectionViewFlowLayout>
|
||||||
|
<cells>
|
||||||
|
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="2U6-d3-e4r" customClass="ScreenshotCollectionViewCell">
|
||||||
|
<rect key="frame" x="15" y="-181" width="189" height="406"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="189" height="406"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</view>
|
||||||
|
</collectionViewCell>
|
||||||
|
</cells>
|
||||||
|
</collectionView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="ppk-lL-at8" secondAttribute="trailing" id="3QR-Y2-v26"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="ppk-lL-at8" secondAttribute="bottom" id="EgJ-Uw-5ta"/>
|
||||||
|
<constraint firstItem="ppk-lL-at8" firstAttribute="leading" secondItem="Z4y-vb-Z4Q" secondAttribute="leading" id="wHf-S9-gMV"/>
|
||||||
|
<constraint firstItem="ppk-lL-at8" firstAttribute="top" secondItem="Z4y-vb-Z4Q" secondAttribute="top" id="xY5-w8-roA"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="EL5-UC-RIw" customClass="AppContentTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="101" width="375" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="EL5-UC-RIw" id="D1G-nK-G0Z">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Hello Me" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Pyt-8D-BZA" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="20" y="20" width="335" height="4"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
|
</textView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="Pyt-8D-BZA" firstAttribute="top" secondItem="D1G-nK-G0Z" secondAttribute="top" constant="20" id="Lm9-lx-kJ8"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="Pyt-8D-BZA" secondAttribute="bottom" constant="20" id="TSS-Au-gYx"/>
|
||||||
|
<constraint firstItem="Pyt-8D-BZA" firstAttribute="leading" secondItem="D1G-nK-G0Z" secondAttribute="leading" constant="20" id="UlS-ct-L9Y"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="Pyt-8D-BZA" secondAttribute="trailing" constant="20" id="Wq4-Ql-wvN"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="47M-El-a4G" customClass="AppContentTableViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="145" width="375" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="47M-El-a4G" id="f9D-OR-oGE">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="n9R-39-Glq">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Screenshots" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e2p-gM-mn5">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="What's New" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="obM-TM-y2E">
|
||||||
<rect key="frame" x="15" y="8" width="345" height="26.5"/>
|
<rect key="frame" x="20" y="0.0" width="335" height="13"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="22"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="UJY-8X-bkB">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="wQF-WY-Gdz" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="42.5" width="375" height="249"/>
|
<rect key="frame" x="20" y="24" width="335" height="0.0"/>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="15" id="9OB-OD-w1I">
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
<size key="itemSize" width="138" height="253"/>
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
|
</textView>
|
||||||
|
</subviews>
|
||||||
|
<edgeInsets key="layoutMargins" top="0.0" left="20" bottom="20" right="20"/>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="n9R-39-Glq" firstAttribute="leading" secondItem="f9D-OR-oGE" secondAttribute="leading" id="4LO-Yz-gXr"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="n9R-39-Glq" secondAttribute="trailing" id="9L9-Iw-UIF"/>
|
||||||
|
<constraint firstItem="n9R-39-Glq" firstAttribute="top" secondItem="f9D-OR-oGE" secondAttribute="top" id="E7L-lf-sCe"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="n9R-39-Glq" secondAttribute="bottom" priority="999" id="Zol-57-Lbq"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="149" id="nM7-vJ-W8b">
|
||||||
|
<rect key="frame" x="0.0" y="189" width="375" height="149"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nM7-vJ-W8b" id="cQ2-Jd-pRK">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="149"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="Jvb-r8-XrY">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="149"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Permissions" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dj7-G8-GFv">
|
||||||
|
<rect key="frame" x="20" y="0.0" width="335" height="26"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="r8T-dj-wQX">
|
||||||
|
<rect key="frame" x="0.0" y="41" width="375" height="88"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="88" id="6Lk-OO-MsA"/>
|
||||||
|
</constraints>
|
||||||
|
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="2HF-4d-3Im">
|
||||||
|
<size key="itemSize" width="60" height="88"/>
|
||||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||||
<inset key="sectionInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
<inset key="sectionInset" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
|
||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<cells>
|
<cells>
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="gbq-ih-dcI" customClass="ScreenshotCollectionViewCell">
|
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="WYy-bZ-h3T" customClass="PermissionCollectionViewCell" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="15" y="-2" width="138" height="253"/>
|
<rect key="frame" x="20" y="0.0" width="60" height="88"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="138" height="253"/>
|
<rect key="frame" x="0.0" y="0.0" width="60" height="88"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="OCS-uW-Big">
|
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" alignment="center" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="fSx-We-L4W">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="138" height="253"/>
|
<rect key="frame" x="0.0" y="0.0" width="56" height="56"/>
|
||||||
</imageView>
|
<subviews>
|
||||||
|
<button opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="79g-9q-mE2">
|
||||||
|
<rect key="frame" x="5" y="0.0" width="50" height="50"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="50" id="0LZ-4n-COH"/>
|
||||||
|
<constraint firstAttribute="height" constant="50" id="keD-mf-Rga"/>
|
||||||
|
</constraints>
|
||||||
|
</button>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pQi-FD-18P">
|
||||||
|
<rect key="frame" x="12.5" y="56" width="35.5" height="31.5"/>
|
||||||
|
<string key="text">Hello
|
||||||
|
World</string>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</view>
|
</view>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="OCS-uW-Big" firstAttribute="leading" secondItem="gbq-ih-dcI" secondAttribute="leading" id="88o-MG-3Eh"/>
|
<constraint firstAttribute="trailing" secondItem="fSx-We-L4W" secondAttribute="trailing" id="IyD-vD-tA4"/>
|
||||||
<constraint firstItem="OCS-uW-Big" firstAttribute="top" secondItem="gbq-ih-dcI" secondAttribute="top" id="IcM-V4-RJ9"/>
|
<constraint firstItem="fSx-We-L4W" firstAttribute="leading" secondItem="WYy-bZ-h3T" secondAttribute="leading" id="bTq-op-ivD"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="OCS-uW-Big" secondAttribute="bottom" id="KNE-HI-Cpo"/>
|
<constraint firstItem="fSx-We-L4W" firstAttribute="top" secondItem="WYy-bZ-h3T" secondAttribute="top" id="sMw-NS-jtY"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="OCS-uW-Big" secondAttribute="trailing" id="O73-Hi-RLf"/>
|
|
||||||
</constraints>
|
</constraints>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="imageView" destination="OCS-uW-Big" id="JYM-5w-apx"/>
|
<outlet property="button" destination="79g-9q-mE2" id="G5V-SS-vaA"/>
|
||||||
|
<outlet property="textLabel" destination="pQi-FD-18P" id="D5d-20-cm3"/>
|
||||||
|
<segue destination="Ojq-DN-xcF" kind="popoverPresentation" identifier="showPermission" popoverAnchorView="r8T-dj-wQX" id="ftM-H7-Q7G">
|
||||||
|
<popoverArrowDirection key="popoverArrowDirection" down="YES"/>
|
||||||
|
</segue>
|
||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
</collectionView>
|
</collectionView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="UJY-8X-bkB" firstAttribute="width" secondItem="NVu-cR-q8f" secondAttribute="width" id="6ud-t2-rUG"/>
|
<constraint firstItem="dj7-G8-GFv" firstAttribute="leading" secondItem="Jvb-r8-XrY" secondAttribute="leading" constant="20" id="9pB-Md-91A"/>
|
||||||
<constraint firstItem="e2p-gM-mn5" firstAttribute="leading" secondItem="NVu-cR-q8f" secondAttribute="leading" constant="15" id="7jL-kY-kwT"/>
|
<constraint firstItem="r8T-dj-wQX" firstAttribute="width" secondItem="Jvb-r8-XrY" secondAttribute="width" id="QJH-2y-DSh"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<edgeInsets key="layoutMargins" top="8" left="0.0" bottom="8" right="0.0"/>
|
<edgeInsets key="layoutMargins" top="0.0" left="0.0" bottom="20" right="0.0"/>
|
||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="NVu-cR-q8f" firstAttribute="leading" secondItem="fNc-tm-ceI" secondAttribute="leading" id="Kh3-a8-SAF"/>
|
<constraint firstAttribute="bottom" secondItem="Jvb-r8-XrY" secondAttribute="bottom" priority="999" id="Afs-2w-g9c"/>
|
||||||
<constraint firstItem="NVu-cR-q8f" firstAttribute="top" secondItem="fNc-tm-ceI" secondAttribute="top" id="OFv-zk-q24"/>
|
<constraint firstItem="Jvb-r8-XrY" firstAttribute="leading" secondItem="cQ2-Jd-pRK" secondAttribute="leading" id="C7d-H3-gR7"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="NVu-cR-q8f" secondAttribute="trailing" id="VEh-Lz-rhi"/>
|
<constraint firstAttribute="trailing" secondItem="Jvb-r8-XrY" secondAttribute="trailing" id="Jmi-6d-3Gz"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="NVu-cR-q8f" secondAttribute="bottom" id="beN-L9-6hC"/>
|
<constraint firstItem="Jvb-r8-XrY" firstAttribute="top" secondItem="cQ2-Jd-pRK" secondAttribute="top" id="Urh-Qr-vrS"/>
|
||||||
</constraints>
|
|
||||||
</tableViewCellContentView>
|
|
||||||
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
|
||||||
</tableViewCell>
|
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="Fng-Dg-Pak">
|
|
||||||
<rect key="frame" x="0.0" y="485" width="375" height="44"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Fng-Dg-Pak" id="Dgq-ek-1h0">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<subviews>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="O4U-vh-Xtu">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
|
||||||
<subviews>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Description" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1ri-OV-tIy">
|
|
||||||
<rect key="frame" x="15" y="8" width="116" height="19.5"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="22"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="g65-MO-uaH">
|
|
||||||
<rect key="frame" x="15" y="35.5" width="37.5" height="0.0"/>
|
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
|
||||||
<nil key="textColor"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
</subviews>
|
|
||||||
<edgeInsets key="layoutMargins" top="8" left="15" bottom="8" right="15"/>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="O4U-vh-Xtu" firstAttribute="top" secondItem="Dgq-ek-1h0" secondAttribute="top" id="5Se-DK-wSx"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="O4U-vh-Xtu" secondAttribute="trailing" id="6fA-iC-Xql"/>
|
|
||||||
<constraint firstAttribute="bottom" secondItem="O4U-vh-Xtu" secondAttribute="bottom" id="MM3-Bf-YYK"/>
|
|
||||||
<constraint firstItem="O4U-vh-Xtu" firstAttribute="leading" secondItem="Dgq-ek-1h0" secondAttribute="leading" id="cDm-2j-B88"/>
|
|
||||||
</constraints>
|
</constraints>
|
||||||
</tableViewCellContentView>
|
</tableViewCellContentView>
|
||||||
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
@@ -351,23 +549,69 @@
|
|||||||
</tableViewSection>
|
</tableViewSection>
|
||||||
</sections>
|
</sections>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="dataSource" destination="hR3-go-2DG" id="HfZ-io-Qfb"/>
|
<outlet property="dataSource" destination="kBq-V8-3XC" id="sRP-ci-fA4"/>
|
||||||
<outlet property="delegate" destination="hR3-go-2DG" id="Kce-sW-Xkm"/>
|
<outlet property="delegate" destination="kBq-V8-3XC" id="cZS-nq-F1v"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="M65-jg-bg9"/>
|
<navigationItem key="navigationItem" title="App" largeTitleDisplayMode="never" id="sWo-Y8-aF6"/>
|
||||||
|
<size key="freeformSize" width="375" height="667"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="appIconImageView" destination="PVH-lp-hGl" id="doE-lb-hMq"/>
|
<outlet property="descriptionTextView" destination="Pyt-8D-BZA" id="cgV-Hg-LrH"/>
|
||||||
<outlet property="descriptionLabel" destination="g65-MO-uaH" id="fod-v0-B4v"/>
|
<outlet property="permissionsCollectionView" destination="r8T-dj-wQX" id="Xud-5X-w2E"/>
|
||||||
<outlet property="developerButton" destination="rMJ-KT-YRw" id="7IH-1I-P8d"/>
|
<outlet property="screenshotsCollectionView" destination="ppk-lL-at8" id="YoQ-Z6-WTP"/>
|
||||||
<outlet property="downloadButton" destination="i1B-Mu-s1h" id="xbF-fk-xF8"/>
|
<outlet property="subtitleLabel" destination="BsL-O2-UjD" id="cfe-cf-4a9"/>
|
||||||
<outlet property="nameLabel" destination="O5s-oz-KYW" id="seg-JJ-VfB"/>
|
<outlet property="versionDescriptionTextView" destination="wQF-WY-Gdz" id="rdV-rY-VAz"/>
|
||||||
<outlet property="screenshotsCollectionView" destination="UJY-8X-bkB" id="6CI-Jg-yt6"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="BLa-Qn-j83" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dhh-ZN-LoG" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="2314" y="-279"/>
|
<point key="canvasLocation" x="3088.8000000000002" y="-1014.2428785607198"/>
|
||||||
|
</scene>
|
||||||
|
<!--Permission Popover View Controller-->
|
||||||
|
<scene sceneID="24j-EJ-G4e">
|
||||||
|
<objects>
|
||||||
|
<viewController id="Ojq-DN-xcF" customClass="PermissionPopoverViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="IgU-aM-YrX">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="217"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<stackView opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="xnC-tS-ZdV">
|
||||||
|
<rect key="frame" x="169" y="90" width="37.5" height="37"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4fh-lO-rAn">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="37.5" height="17"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="300" translatesAutoresizingMaskIntoConstraints="NO" id="ErG-8A-uqY">
|
||||||
|
<rect key="frame" x="0.0" y="21" width="37.5" height="16"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</stackView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="xnC-tS-ZdV" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="IgU-aM-YrX" secondAttribute="leadingMargin" id="LO8-Au-SYF"/>
|
||||||
|
<constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="xnC-tS-ZdV" secondAttribute="bottom" id="NZ9-iG-E10"/>
|
||||||
|
<constraint firstItem="xnC-tS-ZdV" firstAttribute="centerX" secondItem="IgU-aM-YrX" secondAttribute="centerX" id="QAB-qN-HdL"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" relation="greaterThanOrEqual" secondItem="xnC-tS-ZdV" secondAttribute="trailing" id="ZkD-tb-mBf"/>
|
||||||
|
<constraint firstItem="xnC-tS-ZdV" firstAttribute="top" relation="greaterThanOrEqual" secondItem="IgU-aM-YrX" secondAttribute="topMargin" id="oKq-9e-DtW"/>
|
||||||
|
<constraint firstItem="xnC-tS-ZdV" firstAttribute="centerY" secondItem="IgU-aM-YrX" secondAttribute="centerY" id="qCU-ye-fSf"/>
|
||||||
|
</constraints>
|
||||||
|
<edgeInsets key="layoutMargins" top="10" left="20" bottom="10" right="20"/>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="descriptionLabel" destination="ErG-8A-uqY" id="iuN-kE-IEm"/>
|
||||||
|
<outlet property="nameLabel" destination="4fh-lO-rAn" id="GWh-7k-yWw"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="7Tu-x9-xBb" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="3908" y="-1484"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Account-->
|
<!--Account-->
|
||||||
<scene sceneID="GaO-Ug-BdZ">
|
<scene sceneID="GaO-Ug-BdZ">
|
||||||
@@ -621,6 +865,10 @@
|
|||||||
<outlet property="developerLabel" destination="Hp4-uP-55T" id="Cqx-3O-knq"/>
|
<outlet property="developerLabel" destination="Hp4-uP-55T" id="Cqx-3O-knq"/>
|
||||||
<outlet property="nameLabel" destination="Nhl-6I-9gW" id="lzd-pp-PEQ"/>
|
<outlet property="nameLabel" destination="Nhl-6I-9gW" id="lzd-pp-PEQ"/>
|
||||||
<outlet property="refreshButton" destination="dh4-fU-DFx" id="KWX-9y-2w8"/>
|
<outlet property="refreshButton" destination="dh4-fU-DFx" id="KWX-9y-2w8"/>
|
||||||
|
<segue destination="0V6-N4-hTO" kind="show" identifier="showApp" id="cnd-KK-o60">
|
||||||
|
<segue key="commit" inheritsFrom="parent" id="YdR-Ct-SlK"/>
|
||||||
|
<segue key="preview" inheritsFrom="commit" id="GSg-SY-gai"/>
|
||||||
|
</segue>
|
||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
</cells>
|
</cells>
|
||||||
@@ -664,11 +912,14 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="DeltaIcon" width="512" height="512"/>
|
<image name="Back" width="18" height="18"/>
|
||||||
<image name="second" width="30" height="30"/>
|
<image name="second" width="30" height="30"/>
|
||||||
<namedColor name="Green">
|
<namedColor name="Green">
|
||||||
<color red="0.22352941176470589" green="0.49411764705882355" blue="0.396078431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.22352941176470589" green="0.49411764705882355" blue="0.396078431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
|
<inferredMetricsTieBreakers>
|
||||||
|
<segue reference="cnd-KK-o60"/>
|
||||||
|
</inferredMetricsTieBreakers>
|
||||||
<color key="tintColor" name="Green"/>
|
<color key="tintColor" name="Green"/>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -36,6 +36,18 @@ class BrowseViewController: UICollectionViewController
|
|||||||
let collectionViewLayout = self.collectionViewLayout as! UICollectionViewFlowLayout
|
let collectionViewLayout = self.collectionViewLayout as! UICollectionViewFlowLayout
|
||||||
collectionViewLayout.itemSize.width = self.view.bounds.width
|
collectionViewLayout.itemSize.width = self.view.bounds.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
|
{
|
||||||
|
guard segue.identifier == "showApp" else { return }
|
||||||
|
|
||||||
|
guard let cell = sender as? UICollectionViewCell, let indexPath = self.collectionView.indexPath(for: cell) else { return }
|
||||||
|
|
||||||
|
let app = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
|
let appViewController = segue.destination as! AppViewController
|
||||||
|
appViewController.app = app
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension BrowseViewController
|
private extension BrowseViewController
|
||||||
|
|||||||
@@ -18,11 +18,25 @@ class ScreenshotCollectionViewCell: UICollectionViewCell
|
|||||||
required init?(coder aDecoder: NSCoder)
|
required init?(coder aDecoder: NSCoder)
|
||||||
{
|
{
|
||||||
self.imageView = UIImageView(image: nil)
|
self.imageView = UIImageView(image: nil)
|
||||||
self.imageView.layer.cornerRadius = 8
|
|
||||||
self.imageView.layer.masksToBounds = true
|
self.imageView.layer.masksToBounds = true
|
||||||
|
|
||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
self.addSubview(self.imageView, pinningEdgesWith: .zero)
|
self.addSubview(self.imageView, pinningEdgesWith: .zero)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews()
|
||||||
|
{
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
if let image = self.imageView.image, (image.size.height / image.size.width) > ((16.0 / 9.0) + 0.1)
|
||||||
|
{
|
||||||
|
// Image aspect ratio is taller than 16:9, so assume it's an X-style screenshot and set corner radius.
|
||||||
|
self.imageView.layer.cornerRadius = max(self.imageView.bounds.width / 9.8, 8)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.imageView.layer.cornerRadius = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
114
AltStore/Components/CollapsingTextView.swift
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
//
|
||||||
|
// CollapsingTextView.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/23/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class CollapsingTextView: UITextView
|
||||||
|
{
|
||||||
|
var isCollapsed = true {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maximumNumberOfLines = 2 {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lineSpacing: CGFloat = 2 {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let moreButton = UIButton(type: .system)
|
||||||
|
|
||||||
|
override func awakeFromNib()
|
||||||
|
{
|
||||||
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
self.layoutManager.delegate = self
|
||||||
|
|
||||||
|
self.textContainerInset = .zero
|
||||||
|
self.textContainer.lineFragmentPadding = 0
|
||||||
|
self.textContainer.lineBreakMode = .byTruncatingTail
|
||||||
|
self.textContainer.heightTracksTextView = true
|
||||||
|
self.textContainer.widthTracksTextView = true
|
||||||
|
|
||||||
|
self.moreButton.setTitle(NSLocalizedString("More", comment: ""), for: .normal)
|
||||||
|
self.moreButton.addTarget(self, action: #selector(CollapsingTextView.toggleCollapsed(_:)), for: .primaryActionTriggered)
|
||||||
|
self.addSubview(self.moreButton)
|
||||||
|
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews()
|
||||||
|
{
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
guard let font = self.font else { return }
|
||||||
|
|
||||||
|
let buttonFont = UIFont.systemFont(ofSize: font.pointSize, weight: .medium)
|
||||||
|
self.moreButton.titleLabel?.font = buttonFont
|
||||||
|
|
||||||
|
let size = self.moreButton.sizeThatFits(CGSize(width: 1000, height: 1000))
|
||||||
|
|
||||||
|
let moreButtonFrame = CGRect(x: self.bounds.width - self.moreButton.bounds.width,
|
||||||
|
y: self.bounds.height - self.moreButton.bounds.height - self.lineSpacing,
|
||||||
|
width: size.width,
|
||||||
|
height: font.lineHeight)
|
||||||
|
self.moreButton.frame = moreButtonFrame
|
||||||
|
|
||||||
|
if self.isCollapsed
|
||||||
|
{
|
||||||
|
var exclusionFrame = moreButtonFrame
|
||||||
|
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
||||||
|
exclusionFrame.size.width = self.bounds.width // Extra wide to make sure it wraps to next line.
|
||||||
|
|
||||||
|
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
||||||
|
self.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionFrame)]
|
||||||
|
|
||||||
|
let maximumCollapsedHeight = font.lineHeight * CGFloat(self.maximumNumberOfLines)
|
||||||
|
if self.bounds.height > maximumCollapsedHeight
|
||||||
|
{
|
||||||
|
self.moreButton.isHidden = false
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.moreButton.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.textContainer.maximumNumberOfLines = 0
|
||||||
|
self.textContainer.exclusionPaths = []
|
||||||
|
|
||||||
|
self.moreButton.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
self.invalidateIntrinsicContentSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension CollapsingTextView
|
||||||
|
{
|
||||||
|
@objc func toggleCollapsed(_ sender: UIButton)
|
||||||
|
{
|
||||||
|
self.isCollapsed.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CollapsingTextView: NSLayoutManagerDelegate
|
||||||
|
{
|
||||||
|
func layoutManager(_ layoutManager: NSLayoutManager, lineSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat
|
||||||
|
{
|
||||||
|
return self.lineSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,17 @@ import Roxas
|
|||||||
|
|
||||||
class ToastView: RSTToastView
|
class ToastView: RSTToastView
|
||||||
{
|
{
|
||||||
|
override init(text: String, detailText detailedText: String?)
|
||||||
|
{
|
||||||
|
super.init(text: text, detailText: detailedText)
|
||||||
|
|
||||||
|
self.layoutMargins = UIEdgeInsets(top: 6, left: 12, bottom: 6, right: 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override func layoutSubviews()
|
override func layoutSubviews()
|
||||||
{
|
{
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|||||||
@@ -27,12 +27,18 @@
|
|||||||
<attribute name="versionDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
<attribute name="versionDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||||
<attribute name="versionDescription" optional="YES" attributeType="String" syncable="YES"/>
|
<attribute name="versionDescription" optional="YES" attributeType="String" syncable="YES"/>
|
||||||
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="app" inverseEntity="InstalledApp" syncable="YES"/>
|
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="app" inverseEntity="InstalledApp" syncable="YES"/>
|
||||||
|
<relationship name="permissions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission" syncable="YES"/>
|
||||||
<uniquenessConstraints>
|
<uniquenessConstraints>
|
||||||
<uniquenessConstraint>
|
<uniquenessConstraint>
|
||||||
<constraint value="identifier"/>
|
<constraint value="identifier"/>
|
||||||
</uniquenessConstraint>
|
</uniquenessConstraint>
|
||||||
</uniquenessConstraints>
|
</uniquenessConstraints>
|
||||||
</entity>
|
</entity>
|
||||||
|
<entity name="AppPermission" representedClassName="AppPermission" syncable="YES">
|
||||||
|
<attribute name="type" attributeType="String" syncable="YES"/>
|
||||||
|
<attribute name="usageDescription" attributeType="String" syncable="YES"/>
|
||||||
|
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="App" inverseName="permissions" inverseEntity="App" syncable="YES"/>
|
||||||
|
</entity>
|
||||||
<entity name="InstalledApp" representedClassName="InstalledApp" syncable="YES">
|
<entity name="InstalledApp" representedClassName="InstalledApp" syncable="YES">
|
||||||
<attribute name="bundleIdentifier" attributeType="String" syncable="YES"/>
|
<attribute name="bundleIdentifier" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||||
@@ -59,8 +65,9 @@
|
|||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Account" positionX="-36" positionY="90" width="128" height="135"/>
|
<element name="Account" positionX="-36" positionY="90" width="128" height="135"/>
|
||||||
<element name="App" positionX="-63" positionY="-18" width="128" height="240"/>
|
<element name="App" positionX="-63" positionY="-18" width="128" height="255"/>
|
||||||
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="120"/>
|
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="120"/>
|
||||||
<element name="Team" positionX="-45" positionY="81" width="128" height="120"/>
|
<element name="Team" positionX="-45" positionY="81" width="128" height="120"/>
|
||||||
|
<element name="AppPermission" positionX="-45" positionY="90" width="128" height="90"/>
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
||||||
@@ -39,6 +39,11 @@ class App: NSManagedObject, Decodable, Fetchable
|
|||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@NSManaged private(set) var installedApp: InstalledApp?
|
@NSManaged private(set) var installedApp: InstalledApp?
|
||||||
|
@objc(permissions) @NSManaged var _permissions: NSOrderedSet
|
||||||
|
|
||||||
|
@nonobjc var permissions: [AppPermission] {
|
||||||
|
return self._permissions.array as! [AppPermission]
|
||||||
|
}
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
@@ -59,6 +64,7 @@ class App: NSManagedObject, Decodable, Fetchable
|
|||||||
case downloadURL
|
case downloadURL
|
||||||
case tintColor
|
case tintColor
|
||||||
case subtitle
|
case subtitle
|
||||||
|
case permissions
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws
|
required init(from decoder: Decoder) throws
|
||||||
@@ -93,7 +99,12 @@ class App: NSManagedObject, Decodable, Fetchable
|
|||||||
self.tintColor = tintColor
|
self.tintColor = tintColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let permissions = try container.decodeIfPresent([AppPermission].self, forKey: .permissions) ?? []
|
||||||
|
|
||||||
context.insert(self)
|
context.insert(self)
|
||||||
|
|
||||||
|
// Must assign after we're inserted into context.
|
||||||
|
self._permissions = NSOrderedSet(array: permissions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
88
AltStore/Model/AppPermission.swift
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// AppPermission.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/23/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension ALTAppPermissionType
|
||||||
|
{
|
||||||
|
var localizedShortName: String? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .photos: return NSLocalizedString("Photos", comment: "")
|
||||||
|
case .backgroundAudio: return NSLocalizedString("Audio (BG)", comment: "")
|
||||||
|
case .backgroundFetch: return NSLocalizedString("Fetch (BG)", comment: "")
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var localizedName: String? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .photos: return NSLocalizedString("Photos", comment: "")
|
||||||
|
case .backgroundAudio: return NSLocalizedString("Background Audio", comment: "")
|
||||||
|
case .backgroundFetch: return NSLocalizedString("Background Fetch", comment: "")
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon: UIImage? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .photos: return UIImage(named: "PhotosPermission")
|
||||||
|
case .backgroundAudio: return UIImage(named: "BackgroundAudioPermission")
|
||||||
|
case .backgroundFetch: return UIImage(named: "BackgroundFetchPermission")
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc(AppPermission)
|
||||||
|
class AppPermission: NSManagedObject, Decodable, Fetchable
|
||||||
|
{
|
||||||
|
/* Properties */
|
||||||
|
@NSManaged var type: ALTAppPermissionType
|
||||||
|
@NSManaged var usageDescription: String
|
||||||
|
|
||||||
|
/* Relationships */
|
||||||
|
@NSManaged private(set) var app: App!
|
||||||
|
|
||||||
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
|
{
|
||||||
|
super.init(entity: entity, insertInto: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey
|
||||||
|
{
|
||||||
|
case type
|
||||||
|
case usageDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||||
|
|
||||||
|
super.init(entity: AppPermission.entity(), insertInto: nil)
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
self.usageDescription = try container.decode(String.self, forKey: .usageDescription)
|
||||||
|
|
||||||
|
let rawType = try container.decode(String.self, forKey: .type)
|
||||||
|
self.type = ALTAppPermissionType(rawValue: rawType)
|
||||||
|
|
||||||
|
context.insert(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppPermission
|
||||||
|
{
|
||||||
|
@nonobjc class func fetchRequest() -> NSFetchRequest<AppPermission>
|
||||||
|
{
|
||||||
|
return NSFetchRequest<AppPermission>(entityName: "AppPermission")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ public class DatabaseManager
|
|||||||
private init()
|
private init()
|
||||||
{
|
{
|
||||||
self.persistentContainer = RSTPersistentContainer(name: "AltStore")
|
self.persistentContainer = RSTPersistentContainer(name: "AltStore")
|
||||||
|
self.persistentContainer.preferredMergePolicy = MergePolicy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
39
AltStore/Model/MergePolicy.swift
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// MergePolicy.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/23/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
import Roxas
|
||||||
|
|
||||||
|
open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
||||||
|
{
|
||||||
|
open override func resolve(constraintConflicts conflicts: [NSConstraintConflict]) throws
|
||||||
|
{
|
||||||
|
guard conflicts.allSatisfy({ $0.databaseObject != nil }) else {
|
||||||
|
assertionFailure("MergePolicy is only intended to work with database-level conflicts.")
|
||||||
|
return try super.resolve(constraintConflicts: conflicts)
|
||||||
|
}
|
||||||
|
|
||||||
|
for conflict in conflicts
|
||||||
|
{
|
||||||
|
switch conflict.databaseObject
|
||||||
|
{
|
||||||
|
case let databaseObject as App:
|
||||||
|
// Delete previous permissions
|
||||||
|
for permission in databaseObject.permissions
|
||||||
|
{
|
||||||
|
permission.managedObjectContext?.delete(permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try super.resolve(constraintConflicts: conflicts)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,6 +79,18 @@ class MyAppsViewController: UICollectionViewController
|
|||||||
self.collectionView.register(UpdateCollectionViewCell.nib, forCellWithReuseIdentifier: "UpdateCell")
|
self.collectionView.register(UpdateCollectionViewCell.nib, forCellWithReuseIdentifier: "UpdateCell")
|
||||||
self.collectionView.register(UpdatesCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader")
|
self.collectionView.register(UpdatesCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
|
||||||
|
{
|
||||||
|
guard segue.identifier == "showApp" else { return }
|
||||||
|
|
||||||
|
guard let cell = sender as? UICollectionViewCell, let indexPath = self.collectionView.indexPath(for: cell) else { return }
|
||||||
|
|
||||||
|
let installedApp = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
|
let appViewController = segue.destination as! AppViewController
|
||||||
|
appViewController.app = installedApp.app
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension MyAppsViewController
|
private extension MyAppsViewController
|
||||||
@@ -122,7 +134,7 @@ private extension MyAppsViewController
|
|||||||
cell.mode = .collapsed
|
cell.mode = .collapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.moreButton.addTarget(self, action: #selector(MyAppsViewController.toggleUpdateCellMode(_:)), for: .primaryActionTriggered)
|
cell.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(MyAppsViewController.toggleUpdateCellMode(_:)), for: .primaryActionTriggered)
|
||||||
|
|
||||||
let progress = AppManager.shared.installationProgress(for: app)
|
let progress = AppManager.shared.installationProgress(for: app)
|
||||||
cell.updateButton.progress = progress
|
cell.updateButton.progress = progress
|
||||||
|
|||||||
@@ -30,10 +30,8 @@ extension UpdateCollectionViewCell
|
|||||||
@IBOutlet var dateLabel: UILabel!
|
@IBOutlet var dateLabel: UILabel!
|
||||||
@IBOutlet var updateButton: PillButton!
|
@IBOutlet var updateButton: PillButton!
|
||||||
@IBOutlet var versionDescriptionTitleLabel: UILabel!
|
@IBOutlet var versionDescriptionTitleLabel: UILabel!
|
||||||
@IBOutlet var versionDescriptionTextView: UITextView!
|
@IBOutlet var versionDescriptionTextView: CollapsingTextView!
|
||||||
|
|
||||||
@IBOutlet var moreButton: UIButton!
|
|
||||||
|
|
||||||
override func awakeFromNib()
|
override func awakeFromNib()
|
||||||
{
|
{
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
@@ -41,11 +39,6 @@ extension UpdateCollectionViewCell
|
|||||||
self.contentView.layer.cornerRadius = 20
|
self.contentView.layer.cornerRadius = 20
|
||||||
self.contentView.layer.masksToBounds = true
|
self.contentView.layer.masksToBounds = true
|
||||||
|
|
||||||
self.versionDescriptionTextView.textContainerInset = .zero
|
|
||||||
self.versionDescriptionTextView.textContainer.lineFragmentPadding = 0
|
|
||||||
self.versionDescriptionTextView.textContainer.lineBreakMode = .byTruncatingTail
|
|
||||||
self.versionDescriptionTextView.textContainer.heightTracksTextView = true
|
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,44 +49,6 @@ extension UpdateCollectionViewCell
|
|||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews()
|
|
||||||
{
|
|
||||||
super.layoutSubviews()
|
|
||||||
|
|
||||||
let textContainer = self.versionDescriptionTextView.textContainer
|
|
||||||
|
|
||||||
switch self.mode
|
|
||||||
{
|
|
||||||
case .collapsed:
|
|
||||||
// Extra wide to make sure it wraps to next line.
|
|
||||||
let frame = CGRect(x: textContainer.size.width - self.moreButton.bounds.width - 8,
|
|
||||||
y: textContainer.size.height - 4,
|
|
||||||
width: textContainer.size.width,
|
|
||||||
height: textContainer.size.height)
|
|
||||||
|
|
||||||
textContainer.maximumNumberOfLines = 2
|
|
||||||
textContainer.exclusionPaths = [UIBezierPath(rect: frame)]
|
|
||||||
|
|
||||||
if let font = self.versionDescriptionTextView.font, self.versionDescriptionTextView.bounds.height > font.lineHeight * 1.5
|
|
||||||
{
|
|
||||||
self.moreButton.isHidden = false
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// One (or less) lines, so hide more button.
|
|
||||||
self.moreButton.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
case .expanded:
|
|
||||||
textContainer.maximumNumberOfLines = 10
|
|
||||||
textContainer.exclusionPaths = []
|
|
||||||
|
|
||||||
self.moreButton.isHidden = true
|
|
||||||
}
|
|
||||||
|
|
||||||
self.versionDescriptionTextView.invalidateIntrinsicContentSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes)
|
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes)
|
||||||
{
|
{
|
||||||
// Animates transition to new attributes.
|
// Animates transition to new attributes.
|
||||||
@@ -108,6 +63,12 @@ private extension UpdateCollectionViewCell
|
|||||||
{
|
{
|
||||||
func update()
|
func update()
|
||||||
{
|
{
|
||||||
|
switch self.mode
|
||||||
|
{
|
||||||
|
case .collapsed: self.versionDescriptionTextView.isCollapsed = true
|
||||||
|
case .expanded: self.versionDescriptionTextView.isCollapsed = false
|
||||||
|
}
|
||||||
|
|
||||||
self.versionDescriptionTitleLabel.textColor = self.tintColor
|
self.versionDescriptionTitleLabel.textColor = self.tintColor
|
||||||
self.contentView.backgroundColor = self.tintColor.withAlphaComponent(0.1)
|
self.contentView.backgroundColor = self.tintColor.withAlphaComponent(0.1)
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Version Notes" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="rNs-2O-k3V">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Version Notes" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="rNs-2O-k3V" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="75" y="-10" width="265" height="24.5"/>
|
<rect key="frame" x="75" y="-10" width="265" height="24.5"/>
|
||||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||||
@@ -90,22 +90,12 @@
|
|||||||
<constraint firstItem="RSR-5W-7tt" firstAttribute="width" secondItem="57X-Ep-rfq" secondAttribute="width" id="d3x-mH-ODQ"/>
|
<constraint firstItem="RSR-5W-7tt" firstAttribute="width" secondItem="57X-Ep-rfq" secondAttribute="width" id="d3x-mH-ODQ"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QTy-uj-lKA">
|
|
||||||
<rect key="frame" x="323" y="99" width="32" height="14.5"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="14.5" id="pWr-Y1-ZW8"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="13"/>
|
|
||||||
<state key="normal" title="More"/>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="bottom" secondItem="57X-Ep-rfq" secondAttribute="bottom" constant="20" id="ArC-R2-jtc"/>
|
<constraint firstAttribute="bottom" secondItem="57X-Ep-rfq" secondAttribute="bottom" constant="20" id="ArC-R2-jtc"/>
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="QTy-uj-lKA" secondAttribute="trailing" id="Otb-0P-uKP"/>
|
|
||||||
<constraint firstItem="57X-Ep-rfq" firstAttribute="leading" secondItem="mdL-JE-wCe" secondAttribute="leading" constant="20" id="PvV-gg-7us"/>
|
<constraint firstItem="57X-Ep-rfq" firstAttribute="leading" secondItem="mdL-JE-wCe" secondAttribute="leading" constant="20" id="PvV-gg-7us"/>
|
||||||
<constraint firstItem="57X-Ep-rfq" firstAttribute="top" secondItem="dmf-hv-bwx" secondAttribute="top" constant="20" id="QHM-k8-g0x"/>
|
<constraint firstItem="57X-Ep-rfq" firstAttribute="top" secondItem="dmf-hv-bwx" secondAttribute="top" constant="20" id="QHM-k8-g0x"/>
|
||||||
<constraint firstItem="QTy-uj-lKA" firstAttribute="bottom" secondItem="rNs-2O-k3V" secondAttribute="bottom" id="mF3-ad-Fl5"/>
|
|
||||||
<constraint firstItem="mdL-JE-wCe" firstAttribute="trailing" secondItem="57X-Ep-rfq" secondAttribute="trailing" constant="15" id="sGL-bx-qIk"/>
|
<constraint firstItem="mdL-JE-wCe" firstAttribute="trailing" secondItem="57X-Ep-rfq" secondAttribute="trailing" constant="15" id="sGL-bx-qIk"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<edgeInsets key="layoutMargins" top="20" left="20" bottom="20" right="20"/>
|
<edgeInsets key="layoutMargins" top="20" left="20" bottom="20" right="20"/>
|
||||||
@@ -124,7 +114,6 @@
|
|||||||
<connections>
|
<connections>
|
||||||
<outlet property="appIconImageView" destination="jg6-wi-ngb" id="j83-Dl-GT6"/>
|
<outlet property="appIconImageView" destination="jg6-wi-ngb" id="j83-Dl-GT6"/>
|
||||||
<outlet property="dateLabel" destination="xaB-Kc-Par" id="mfG-3C-r7j"/>
|
<outlet property="dateLabel" destination="xaB-Kc-Par" id="mfG-3C-r7j"/>
|
||||||
<outlet property="moreButton" destination="QTy-uj-lKA" id="UME-5m-Dqe"/>
|
|
||||||
<outlet property="nameLabel" destination="qmI-m4-Mra" id="LQz-w7-HNb"/>
|
<outlet property="nameLabel" destination="qmI-m4-Mra" id="LQz-w7-HNb"/>
|
||||||
<outlet property="updateButton" destination="OSL-U2-BKa" id="WbI-96-Nel"/>
|
<outlet property="updateButton" destination="OSL-U2-BKa" id="WbI-96-Nel"/>
|
||||||
<outlet property="versionDescriptionTextView" destination="rNs-2O-k3V" id="4TC-A3-oxb"/>
|
<outlet property="versionDescriptionTextView" destination="rNs-2O-k3V" id="4TC-A3-oxb"/>
|
||||||
|
|||||||
@@ -3,25 +3,41 @@
|
|||||||
"name": "AltStore",
|
"name": "AltStore",
|
||||||
"identifier": "com.rileytestut.AltStore",
|
"identifier": "com.rileytestut.AltStore",
|
||||||
"developerName": "Riley Testut",
|
"developerName": "Riley Testut",
|
||||||
"version": "1.0",
|
"version": "0.8",
|
||||||
"versionDate": "2019-05-20",
|
"versionDate": "2019-07-16",
|
||||||
"versionDescription": "AltStore has been updated with bug fixes and improvements.",
|
"versionDescription": "AltStore has been updated with bug fixes and improvements and other nice goodies for you to enjoy.",
|
||||||
"downloadURL": "https://www.dropbox.com/s/w1gn9iztlqvltyp/AltStore.ipa?dl=1",
|
"downloadURL": "https://www.dropbox.com/s/w1gn9iztlqvltyp/AltStore.ipa?dl=1",
|
||||||
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
||||||
"iconName": "DeltaIcon"
|
"iconName": "AppIcon",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"type": "background-fetch",
|
||||||
|
"usageDescription": "AltStore periodically refreshes apps in the background to prevent them from expiring."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "background-audio",
|
||||||
|
"usageDescription": "Allows AltStore to run longer than 30 seconds when refreshing apps in background."
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Delta",
|
"name": "Delta",
|
||||||
"identifier": "com.rileytestut.Delta",
|
"identifier": "com.rileytestut.Delta",
|
||||||
"developerName": "Riley Testut",
|
"developerName": "Riley Testut",
|
||||||
"subtitle": "Classic Nintendo games in your pocket.",
|
"subtitle": "Classic games in your pocket.",
|
||||||
"version": "1.0",
|
"version": "0.8",
|
||||||
"versionDate": "2019-05-20",
|
"versionDate": "2019-07-11",
|
||||||
"versionDescription": "Finally, after over five years of waiting, Delta is out of beta and ready for everyone to enjoy!\n\nCurrently supports NES, SNES, N64, GB(C), and GBA games, with more to come in the future.",
|
"versionDescription": "Finally, after over five years of waiting, Delta is out of beta and ready for everyone to enjoy!\n\nCurrently supports NES, SNES, N64, GB(C), and GBA games, with more to come in the future.",
|
||||||
"downloadURL": "https://www.dropbox.com/s/31i4hcqnorucrxi/Delta.ipa?dl=1",
|
"downloadURL": "https://www.dropbox.com/s/31i4hcqnorucrxi/Delta.ipa?dl=1",
|
||||||
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
||||||
"iconName": "DeltaIcon",
|
"iconName": "DeltaIcon",
|
||||||
"tintColor": "8A28F7",
|
"tintColor": "8A28F7",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"type": "photos",
|
||||||
|
"usageDescription": "Allows Delta to use images from your Photo Library as game artwork."
|
||||||
|
}
|
||||||
|
],
|
||||||
"screenshotNames": [
|
"screenshotNames": [
|
||||||
"Delta1",
|
"Delta1",
|
||||||
"Delta2",
|
"Delta2",
|
||||||
@@ -34,10 +50,17 @@
|
|||||||
"identifier": "com.rileytestut.ClipboardManager",
|
"identifier": "com.rileytestut.ClipboardManager",
|
||||||
"subtitle": "Manage your clipboard history with ease.",
|
"subtitle": "Manage your clipboard history with ease.",
|
||||||
"developerName": "Riley Testut",
|
"developerName": "Riley Testut",
|
||||||
"version": "1.0",
|
"version": "0.8",
|
||||||
"versionDate": "2019-06-20",
|
"versionDate": "2019-06-20",
|
||||||
|
"versionDescription": "Bug fixes and improvements.",
|
||||||
"downloadURL": "https://www.dropbox.com/s/rqopivl22iz4ldw/Clipboard.ipa?dl=1",
|
"downloadURL": "https://www.dropbox.com/s/rqopivl22iz4ldw/Clipboard.ipa?dl=1",
|
||||||
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
"localizedDescription": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed enim ut sem viverra aliquet eget sit amet. Viverra mauris in aliquam sem fringilla ut. Egestas erat imperdiet sed euismod nisi porta. Sit amet dictum sit amet justo donec enim diam vulputate. Phasellus vestibulum lorem sed risus ultricies tristique nulla. Elit pellentesque habitant morbi tristique. Ut lectus arcu bibendum at. Ullamcorper a lacus vestibulum sed. Mi tempus imperdiet nulla malesuada pellentesque elit eget gravida. Nec feugiat nisl pretium fusce id velit ut. Amet nulla facilisi morbi tempus. Ut sem nulla pharetra diam sit amet nisl.\n\nTortor at auctor urna nunc id cursus metus. Commodo ullamcorper a lacus vestibulum sed arcu non odio. Faucibus turpis in eu mi bibendum neque egestas. Auctor augue mauris augue neque gravida in fermentum et sollicitudin. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Placerat in egestas erat imperdiet sed euismod nisi. Aliquam id diam maecenas ultricies mi eget mauris. Nunc faucibus a pellentesque sit amet porttitor eget dolor. Sed faucibus turpis in eu mi. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus sed. Id semper risus in hendrerit gravida rutrum quisque. At lectus urna duis convallis convallis. Egestas maecenas pharetra convallis posuere. Id velit ut tortor pretium viverra. Quam pellentesque nec nam aliquam sem et tortor consequat. Risus pretium quam vulputate dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar.\n\nTempor orci eu lobortis elementum nibh tellus. Mattis rhoncus urna neque viverra justo nec. Maecenas pharetra convallis posuere morbi leo. Rhoncus mattis rhoncus urna neque viverra justo nec. Gravida dictum fusce ut placerat orci nulla pellentesque dignissim enim. At imperdiet dui accumsan sit amet. Elit sed vulputate mi sit amet mauris commodo. Pellentesque habitant morbi tristique senectus. Tortor id aliquet lectus proin nibh. Magna etiam tempor orci eu lobortis elementum. Est pellentesque elit ullamcorper dignissim. Dapibus ultrices in iaculis nunc. Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Diam vel quam elementum pulvinar. Vel turpis nunc eget lorem dolor sed viverra. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae. Euismod nisi porta lorem mollis. Massa id neque aliquam vestibulum morbi blandit.\n\nMassa massa ultricies mi quis hendrerit dolor magna eget est. Augue interdum velit euismod in pellentesque massa. Sed risus ultricies tristique nulla aliquet enim. Risus viverra adipiscing at in tellus. Donec adipiscing tristique risus nec feugiat. Eget sit amet tellus cras adipiscing enim eu turpis. Auctor neque vitae tempus quam pellentesque nec. Sit amet tellus cras adipiscing enim eu turpis egestas. Dui faucibus in ornare quam viverra. Fermentum iaculis eu non diam phasellus vestibulum lorem. Odio ut enim blandit volutpat maecenas. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Pellentesque diam volutpat commodo sed egestas egestas. Aliquam purus sit amet luctus venenatis lectus magna fringilla. Viverra mauris in aliquam sem fringilla ut morbi tincidunt. Elit duis tristique sollicitudin nibh sit. Fermentum dui faucibus in ornare quam viverra orci sagittis. Aliquet eget sit amet tellus cras adipiscing.",
|
||||||
"iconName": "ClipboardIcon"
|
"iconName": "ClipboardIcon",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"type": "background-audio",
|
||||||
|
"usageDescription": "Allows Clipboard Manager to continuously monitor your clipboard in the background."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
BIN
AltStore/Resources/Assets.xcassets/Back.imageset/Back@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 940 B |
24
AltStore/Resources/Assets.xcassets/Back.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "Back@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "sound@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "sound@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/sound@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/sound@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
22
AltStore/Resources/Assets.xcassets/Permissions/BackgroundFetchPermission.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "fetch@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "fetch@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AltStore/Resources/Assets.xcassets/Permissions/BackgroundFetchPermission.imageset/fetch@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
AltStore/Resources/Assets.xcassets/Permissions/BackgroundFetchPermission.imageset/fetch@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "photos@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "photos@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/photos@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/photos@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
14
AltStore/Types/ALTAppPermission.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// ALTAppPermission.h
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/23/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
typedef NSString *ALTAppPermissionType NS_TYPED_EXTENSIBLE_ENUM;
|
||||||
|
extern ALTAppPermissionType const ALTAppPermissionTypePhotos;
|
||||||
|
extern ALTAppPermissionType const ALTAppPermissionTypeBackgroundAudio;
|
||||||
|
extern ALTAppPermissionType const ALTAppPermissionTypeBackgroundFetch;
|
||||||
13
AltStore/Types/ALTAppPermission.m
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// ALTAppPermission.m
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 7/23/19.
|
||||||
|
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ALTAppPermission.h"
|
||||||
|
|
||||||
|
ALTAppPermissionType const ALTAppPermissionTypePhotos = @"photos";
|
||||||
|
ALTAppPermissionType const ALTAppPermissionTypeBackgroundAudio = @"background-audio";
|
||||||
|
ALTAppPermissionType const ALTAppPermissionTypeBackgroundFetch = @"background-fetch";
|
||||||