From fc44dfb19c2de2cc043a1e42b6d244b4bd9b764a Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Wed, 24 Jul 2019 12:23:54 -0700 Subject: [PATCH] [AltStore] Adds redesigned AppViewController to view/download AltStore apps --- AltStore.xcodeproj/project.pbxproj | 62 ++- AltStore/AltStore-Bridging-Header.h | 1 + .../App Detail/AppContentViewController.swift | 178 ++++++ .../AppContentViewControllerCells.swift | 43 ++ AltStore/App Detail/AppViewController.swift | 406 ++++++++++++++ .../PermissionPopoverViewController.swift | 25 + AltStore/AppDelegate.swift | 10 + AltStore/Apps/AppDetailViewController.swift | 183 ------ AltStore/Base.lproj/Main.storyboard | 527 +++++++++++++----- AltStore/Browse/BrowseViewController.swift | 12 + .../Browse/ScreenshotCollectionViewCell.swift | 16 +- AltStore/Components/CollapsingTextView.swift | 114 ++++ AltStore/Components/ToastView.swift | 11 + .../{Apps => Managing Apps}/AppManager.swift | 0 .../AltStore.xcdatamodel/contents | 9 +- AltStore/Model/App.swift | 11 + AltStore/Model/AppPermission.swift | 88 +++ AltStore/Model/DatabaseManager.swift | 1 + AltStore/Model/MergePolicy.swift | 39 ++ AltStore/My Apps/MyAppsViewController.swift | 14 +- .../My Apps/UpdateCollectionViewCell.swift | 55 +- AltStore/My Apps/UpdateCollectionViewCell.xib | 13 +- AltStore/Resources/Apps-Dev.json | 41 +- .../Assets.xcassets/Back.imageset/Back@2x.png | Bin 0 -> 940 bytes .../Back.imageset/Contents.json | 24 + .../Contents.json | 22 + .../sound@2x.png | Bin 0 -> 1896 bytes .../sound@3x.png | Bin 0 -> 2991 bytes .../Contents.json | 22 + .../fetch@2x.png | Bin 0 -> 2443 bytes .../fetch@3x.png | Bin 0 -> 3974 bytes .../Assets.xcassets/Permissions/Contents.json | 6 + .../PhotosPermission.imageset/Contents.json | 22 + .../PhotosPermission.imageset/photos@2x.png | Bin 0 -> 6167 bytes .../PhotosPermission.imageset/photos@3x.png | Bin 0 -> 10703 bytes AltStore/Types/ALTAppPermission.h | 14 + AltStore/Types/ALTAppPermission.m | 13 + 37 files changed, 1583 insertions(+), 399 deletions(-) create mode 100644 AltStore/App Detail/AppContentViewController.swift create mode 100644 AltStore/App Detail/AppContentViewControllerCells.swift create mode 100644 AltStore/App Detail/AppViewController.swift create mode 100644 AltStore/App Detail/PermissionPopoverViewController.swift delete mode 100644 AltStore/Apps/AppDetailViewController.swift create mode 100644 AltStore/Components/CollapsingTextView.swift rename AltStore/{Apps => Managing Apps}/AppManager.swift (100%) create mode 100644 AltStore/Model/AppPermission.swift create mode 100644 AltStore/Model/MergePolicy.swift create mode 100644 AltStore/Resources/Assets.xcassets/Back.imageset/Back@2x.png create mode 100644 AltStore/Resources/Assets.xcassets/Back.imageset/Contents.json create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/Contents.json create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/sound@2x.png create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/sound@3x.png create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/BackgroundFetchPermission.imageset/Contents.json create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/BackgroundFetchPermission.imageset/fetch@2x.png create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/BackgroundFetchPermission.imageset/fetch@3x.png create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/Contents.json create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/Contents.json create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/photos@2x.png create mode 100644 AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/photos@3x.png create mode 100644 AltStore/Types/ALTAppPermission.h create mode 100644 AltStore/Types/ALTAppPermission.m diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 2e9eceb5..98309fc4 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -22,6 +22,12 @@ BF1E315A22A0620000370A3C /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; }; BF1E315F22A0635900370A3C /* 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 */; }; BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002D22A714AF0051E2BC /* Keychain.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 */; }; BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */ = {isa = PBXBuildFile; fileRef = BF770E6822BD57DD002A40FE /* Silence.m4a */; }; 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 */; }; BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.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, ); }; }; BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478B2284C4C300981D42 /* AppIconImageView.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 */; }; BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BD322A0800A000B7ED1 /* ServerManager.swift */; }; 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 = ""; }; 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 = ""; }; + BF3D648722E79A3700E9056B /* AppPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermission.swift; sourceTree = ""; }; + BF3D648B22E79AC800E9056B /* ALTAppPermission.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTAppPermission.h; sourceTree = ""; }; + BF3D648C22E79AC800E9056B /* ALTAppPermission.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTAppPermission.m; sourceTree = ""; }; + BF3D649C22E7AC1B00E9056B /* PermissionPopoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionPopoverViewController.swift; sourceTree = ""; }; + BF3D649E22E7B24C00E9056B /* CollapsingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsingTextView.swift; sourceTree = ""; }; + BF3D64A122E8031100E9056B /* MergePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergePolicy.swift; sourceTree = ""; }; + BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewControllerCells.swift; sourceTree = ""; }; BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTDeviceManager+Installation.swift"; sourceTree = ""; }; BF43002D22A714AF0051E2BC /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AltStore.swift"; sourceTree = ""; }; @@ -338,6 +352,8 @@ BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = ""; }; BF770E6822BD57DD002A40FE /* Silence.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Silence.m4a; sourceTree = ""; }; BF7B9EF222B82B1F0042C873 /* FetchAppsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAppsOperation.swift; sourceTree = ""; }; + BF8F69C122E659F700049BA1 /* AppContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewController.swift; sourceTree = ""; }; + BF8F69C322E662D300049BA1 /* AppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewController.swift; sourceTree = ""; }; BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseViewController.swift; sourceTree = ""; }; BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowseCollectionViewCell.swift; sourceTree = ""; }; BF9ABA4822DD0742008935CF /* ScreenshotCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotCollectionViewCell.swift; sourceTree = ""; }; @@ -363,7 +379,6 @@ 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 = ""; }; BFD2478E2284C8F900981D42 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; - BFD2479B2284E19A00981D42 /* AppDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDetailViewController.swift; sourceTree = ""; }; BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = ""; }; BFD52BD222A06EFB000B7ED1 /* AltKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AltKit.h; sourceTree = ""; }; BFD52BD322A0800A000B7ED1 /* ServerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerManager.swift; sourceTree = ""; }; @@ -479,6 +494,26 @@ path = AltKit; sourceTree = ""; }; + BF3D648922E79A7700E9056B /* Types */ = { + isa = PBXGroup; + children = ( + BF3D648B22E79AC800E9056B /* ALTAppPermission.h */, + BF3D648C22E79AC800E9056B /* ALTAppPermission.m */, + ); + path = Types; + sourceTree = ""; + }; + BF3D64A022E7FAD800E9056B /* App Detail */ = { + isa = PBXGroup; + children = ( + BF8F69C322E662D300049BA1 /* AppViewController.swift */, + BF8F69C122E659F700049BA1 /* AppContentViewController.swift */, + BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */, + BF3D649C22E7AC1B00E9056B /* PermissionPopoverViewController.swift */, + ); + path = "App Detail"; + sourceTree = ""; + }; BF45868E229872EA00BD7491 /* AltServer */ = { isa = PBXGroup; children = ( @@ -721,13 +756,15 @@ BFD247732284B9A500981D42 /* Main.storyboard */, BFE6325822A83BA800F30809 /* Authentication */, BF9ABA4322DCFF33008935CF /* Browse */, - BFD2478A2284C49000981D42 /* Apps */, + BF3D64A022E7FAD800E9056B /* App Detail */, BFBBE2E2229320A2002097FA /* My Apps */, BFDB69FB22A9A7A6007EA6D6 /* Account */, + BFD2478A2284C49000981D42 /* Managing Apps */, BFC51D7922972F1F00388324 /* Server */, BFD247982284D7FC00981D42 /* Model */, BFDB6A0922AAEDA1007EA6D6 /* Operations */, BFD2478D2284C4C700981D42 /* Components */, + BF3D648922E79A7700E9056B /* Types */, BFDB6A0622A9B114007EA6D6 /* Protocols */, BFD2479D2284FBBD00981D42 /* Extensions */, BFD247962284D7C100981D42 /* Resources */, @@ -751,13 +788,12 @@ name = Frameworks; sourceTree = ""; }; - BFD2478A2284C49000981D42 /* Apps */ = { + BFD2478A2284C49000981D42 /* Managing Apps */ = { isa = PBXGroup; children = ( - BFD2479B2284E19A00981D42 /* AppDetailViewController.swift */, BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */, ); - path = Apps; + path = "Managing Apps"; sourceTree = ""; }; BFD2478D2284C4C700981D42 /* Components */ = { @@ -770,6 +806,7 @@ BF9ABA4A22DD137F008935CF /* NavigationBar.swift */, BF9ABA4C22DD16DE008935CF /* PillButton.swift */, BF18B0F022E25DF9005C4CF5 /* ToastView.swift */, + BF3D649E22E7B24C00E9056B /* CollapsingTextView.swift */, ); path = Components; sourceTree = ""; @@ -799,8 +836,10 @@ children = ( BFBBE2DB22931B20002097FA /* AltStore.xcdatamodeld */, BFB11691229322E400BB457C /* DatabaseManager.swift */, + BF3D64A122E8031100E9056B /* MergePolicy.swift */, BFE6326722A858F300F30809 /* Account.swift */, BFBBE2DE22931F73002097FA /* App.swift */, + BF3D648722E79A3700E9056B /* AppPermission.swift */, BFBBE2E022931F81002097FA /* InstalledApp.swift */, BFE6326522A857C100F30809 /* Team.swift */, ); @@ -1223,6 +1262,7 @@ BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */, BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */, BF7B9EF322B82B1F0042C873 /* FetchAppsOperation.swift in Sources */, + BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */, BFE6325E22A8497000F30809 /* SelectTeamViewController.swift in Sources */, BFD2478F2284C8F900981D42 /* Button.swift in Sources */, BFDB69FD22A9A7B7007EA6D6 /* AccountViewController.swift in Sources */, @@ -1235,27 +1275,33 @@ BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */, BF770E5422BC044E002A40FE /* AppOperationContext.swift in Sources */, BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */, + BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */, BFE6326822A858F300F30809 /* Account.swift in Sources */, BFE6326622A857C200F30809 /* Team.swift in Sources */, - BFD2479C2284E19A00981D42 /* AppDetailViewController.swift in Sources */, BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */, BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */, BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */, + BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */, BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */, BFE6325C22A83C0100F30809 /* AuthenticationViewController.swift in Sources */, BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */, BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */, + BF3D648822E79A3700E9056B /* AppPermission.swift in Sources */, BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */, + BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */, BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */, BF770E5822BC3D0F002A40FE /* OperationGroup.swift in Sources */, BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */, + BF3D649F22E7B24C00E9056B /* CollapsingTextView.swift in Sources */, BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */, BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */, BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */, BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */, + BF3D64A222E8031100E9056B /* MergePolicy.swift in Sources */, BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */, BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */, BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */, + BF3D648D22E79AC800E9056B /* ALTAppPermission.m in Sources */, BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */, BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */, BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */, @@ -1646,6 +1692,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltStore; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; @@ -1671,6 +1718,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltStore; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; diff --git a/AltStore/AltStore-Bridging-Header.h b/AltStore/AltStore-Bridging-Header.h index 83dbb109..52a2d835 100644 --- a/AltStore/AltStore-Bridging-Header.h +++ b/AltStore/AltStore-Bridging-Header.h @@ -3,3 +3,4 @@ // #import "NSError+ALTServerError.h" +#import "ALTAppPermission.h" diff --git a/AltStore/App Detail/AppContentViewController.swift b/AltStore/App Detail/AppContentViewController.swift new file mode 100644 index 00000000..fee28b12 --- /dev/null +++ b/AltStore/App Detail/AppContentViewController.swift @@ -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 + { + 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 + { + 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 + } +} diff --git a/AltStore/App Detail/AppContentViewControllerCells.swift b/AltStore/App Detail/AppContentViewControllerCells.swift new file mode 100644 index 00000000..923b5d6c --- /dev/null +++ b/AltStore/App Detail/AppContentViewControllerCells.swift @@ -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 + } +} diff --git a/AltStore/App Detail/AppViewController.swift b/AltStore/App Detail/AppViewController.swift new file mode 100644 index 00000000..6f8467e1 --- /dev/null +++ b/AltStore/App Detail/AppViewController.swift @@ -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() + } +} diff --git a/AltStore/App Detail/PermissionPopoverViewController.swift b/AltStore/App Detail/PermissionPopoverViewController.swift new file mode 100644 index 00000000..b34e2c68 --- /dev/null +++ b/AltStore/App Detail/PermissionPopoverViewController.swift @@ -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 + } +} diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index 81928f17..22b42489 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -49,6 +49,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { { self.isLaunching = true + self.setTintColor() + ServerManager.shared.startDiscovering() DatabaseManager.shared.start { (error) in @@ -93,6 +95,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } +private extension AppDelegate +{ + func setTintColor() + { + self.window?.tintColor = .altGreen + } +} + extension AppDelegate { private func prepareForBackgroundFetch() diff --git a/AltStore/Apps/AppDetailViewController.swift b/AltStore/Apps/AppDetailViewController.swift deleted file mode 100644 index 6d423240..00000000 --- a/AltStore/Apps/AppDetailViewController.swift +++ /dev/null @@ -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 - { - 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 - } -} diff --git a/AltStore/Base.lproj/Main.storyboard b/AltStore/Base.lproj/Main.storyboard index 7536f716..d26f1c95 100644 --- a/AltStore/Base.lproj/Main.storyboard +++ b/AltStore/Base.lproj/Main.storyboard @@ -7,6 +7,7 @@ + @@ -105,7 +106,7 @@ - + @@ -158,6 +159,10 @@ + + + + @@ -172,177 +177,370 @@ - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - + + + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - + - - - + + + + + + + - - - - - + + + - + + + + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + @@ -351,23 +549,69 @@ - - + + - + + - - - - - - + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -621,6 +865,10 @@ + + + + @@ -664,11 +912,14 @@ - + + + + diff --git a/AltStore/Browse/BrowseViewController.swift b/AltStore/Browse/BrowseViewController.swift index 5af484d5..1323db8f 100644 --- a/AltStore/Browse/BrowseViewController.swift +++ b/AltStore/Browse/BrowseViewController.swift @@ -36,6 +36,18 @@ class BrowseViewController: UICollectionViewController let collectionViewLayout = self.collectionViewLayout as! UICollectionViewFlowLayout 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 diff --git a/AltStore/Browse/ScreenshotCollectionViewCell.swift b/AltStore/Browse/ScreenshotCollectionViewCell.swift index bc329a5a..a0bb049c 100644 --- a/AltStore/Browse/ScreenshotCollectionViewCell.swift +++ b/AltStore/Browse/ScreenshotCollectionViewCell.swift @@ -18,11 +18,25 @@ class ScreenshotCollectionViewCell: UICollectionViewCell required init?(coder aDecoder: NSCoder) { self.imageView = UIImageView(image: nil) - self.imageView.layer.cornerRadius = 8 self.imageView.layer.masksToBounds = true super.init(coder: aDecoder) 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 + } + } } diff --git a/AltStore/Components/CollapsingTextView.swift b/AltStore/Components/CollapsingTextView.swift new file mode 100644 index 00000000..b5b73378 --- /dev/null +++ b/AltStore/Components/CollapsingTextView.swift @@ -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 + } +} diff --git a/AltStore/Components/ToastView.swift b/AltStore/Components/ToastView.swift index 6961dc62..1e9f07c0 100644 --- a/AltStore/Components/ToastView.swift +++ b/AltStore/Components/ToastView.swift @@ -10,6 +10,17 @@ import Roxas 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() { super.layoutSubviews() diff --git a/AltStore/Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift similarity index 100% rename from AltStore/Apps/AppManager.swift rename to AltStore/Managing Apps/AppManager.swift diff --git a/AltStore/Model/AltStore.xcdatamodeld/AltStore.xcdatamodel/contents b/AltStore/Model/AltStore.xcdatamodeld/AltStore.xcdatamodel/contents index 595ff761..ed9210cf 100644 --- a/AltStore/Model/AltStore.xcdatamodeld/AltStore.xcdatamodel/contents +++ b/AltStore/Model/AltStore.xcdatamodeld/AltStore.xcdatamodel/contents @@ -27,12 +27,18 @@ + + + + + + @@ -59,8 +65,9 @@ - + + \ No newline at end of file diff --git a/AltStore/Model/App.swift b/AltStore/Model/App.swift index 14094d52..d506cff4 100644 --- a/AltStore/Model/App.swift +++ b/AltStore/Model/App.swift @@ -39,6 +39,11 @@ class App: NSManagedObject, Decodable, Fetchable /* Relationships */ @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?) { @@ -59,6 +64,7 @@ class App: NSManagedObject, Decodable, Fetchable case downloadURL case tintColor case subtitle + case permissions } required init(from decoder: Decoder) throws @@ -93,7 +99,12 @@ class App: NSManagedObject, Decodable, Fetchable self.tintColor = tintColor } + let permissions = try container.decodeIfPresent([AppPermission].self, forKey: .permissions) ?? [] + context.insert(self) + + // Must assign after we're inserted into context. + self._permissions = NSOrderedSet(array: permissions) } } diff --git a/AltStore/Model/AppPermission.swift b/AltStore/Model/AppPermission.swift new file mode 100644 index 00000000..7dc75373 --- /dev/null +++ b/AltStore/Model/AppPermission.swift @@ -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 + { + return NSFetchRequest(entityName: "AppPermission") + } +} diff --git a/AltStore/Model/DatabaseManager.swift b/AltStore/Model/DatabaseManager.swift index 017f35c1..58082454 100644 --- a/AltStore/Model/DatabaseManager.swift +++ b/AltStore/Model/DatabaseManager.swift @@ -22,6 +22,7 @@ public class DatabaseManager private init() { self.persistentContainer = RSTPersistentContainer(name: "AltStore") + self.persistentContainer.preferredMergePolicy = MergePolicy() } } diff --git a/AltStore/Model/MergePolicy.swift b/AltStore/Model/MergePolicy.swift new file mode 100644 index 00000000..e7e25e4e --- /dev/null +++ b/AltStore/Model/MergePolicy.swift @@ -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) + } +} diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index 95a8fe19..6da2618f 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -79,6 +79,18 @@ class MyAppsViewController: UICollectionViewController self.collectionView.register(UpdateCollectionViewCell.nib, forCellWithReuseIdentifier: "UpdateCell") 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 @@ -122,7 +134,7 @@ private extension MyAppsViewController 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) cell.updateButton.progress = progress diff --git a/AltStore/My Apps/UpdateCollectionViewCell.swift b/AltStore/My Apps/UpdateCollectionViewCell.swift index f610cc80..486665eb 100644 --- a/AltStore/My Apps/UpdateCollectionViewCell.swift +++ b/AltStore/My Apps/UpdateCollectionViewCell.swift @@ -30,10 +30,8 @@ extension UpdateCollectionViewCell @IBOutlet var dateLabel: UILabel! @IBOutlet var updateButton: PillButton! @IBOutlet var versionDescriptionTitleLabel: UILabel! - @IBOutlet var versionDescriptionTextView: UITextView! - - @IBOutlet var moreButton: UIButton! - + @IBOutlet var versionDescriptionTextView: CollapsingTextView! + override func awakeFromNib() { super.awakeFromNib() @@ -41,11 +39,6 @@ extension UpdateCollectionViewCell self.contentView.layer.cornerRadius = 20 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() } @@ -56,44 +49,6 @@ extension UpdateCollectionViewCell 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) { // Animates transition to new attributes. @@ -108,6 +63,12 @@ private extension UpdateCollectionViewCell { func update() { + switch self.mode + { + case .collapsed: self.versionDescriptionTextView.isCollapsed = true + case .expanded: self.versionDescriptionTextView.isCollapsed = false + } + self.versionDescriptionTitleLabel.textColor = self.tintColor self.contentView.backgroundColor = self.tintColor.withAlphaComponent(0.1) diff --git a/AltStore/My Apps/UpdateCollectionViewCell.xib b/AltStore/My Apps/UpdateCollectionViewCell.xib index 035acc78..3ec8c953 100644 --- a/AltStore/My Apps/UpdateCollectionViewCell.xib +++ b/AltStore/My Apps/UpdateCollectionViewCell.xib @@ -76,7 +76,7 @@ - + @@ -90,22 +90,12 @@ - - - @@ -124,7 +114,6 @@ - diff --git a/AltStore/Resources/Apps-Dev.json b/AltStore/Resources/Apps-Dev.json index 0b7ef4ae..8158fe95 100644 --- a/AltStore/Resources/Apps-Dev.json +++ b/AltStore/Resources/Apps-Dev.json @@ -3,25 +3,41 @@ "name": "AltStore", "identifier": "com.rileytestut.AltStore", "developerName": "Riley Testut", - "version": "1.0", - "versionDate": "2019-05-20", - "versionDescription": "AltStore has been updated with bug fixes and improvements.", + "version": "0.8", + "versionDate": "2019-07-16", + "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", "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", "identifier": "com.rileytestut.Delta", "developerName": "Riley Testut", - "subtitle": "Classic Nintendo games in your pocket.", - "version": "1.0", - "versionDate": "2019-05-20", + "subtitle": "Classic games in your pocket.", + "version": "0.8", + "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.", "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.", "iconName": "DeltaIcon", "tintColor": "8A28F7", + "permissions": [ + { + "type": "photos", + "usageDescription": "Allows Delta to use images from your Photo Library as game artwork." + } + ], "screenshotNames": [ "Delta1", "Delta2", @@ -34,10 +50,17 @@ "identifier": "com.rileytestut.ClipboardManager", "subtitle": "Manage your clipboard history with ease.", "developerName": "Riley Testut", - "version": "1.0", + "version": "0.8", "versionDate": "2019-06-20", + "versionDescription": "Bug fixes and improvements.", "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.", - "iconName": "ClipboardIcon" + "iconName": "ClipboardIcon", + "permissions": [ + { + "type": "background-audio", + "usageDescription": "Allows Clipboard Manager to continuously monitor your clipboard in the background." + } + ] } ] diff --git a/AltStore/Resources/Assets.xcassets/Back.imageset/Back@2x.png b/AltStore/Resources/Assets.xcassets/Back.imageset/Back@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b03046a30383a475a0e5753edf20d6437e98e0bf GIT binary patch literal 940 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6Xz}5igjtE6@fg!CBxDSzC zHb_`sNdc^+B->Ug!Z$#{Ilm}X!Bo#g&p^qJOF==wrYI%ND#*nRsvXF)RmvzSDX`Ml zFE20GD>v55FG|-pw6wI;H!#vSGSUUA&@HaaD@m--%_~-h7y>iLCAB!YD6^m>Ge1uO zWNucmA z*yy9HfNHkU2YDCC`;f2&ivk1Jjti(07EyLwELUEf21cv5r;B4q1n1is2e}Rz@U(3X zb^P!knKQ12<=||&udCiad(F{R5prc_g3{tG(XCs1H#l<4*7-Ya$3+r}pf zou9n^I@e78bt&Kc%rVZ(rt8@c|JHomM|eOa z`%?X2t%bklO)9!+SE6#kM%Z#wa!H*$*VHHZ6Kdx2K7GAXZRMnhxjzF|ENQyOHucK} t$Dp@Or^;j=H&1e2t-~HBb|v4vDnHxuXjRbD|B0YH;pyt?JmR7d~- literal 0 HcmV?d00001 diff --git a/AltStore/Resources/Assets.xcassets/Back.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Back.imageset/Contents.json new file mode 100644 index 00000000..02a54137 --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Back.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/Contents.json new file mode 100644 index 00000000..fcc90b1f --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/sound@2x.png b/AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/sound@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f7121412e6dd319cdc26a9ada4996aea5e870ae6 GIT binary patch literal 1896 zcmY*ac{m%`7LU+cT6+~$L^5R_B?wh%B0<^+vBXkT`3MntLPF9?rq!_zZGA$dsM5|< ztF+@;I;vAkq(SYf(;C!ThBAU0Ol^5d+t>Hr{l0VWx##}Q?|07qr; zsHHX3+S*)#FlVr1nPDvRSO(-X$$xk-Bu02Vh0dhVVnI^gFe2>&6A1=Og}#o@buuZD z|5b`*d}&M4AXIt+wX(2;e&v>=BBW8cXFP=@QI_&MSRp!s&L9ZET)sjQf;)hbR>OHKiD1W{o-Kr5lGk&!ErF8g;fu%Gq^VyGZ2 za=Ay;bcu}Ygv~V?ZZ^)bQGJaJZPj&81J2@h+NhOqcko(T5FteEpK%(q8pE7gbd+Oe zs)7GwZ2FqOeo8Y8gFfe(F^s*aNUZ?h_j}@q9`x&uKKH>UtM}?$1|SQS{ynhj)Lm-< zn^ICJECfF@MNcmewD~=)*doYO)U~ratx}NN^PdMR7CfoYUuAc>Z8{j` zy6R6;#3L77r?q!0RTQ_V+3Jzt>3aQzOHKjmEo@6>e?G3dWT7g}f2Jr?(0Vx4)qncY zk&k0`0y^{2RcnPS=A2 zX&5_jh;sXu#i|(l`)yoMBo6?ZbliAh4D#ZYybZbk?B`6`^XB4-_LKMK(Z{F)a%Dj)N0_KenOze915aid{Xx1!lyc)xmJhuqQW z>)OHG$yd!JOyfv=vEuB#FoxbrSCIGt{Pw-m#%QnTbj2w`vRx@ z-0|!C2r~`*H(`T?mKxogf>A*SFfnTrH>E!%+lt5-_ie?$Dl!}4Bt_ITQ=T?}#|^dx zc0)z(Kqn5>cBYIU@c3?d+7D^?ke9EL>4hL+h+O^4CeUP@H@LKyY`cG{bl_TT(=T`? zVoUAX_7bn8Q~SGV{&8dg>Vj^rDntbs;iM~0O5#u(U!!>Lqs*xKP~)D1#N`GN)>* z8E!$>h$b7^9?=Ap*KA1~fpFO%xB3F7obj4ns@lC=`%4UDn%__hG2X<4{JFzp+INXs z?#Eynd%w>{2XA1t0uMedeb~1|WEW>*sQ0?ed`4VI#hI4dDy);>i$yth!c~V`+NS~+ zEA@W5az_c=cfOg-j@~_#Yd>yFxG3(9)%iA6=dEK-Wg~3i^3YbL+N2NRM6O#5c>3U9 zzoyKzB{<&^0`2a=nlHkUh#LGIR0h#%P$6;G}%H^>g46W$T|dJnfqW83^=$(x^`?1D(mZVOd9W?IW;~+( zw@H`}uh(Vx?$jNRP|lQBDqEdvB(qXK+rxRyiUSUn+{o?;3jIj$Ej6xlL#4W?w><0{ ziYMQXg!F{dmM_ItV$&JP&j%quZ?fAgQhoGy{_?SZLs3QhSpG@wqw literal 0 HcmV?d00001 diff --git a/AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/sound@3x.png b/AltStore/Resources/Assets.xcassets/Permissions/BackgroundAudioPermission.imageset/sound@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..23bbdff5899b43403db59b2c41f56b9568fd6a65 GIT binary patch literal 2991 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 T&z+w=`5f!(m>>z)T<-h_M~`Ii literal 0 HcmV?d00001 diff --git a/AltStore/Resources/Assets.xcassets/Permissions/Contents.json b/AltStore/Resources/Assets.xcassets/Permissions/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Permissions/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/Contents.json b/AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/Contents.json new file mode 100644 index 00000000..de4d09de --- /dev/null +++ b/AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/photos@2x.png b/AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/photos@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..63dcd17722e888a4a481b62ae4b655ed11fd1f39 GIT binary patch literal 6167 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 literal 0 HcmV?d00001 diff --git a/AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/photos@3x.png b/AltStore/Resources/Assets.xcassets/Permissions/PhotosPermission.imageset/photos@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..522b8bd84b98aa455e33bf2da466cb0e5eb6e8c7 GIT binary patch literal 10703 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 BGOhps literal 0 HcmV?d00001 diff --git a/AltStore/Types/ALTAppPermission.h b/AltStore/Types/ALTAppPermission.h new file mode 100644 index 00000000..5c68b1b1 --- /dev/null +++ b/AltStore/Types/ALTAppPermission.h @@ -0,0 +1,14 @@ +// +// ALTAppPermission.h +// AltStore +// +// Created by Riley Testut on 7/23/19. +// Copyright © 2019 Riley Testut. All rights reserved. +// + +#import + +typedef NSString *ALTAppPermissionType NS_TYPED_EXTENSIBLE_ENUM; +extern ALTAppPermissionType const ALTAppPermissionTypePhotos; +extern ALTAppPermissionType const ALTAppPermissionTypeBackgroundAudio; +extern ALTAppPermissionType const ALTAppPermissionTypeBackgroundFetch; diff --git a/AltStore/Types/ALTAppPermission.m b/AltStore/Types/ALTAppPermission.m new file mode 100644 index 00000000..7eee2905 --- /dev/null +++ b/AltStore/Types/ALTAppPermission.m @@ -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";