From 24334f88ce7cb362b684d0810518fa0471b6c156 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Wed, 11 Oct 2023 17:59:01 -0500 Subject: [PATCH 01/19] [AltStoreCore] Updates DatabaseManager to support #Preview macro Synchronously loads database via startForPreview(), and also erases database for DEBUG builds. # Conflicts: # AltStore.xcodeproj/project.pbxproj --- AltStore.xcodeproj/project.pbxproj | 4 +++ .../Extensions/ProcessInfo+Previews.swift | 16 ++++++++++ AltStoreCore/Model/DatabaseManager.swift | 32 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 AltStoreCore/Extensions/ProcessInfo+Previews.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 701d3416..83393f54 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -414,6 +414,7 @@ D5A299882AAB9E4E00A3988D /* JITError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1D2E32AA50EB60066CACC /* JITError.swift */; }; D5A299892AAB9E5900A3988D /* AppProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59A6B7D2AA9226C00F61259 /* AppProcess.swift */; }; D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */; }; + D5B6F6A92AD75D01007EED5A /* ProcessInfo+Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6F6A82AD75D01007EED5A /* ProcessInfo+Previews.swift */; }; D5BA9E9B2A9FE1E8007C0661 /* JITManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5BA9E9A2A9FE1E8007C0661 /* JITManager.swift */; }; D5C8ACDB2A956B2B00669F92 /* Process+STPrivilegedTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C8ACDA2A956B2B00669F92 /* Process+STPrivilegedTask.swift */; }; D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4A280E141900469595 /* ManagedPatron.swift */; }; @@ -1012,6 +1013,7 @@ D5A1D2EA2AA513410066CACC /* URL+Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Tools.swift"; sourceTree = ""; }; D5A2193329B14F94002229FC /* DeprecatedAPIs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedAPIs.swift; sourceTree = ""; }; D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearAppCacheOperation.swift; sourceTree = ""; }; + D5B6F6A82AD75D01007EED5A /* ProcessInfo+Previews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+Previews.swift"; sourceTree = ""; }; D5BA9E9A2A9FE1E8007C0661 /* JITManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JITManager.swift; sourceTree = ""; }; D5C8ACDA2A956B2B00669F92 /* Process+STPrivilegedTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Process+STPrivilegedTask.swift"; sourceTree = ""; }; D5CA0C4A280E141900469595 /* ManagedPatron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPatron.swift; sourceTree = ""; }; @@ -1581,6 +1583,7 @@ BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */, D5F48B4729CCF21B002B52A4 /* AltStore+Async.swift */, D5893F7E2A14183200E767CD /* NSManagedObjectContext+Conveniences.swift */, + D5B6F6A82AD75D01007EED5A /* ProcessInfo+Previews.swift */, ); path = Extensions; sourceTree = ""; @@ -3026,6 +3029,7 @@ BFAECC5A2501B0A400528F27 /* NetworkConnection.swift in Sources */, D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */, BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */, + D5B6F6A92AD75D01007EED5A /* ProcessInfo+Previews.swift in Sources */, BF66EEEB2501AED0007EE018 /* UIApplication+AppExtension.swift in Sources */, D5F48B4829CCF21B002B52A4 /* AltStore+Async.swift in Sources */, BF66EED92501AECA007EE018 /* Team.swift in Sources */, diff --git a/AltStoreCore/Extensions/ProcessInfo+Previews.swift b/AltStoreCore/Extensions/ProcessInfo+Previews.swift new file mode 100644 index 00000000..2687054d --- /dev/null +++ b/AltStoreCore/Extensions/ProcessInfo+Previews.swift @@ -0,0 +1,16 @@ +// +// ProcessInfo+Previews.swift +// AltStoreCore +// +// Created by Riley Testut on 10/11/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation + +public extension ProcessInfo +{ + var isPreview: Bool { + ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" + } +} diff --git a/AltStoreCore/Model/DatabaseManager.swift b/AltStoreCore/Model/DatabaseManager.swift index c58ecd3d..c81f03e6 100644 --- a/AltStoreCore/Model/DatabaseManager.swift +++ b/AltStoreCore/Model/DatabaseManager.swift @@ -87,6 +87,22 @@ public extension DatabaseManager guard !self.isStarted else { return finish(nil) } + #if DEBUG + // Wrap in #if DEBUG to *ensure* we never accidentally delete production databases. + if ProcessInfo.processInfo.isPreview + { + do + { + print("!!! Purging database for preview...") + try FileManager.default.removeItem(at: PersistentContainer.defaultDirectoryURL()) + } + catch + { + print("Failed to remove database directory for preview.", error) + } + } + #endif + if self.persistentContainer.isMigrationRequired { // Quit any other running AltStore processes to prevent concurrent database access during and after migration. @@ -166,6 +182,22 @@ public extension DatabaseManager } } +public extension DatabaseManager +{ + func startForPreview() + { + let semaphore = DispatchSemaphore(value: 0) + + self.dispatchQueue.async { + self.startCompletionHandlers.append { error in + semaphore.signal() + } + } + + semaphore.wait() + } +} + public extension DatabaseManager { var viewContext: NSManagedObjectContext { From ff46fb38b90d49f766613ab96cbb2cb5a47dd447 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Mon, 16 Oct 2023 18:18:06 -0500 Subject: [PATCH 02/19] [AltStoreCore] Adds Source.isRecommended MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also replaces legacy “Trusted Sources” references with “Recommended Sources” --- AltStore.xcodeproj/project.pbxproj | 6 +++--- .../UpdateKnownSourcesOperation.swift | 4 +++- AltStore/Operations/VerifyAppOperation.swift | 8 ++++---- AltStoreCore/Model/Source.swift | 10 ++++++++++ .../Types/KnownSource.swift | 20 +++++++++---------- 5 files changed, 30 insertions(+), 18 deletions(-) rename {AltStore => AltStoreCore}/Types/KnownSource.swift (72%) diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 83393f54..fab7f83b 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -390,7 +390,6 @@ D586D39B28EF58B0000E101F /* AltTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586D39A28EF58B0000E101F /* AltTests.swift */; }; D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58916FD28C7C55C00E39C8B /* LoggedError.swift */; }; D5893F802A1419E800E767CD /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5893F7E2A14183200E767CD /* NSManagedObjectContext+Conveniences.swift */; }; - D5893F822A141E4900E767CD /* KnownSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5893F812A141E4900E767CD /* KnownSource.swift */; }; D58D5F2E26DFE68E00E55E38 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = D58D5F2D26DFE68E00E55E38 /* LaunchAtLogin */; }; D59162AB29BA60A9005CBF47 /* SourceHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59162AA29BA60A9005CBF47 /* SourceHeaderView.swift */; }; D59162AD29BA616A005CBF47 /* SourceHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D59162AC29BA616A005CBF47 /* SourceHeaderView.xib */; }; @@ -435,6 +434,7 @@ D5F5AF7D28ECEA990067C736 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */; }; D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */; }; D5F99A1A28D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */; }; + D5FB28EE2ADDF89800A1C337 /* KnownSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5893F812A141E4900E767CD /* KnownSource.swift */; }; D5FB7A0E2AA25A4E00EF863D /* Previews.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5FB7A0D2AA25A4E00EF863D /* Previews.xcassets */; }; D5FB7A212AA284ED00EF863D /* EnableJIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1A2AA284ED00EF863D /* EnableJIT.swift */; }; D5FB7A242AA284ED00EF863D /* Logger+AltJIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1D2AA284ED00EF863D /* Logger+AltJIT.swift */; }; @@ -1231,7 +1231,6 @@ BF41B807233433C100C593A3 /* LoadingState.swift */, D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */, D5A2193329B14F94002229FC /* DeprecatedAPIs.swift */, - D5893F812A141E4900E767CD /* KnownSource.swift */, ); path = Types; sourceTree = ""; @@ -1474,6 +1473,7 @@ children = ( BFB39B5B252BC10E00D1BE50 /* Managed.swift */, D5F48B4929CD0B67002B52A4 /* AsyncManaged.swift */, + D5893F812A141E4900E767CD /* KnownSource.swift */, BF66EE8E2501AEBC007EE018 /* ALTAppPermissions.h */, BF66EE912501AEBC007EE018 /* ALTAppPermissions.m */, BF66EE922501AEBC007EE018 /* ALTPatreonBenefitType.h */, @@ -2989,6 +2989,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D5FB28EE2ADDF89800A1C337 /* KnownSource.swift in Sources */, BF66EED32501AECA007EE018 /* AltStore2ToAltStore3.xcmappingmodel in Sources */, BF66EEA52501AEC5007EE018 /* Benefit.swift in Sources */, BF66EED22501AECA007EE018 /* AltStore4ToAltStore5.xcmappingmodel in Sources */, @@ -3098,7 +3099,6 @@ D540E93828EE1BDE000F1B0F /* ErrorDetailsViewController.swift in Sources */, D513F6162A12CE4E0061EAA1 /* SourceError.swift in Sources */, BF56D2AC23DF8E170006506D /* FetchAppIDsOperation.swift in Sources */, - D5893F822A141E4900E767CD /* KnownSource.swift in Sources */, BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */, BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */, D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */, diff --git a/AltStore/Operations/UpdateKnownSourcesOperation.swift b/AltStore/Operations/UpdateKnownSourcesOperation.swift index e553b5f1..24de3061 100644 --- a/AltStore/Operations/UpdateKnownSourcesOperation.swift +++ b/AltStore/Operations/UpdateKnownSourcesOperation.swift @@ -8,6 +8,8 @@ import Foundation +import AltStoreCore + private extension URL { #if STAGING @@ -66,7 +68,7 @@ class UpdateKnownSourcesOperation: ResultOperation<([KnownSource], [KnownSource] let sources = (trusted: response.trusted ?? [], blocked: response.blocked ?? []) // Cache sources - UserDefaults.shared.trustedSources = sources.trusted + UserDefaults.shared.recommendedSources = sources.trusted UserDefaults.shared.blockedSources = sources.blocked self.finish(.success(sources)) diff --git a/AltStore/Operations/VerifyAppOperation.swift b/AltStore/Operations/VerifyAppOperation.swift index d2cede60..e0db2a26 100644 --- a/AltStore/Operations/VerifyAppOperation.swift +++ b/AltStore/Operations/VerifyAppOperation.swift @@ -94,11 +94,11 @@ class VerifyAppOperation: ResultOperation throw error #endif - if let trustedSources = UserDefaults.shared.trustedSources, let sourceID = await self.context.$appVersion.sourceID + if let recommendedSources = UserDefaults.shared.recommendedSources, let sourceID = await self.context.$appVersion.sourceID { - let isTrusted = trustedSources.contains { $0.identifier == sourceID } - guard !isTrusted else { - // Don't enforce permission checking for Trusted Sources while 2.0 is in beta. + let isRecommended = recommendedSources.contains { $0.identifier == sourceID } + guard !isRecommended else { + // Don't enforce permission checking for Recommended Sources while 2.0 is in beta. return self.finish(.success(())) } } diff --git a/AltStoreCore/Model/Source.swift b/AltStoreCore/Model/Source.swift index de4349b7..65a6fda1 100644 --- a/AltStoreCore/Model/Source.swift +++ b/AltStoreCore/Model/Source.swift @@ -236,6 +236,16 @@ public extension Source return isAdded } } + + var isRecommended: Bool { + guard let recommendedSources = UserDefaults.shared.recommendedSources else { return false } + + // TODO: Support alternate URLs + let isRecommended = recommendedSources.contains { source in + return source.identifier == self.identifier || source.sourceURL?.absoluteString.lowercased() == self.sourceURL.absoluteString + } + return isRecommended + } } internal extension Source diff --git a/AltStore/Types/KnownSource.swift b/AltStoreCore/Types/KnownSource.swift similarity index 72% rename from AltStore/Types/KnownSource.swift rename to AltStoreCore/Types/KnownSource.swift index dd961843..6f365ac4 100644 --- a/AltStore/Types/KnownSource.swift +++ b/AltStoreCore/Types/KnownSource.swift @@ -8,11 +8,11 @@ import Foundation -struct KnownSource: Decodable +public struct KnownSource: Decodable { - var identifier: String - var sourceURL: URL? - var bundleIDs: [String]? + public var identifier: String + public var sourceURL: URL? + public var bundleIDs: [String]? } private extension KnownSource @@ -42,19 +42,19 @@ private extension KnownSource } } -extension UserDefaults +public extension UserDefaults { - // Cache trusted sources just in case we need to check whether source is trusted or not. - @nonobjc var trustedSources: [KnownSource]? { + // Cache recommended sources just in case we need to check whether source is recommended or not. + @nonobjc var recommendedSources: [KnownSource]? { get { - guard let sources = _trustedSources?.compactMap({ KnownSource(dictionary: $0) }) else { return nil } + guard let sources = _recommendedSources?.compactMap({ KnownSource(dictionary: $0) }) else { return nil } return sources } set { - _trustedSources = newValue?.map { $0.dictionaryRepresentation } + _recommendedSources = newValue?.map { $0.dictionaryRepresentation } } } - @NSManaged @objc(trustedSources) private var _trustedSources: [[String: Any]]? + @NSManaged @objc(recommendedSources) private var _recommendedSources: [[String: Any]]? @nonobjc var blockedSources: [KnownSource]? { get { From 17b6235ad990fb96d41d27213b4bcaa0d0f38e14 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Mon, 16 Oct 2023 18:27:48 -0500 Subject: [PATCH 03/19] =?UTF-8?q?Limits=20relative=20date=20strings=20to?= =?UTF-8?q?=20=E2=80=9CYesterday=E2=80=9D=20and=20=E2=80=9CToday=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Any relative date older than “Yesterday” will be displayed as absolute date instead. --- AltStore/App Detail/AppContentViewController.swift | 9 +-------- AltStore/My Apps/MyAppsViewController.swift | 9 +-------- AltStoreCore/Extensions/Date+RelativeDate.swift | 11 +++++++++-- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/AltStore/App Detail/AppContentViewController.swift b/AltStore/App Detail/AppContentViewController.swift index 01affae1..b9e882b8 100644 --- a/AltStore/App Detail/AppContentViewController.swift +++ b/AltStore/App Detail/AppContentViewController.swift @@ -31,13 +31,6 @@ class AppContentViewController: UITableViewController private lazy var screenshotsDataSource = self.makeScreenshotsDataSource() - private lazy var dateFormatter: DateFormatter = { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .medium - dateFormatter.timeStyle = .none - return dateFormatter - }() - private lazy var byteCountFormatter: ByteCountFormatter = { let formatter = ByteCountFormatter() return formatter @@ -84,7 +77,7 @@ class AppContentViewController: UITableViewController { self.versionDescriptionTextView.text = version.localizedDescription self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.localizedVersion) - self.versionDateLabel.text = Date().relativeDateString(since: version.date, dateFormatter: self.dateFormatter) + self.versionDateLabel.text = Date().relativeDateString(since: version.date) self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: version.size) } else diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index 97857602..ddce5cc4 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -62,13 +62,6 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing // Cache private var cachedUpdateSizes = [String: CGSize]() - private lazy var dateFormatter: DateFormatter = { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .medium - dateFormatter.timeStyle = .none - return dateFormatter - }() - required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) @@ -236,7 +229,7 @@ private extension MyAppsViewController cell.bannerView.configure(for: app) - let versionDate = Date().relativeDateString(since: latestSupportedVersion.date, dateFormatter: self.dateFormatter) + let versionDate = Date().relativeDateString(since: latestSupportedVersion.date) cell.bannerView.subtitleLabel.text = versionDate let appName: String diff --git a/AltStoreCore/Extensions/Date+RelativeDate.swift b/AltStoreCore/Extensions/Date+RelativeDate.swift index 62b7251b..ec7becee 100644 --- a/AltStoreCore/Extensions/Date+RelativeDate.swift +++ b/AltStoreCore/Extensions/Date+RelativeDate.swift @@ -10,6 +10,13 @@ import Foundation public extension Date { + private static let mediumDateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .medium + dateFormatter.timeStyle = .none + return dateFormatter + }() + func numberOfCalendarDays(since date: Date) -> Int { let today = Calendar.current.startOfDay(for: self) @@ -19,15 +26,15 @@ public extension Date return components.day! } - func relativeDateString(since date: Date, dateFormatter: DateFormatter) -> String + func relativeDateString(since date: Date, dateFormatter: DateFormatter? = nil) -> String { + let dateFormatter = dateFormatter ?? Date.mediumDateFormatter let numberOfDays = self.numberOfCalendarDays(since: date) switch numberOfDays { case 0: return NSLocalizedString("Today", comment: "") case 1: return NSLocalizedString("Yesterday", comment: "") - case 2...7: return String(format: NSLocalizedString("%@ days ago", comment: ""), NSNumber(value: numberOfDays)) default: return dateFormatter.string(from: date) } } From cdfb55deeb48c2eefa68d1010ebeefcdf0817179 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Mon, 16 Oct 2023 18:29:59 -0500 Subject: [PATCH 04/19] Posts Notification when Source is added or removed --- AltStore/Managing Apps/AppManager.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index e1d1973f..1f0a22bf 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -22,6 +22,8 @@ extension AppManager { static let didFetchSourceNotification = Notification.Name("io.altstore.AppManager.didFetchSource") static let didUpdatePatronsNotification = Notification.Name("io.altstore.AppManager.didUpdatePatrons") + static let didAddSourceNotification = Notification.Name("io.altstore.AppManager.didAddSource") + static let didRemoveSourceNotification = Notification.Name("io.altstore.AppManager.didRemoveSource") static let expirationWarningNotificationID = "altstore-expiration-warning" static let enableJITResultNotificationID = "altstore-enable-jit" @@ -366,6 +368,8 @@ extension AppManager try await context.performAsync { try context.save() } + + NotificationCenter.default.post(name: AppManager.didAddSourceNotification, object: source) } func remove(@AsyncManaged _ source: Source, presentingViewController: UIViewController) async throws @@ -388,6 +392,8 @@ extension AppManager context.delete(source) try context.save() } + + NotificationCenter.default.post(name: AppManager.didRemoveSourceNotification, object: source) } } From b43bdede74f6786b2f14e2fb3fe0246804a6435c Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Mon, 16 Oct 2023 18:40:01 -0500 Subject: [PATCH 05/19] Adds AppIconImageView.style to switch between `icon` and `circular` styles `icon` approximates the rounded corners of an app icon, while `circular` makes the icon a circle. --- AltStore/Components/AppIconImageView.swift | 29 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/AltStore/Components/AppIconImageView.swift b/AltStore/Components/AppIconImageView.swift index 041afdc2..61462ed4 100644 --- a/AltStore/Components/AppIconImageView.swift +++ b/AltStore/Components/AppIconImageView.swift @@ -8,8 +8,23 @@ import UIKit +extension AppIconImageView +{ + enum Style + { + case icon + case circular + } +} + class AppIconImageView: UIImageView { + var style: Style = .icon { + didSet { + self.setNeedsLayout() + } + } + override func awakeFromNib() { super.awakeFromNib() @@ -25,8 +40,16 @@ class AppIconImageView: UIImageView { super.layoutSubviews() - // Based off of 60pt icon having 12pt radius. - let radius = self.bounds.height / 5 - self.layer.cornerRadius = radius + switch self.style + { + case .icon: + // Based off of 60pt icon having 12pt radius. + let radius = self.bounds.height / 5 + self.layer.cornerRadius = radius + + case .circular: + let radius = self.bounds.height / 2 + self.layer.cornerRadius = radius + } } } From 4e104ac6744e7623aa4ac5c243356e6ebe9ec95e Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Mon, 16 Oct 2023 18:37:36 -0500 Subject: [PATCH 06/19] Adds PillButton.style to switch between `pill` and `custom` styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pill` style enforces minimum size + content insets, while `custom` doesn’t. --- AltStore/Components/AppBannerView.xib | 4 +-- AltStore/Components/PillButton.swift | 42 ++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/AltStore/Components/AppBannerView.xib b/AltStore/Components/AppBannerView.xib index 95337f61..c27095f2 100644 --- a/AltStore/Components/AppBannerView.xib +++ b/AltStore/Components/AppBannerView.xib @@ -110,12 +110,12 @@ -