diff --git a/Mist.xcodeproj/project.pbxproj b/Mist.xcodeproj/project.pbxproj index 8d01f1a..0f42490 100644 --- a/Mist.xcodeproj/project.pbxproj +++ b/Mist.xcodeproj/project.pbxproj @@ -26,7 +26,7 @@ 390451DC28573F1000E0B563 /* Dictionary+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451DB28573F1000E0B563 /* Dictionary+Extension.swift */; }; 390451DF28573FAA00E0B563 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 390451DE28573FAA00E0B563 /* Yams */; }; 390451E1285740E800E0B563 /* Sequence+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451E0285740E800E0B563 /* Sequence+Extension.swift */; }; - 390451E528574F0000E0B563 /* Catalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451E428574F0000E0B563 /* Catalog.swift */; }; + 390451E528574F0000E0B563 /* CatalogType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451E428574F0000E0B563 /* CatalogType.swift */; }; 390451E72857510C00E0B563 /* TextTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390451E62857510B00E0B563 /* TextTag.swift */; }; 39148CFC28DD55B300011FF5 /* PathControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39148CFB28DD55B300011FF5 /* PathControl.swift */; }; 39252A77285A849F00956C74 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39252A76285A849F00956C74 /* AppDelegate.swift */; }; @@ -94,7 +94,8 @@ 398734D028603D9E00B4C357 /* UInt8+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734CF28603D9E00B4C357 /* UInt8+Extension.swift */; }; 398734D228603DE700B4C357 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734D128603DE700B4C357 /* Array+Extension.swift */; }; 398734D4286046B000B4C357 /* UInt32+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734D3286046B000B4C357 /* UInt32+Extension.swift */; }; - 39CF4E732859C03D009E708C /* CatalogRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CF4E722859C03D009E708C /* CatalogRow.swift */; }; + 39CB5E3D293F5C2E00CFDBB8 /* Catalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CB5E3C293F5C2E00CFDBB8 /* Catalog.swift */; }; + 39CB5E3F2941486D00CFDBB8 /* CatalogSeedType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CB5E3E2941486D00CFDBB8 /* CatalogSeedType.swift */; }; 39CF55A028614DD8006FB5D2 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CF559F28614DD8006FB5D2 /* main.swift */; }; 39CF55AA286154A5006FB5D2 /* Blessed in Frameworks */ = {isa = PBXBuildFile; productRef = 39CF55A9286154A5006FB5D2 /* Blessed */; }; 39CF55AB286154D1006FB5D2 /* com.ninxsoft.mist.helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 39CF559D28614DD8006FB5D2 /* com.ninxsoft.mist.helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -173,7 +174,7 @@ 390451D928573ADC00E0B563 /* ExportListType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportListType.swift; sourceTree = ""; }; 390451DB28573F1000E0B563 /* Dictionary+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Extension.swift"; sourceTree = ""; }; 390451E0285740E800E0B563 /* Sequence+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Extension.swift"; sourceTree = ""; }; - 390451E428574F0000E0B563 /* Catalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Catalog.swift; sourceTree = ""; }; + 390451E428574F0000E0B563 /* CatalogType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogType.swift; sourceTree = ""; }; 390451E62857510B00E0B563 /* TextTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextTag.swift; sourceTree = ""; }; 39148CFB28DD55B300011FF5 /* PathControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathControl.swift; sourceTree = ""; }; 39252A76285A849F00956C74 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -237,7 +238,8 @@ 398734CF28603D9E00B4C357 /* UInt8+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt8+Extension.swift"; sourceTree = ""; }; 398734D128603DE700B4C357 /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = ""; }; 398734D3286046B000B4C357 /* UInt32+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt32+Extension.swift"; sourceTree = ""; }; - 39CF4E722859C03D009E708C /* CatalogRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogRow.swift; sourceTree = ""; }; + 39CB5E3C293F5C2E00CFDBB8 /* Catalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Catalog.swift; sourceTree = ""; }; + 39CB5E3E2941486D00CFDBB8 /* CatalogSeedType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogSeedType.swift; sourceTree = ""; }; 39CF559D28614DD8006FB5D2 /* com.ninxsoft.mist.helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = com.ninxsoft.mist.helper; sourceTree = BUILT_PRODUCTS_DIR; }; 39CF559F28614DD8006FB5D2 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 39CF55A528614E66006FB5D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -379,8 +381,9 @@ 390451C32856E4A500E0B563 /* Model */ = { isa = PBXGroup; children = ( - 390451E428574F0000E0B563 /* Catalog.swift */, - 39CF4E722859C03D009E708C /* CatalogRow.swift */, + 39CB5E3C293F5C2E00CFDBB8 /* Catalog.swift */, + 390451E428574F0000E0B563 /* CatalogType.swift */, + 39CB5E3E2941486D00CFDBB8 /* CatalogSeedType.swift */, 398734CB28603D5F00B4C357 /* Chunklist.swift */, 398734CD28603D7F00B4C357 /* Chunk.swift */, 395DCD15287FE36E00C411CE /* DownloadAlertType.swift */, @@ -740,6 +743,7 @@ 398734C428600E6E00B4C357 /* TaskManager.swift in Sources */, 390451D62856F7FE00E0B563 /* UInt64+Extension.swift in Sources */, 3935F47E2864813B00760AB0 /* DownloadManager.swift in Sources */, + 39CB5E3D293F5C2E00CFDBB8 /* Catalog.swift in Sources */, 39252AA1285C2A1600956C74 /* PaddedDivider.swift in Sources */, 39CF560C2861AE93006FB5D2 /* HelperToolCommandResponse.swift in Sources */, 39252A99285BFE2C00956C74 /* MistTaskState.swift in Sources */, @@ -767,14 +771,14 @@ 390451C22856E3F500E0B563 /* Hardware.swift in Sources */, 39CF56092861AE7F006FB5D2 /* HelperToolCommandRequest.swift in Sources */, 390451C82856E94900E0B563 /* FirmwareListRow.swift in Sources */, - 39CF4E732859C03D009E708C /* CatalogRow.swift in Sources */, - 390451E528574F0000E0B563 /* Catalog.swift in Sources */, + 390451E528574F0000E0B563 /* CatalogType.swift in Sources */, 3935F4852866B64900760AB0 /* MistTaskSection.swift in Sources */, 390451AC2856E1D900E0B563 /* ContentView.swift in Sources */, 3935F4A4286AD21000760AB0 /* DownloadProgressView.swift in Sources */, 39252A89285AD0AB00956C74 /* SettingsHeaderView.swift in Sources */, 39252A85285ACDC800956C74 /* ResetToDefaultButton.swift in Sources */, 39CF560F2861B857006FB5D2 /* XPCRoute+Extension.swift in Sources */, + 39CB5E3F2941486D00CFDBB8 /* CatalogSeedType.swift in Sources */, 39252A7F285AC6F600956C74 /* SettingsPackagesView.swift in Sources */, 39CF562F2862A797006FB5D2 /* ISOConverter.swift in Sources */, 39CF56392862D75D006FB5D2 /* FileCreator.swift in Sources */, diff --git a/Mist/Model/Catalog.swift b/Mist/Model/Catalog.swift index b9f5a3b..9bb50aa 100644 --- a/Mist/Model/Catalog.swift +++ b/Mist/Model/Catalog.swift @@ -2,32 +2,61 @@ // Catalog.swift // Mist // -// Created by Nindi Gill on 13/6/2022. +// Created by Nindi Gill on 6/12/2022. // import Foundation -enum Catalog: String, CaseIterable { - // swiftlint:disable redundant_string_enum_value - case standard = "standard" - case customer = "customer" - case developer = "developer" - case `public` = "public" +struct Catalog: Identifiable, Decodable, Equatable { - static var urls: [String] { - self.allCases.map { $0.url } + enum CodingKeys: String, CodingKey { + // swiftlint:disable:next redundant_string_enum_value + case type = "type" + // swiftlint:disable:next redundant_string_enum_value + case standard = "standard" + // swiftlint:disable:next redundant_string_enum_value + case customerSeed = "customerSeed" + // swiftlint:disable:next redundant_string_enum_value + case developerSeed = "developerSeed" + // swiftlint:disable:next redundant_string_enum_value + case publicSeed = "publicSeed" } - var url: String { - switch self { - case .standard: - return "https://swscan.apple.com/content/catalogs/others/index-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" - case .customer: - return "https://swscan.apple.com/content/catalogs/others/index-12customerseed-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" - case .developer: - return "https://swscan.apple.com/content/catalogs/others/index-12seed-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" - case .`public`: - return "https://swscan.apple.com/content/catalogs/others/index-12beta-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" - } + static var example: Catalog { + Catalog(type: .ventura, standard: true, customerSeed: false, developerSeed: false, publicSeed: false) + } + + var id: UUID = UUID() + var type: CatalogType + var standard: Bool + var customerSeed: Bool + var developerSeed: Bool + var publicSeed: Bool + + init(type: CatalogType, standard: Bool, customerSeed: Bool, developerSeed: Bool, publicSeed: Bool) { + self.type = type + self.standard = standard + self.customerSeed = customerSeed + self.developerSeed = developerSeed + self.publicSeed = publicSeed + } + + init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + type = try container.decode(CatalogType.self, forKey: .type) + standard = try container.decode(Bool.self, forKey: .standard) + customerSeed = try container.decode(Bool.self, forKey: .customerSeed) + developerSeed = try container.decode(Bool.self, forKey: .developerSeed) + publicSeed = try container.decode(Bool.self, forKey: .publicSeed) + } + + func dictionary() -> [String: Any] { + [ + "type": type.description, + "standard": standard, + "customerSeed": customerSeed, + "developerSeed": developerSeed, + "publicSeed": publicSeed + ] } } diff --git a/Mist/Model/CatalogRow.swift b/Mist/Model/CatalogRow.swift deleted file mode 100644 index 9ab13a5..0000000 --- a/Mist/Model/CatalogRow.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// CatalogRow.swift -// Mist -// -// Created by Nindi Gill on 15/6/2022. -// - -import Foundation - -struct CatalogRow: Identifiable, Hashable { - - static var example: CatalogRow { - CatalogRow(url: Catalog.standard.url) - } - - var id: UUID = UUID() - var url: String -} diff --git a/Mist/Model/CatalogSeedType.swift b/Mist/Model/CatalogSeedType.swift new file mode 100644 index 0000000..c329d86 --- /dev/null +++ b/Mist/Model/CatalogSeedType.swift @@ -0,0 +1,19 @@ +// +// CatalogSeedType.swift +// Mist +// +// Created by Nindi Gill on 8/12/2022. +// + +import Foundation + +enum CatalogSeedType: String { + case standard = "Standard" + case customer = "Customer" + case developer = "Developer" + case `public` = "Public" + + var description: String { + rawValue + } +} diff --git a/Mist/Model/CatalogType.swift b/Mist/Model/CatalogType.swift new file mode 100644 index 0000000..f09af2c --- /dev/null +++ b/Mist/Model/CatalogType.swift @@ -0,0 +1,80 @@ +// +// CatalogType.swift +// Mist +// +// Created by Nindi Gill on 13/6/2022. +// + +import Foundation + +enum CatalogType: String, CaseIterable, Comparable, Decodable { + case ventura = "macOS Ventura" + case monterey = "macOS Monterey" + case bigSur = "macOS Big Sur" + + var description: String { + rawValue + } + + var imageName: String { + rawValue + } + + private var sortOrder: Int { + switch self { + case .ventura: + return 0 + case .monterey: + return 1 + case .bigSur: + return 2 + } + } + + static func < (lhs: CatalogType, rhs: CatalogType) -> Bool { + lhs.sortOrder < rhs.sortOrder + } + + // swiftlint:disable:next cyclomatic_complexity + func url(for seedType: CatalogSeedType) -> String { + + switch self { + case .ventura: + switch seedType { + case .standard: + return "https://swscan.apple.com/content/catalogs/others/index-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + case .customer: + // swiftlint:disable:next line_length + return "https://swscan.apple.com/content/catalogs/others/index-13customerseed-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + case .developer: + return "https://swscan.apple.com/content/catalogs/others/index-13seed-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + case .public: + return "https://swscan.apple.com/content/catalogs/others/index-13beta-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + } + case .monterey: + switch seedType { + case .standard: + return "https://swscan.apple.com/content/catalogs/others/index-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + case .customer: + // swiftlint:disable:next line_length + return "https://swscan.apple.com/content/catalogs/others/index-12customerseed-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + case .developer: + return "https://swscan.apple.com/content/catalogs/others/index-12seed-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + case .public: + return "https://swscan.apple.com/content/catalogs/others/index-12beta-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + } + case .bigSur: + switch seedType { + case .standard: + return "https://swscan.apple.com/content/catalogs/others/index-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + case .customer: + // swiftlint:disable:next line_length + return "https://swscan.apple.com/content/catalogs/others/index-10.16customerseed-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + case .developer: + return "https://swscan.apple.com/content/catalogs/others/index-10.16seed-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + case .public: + return "https://swscan.apple.com/content/catalogs/others/index-10.16beta-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz" + } + } + } +} diff --git a/Mist/Views/Refresh/RefreshView.swift b/Mist/Views/Refresh/RefreshView.swift index 9e726fe..c9cef57 100644 --- a/Mist/Views/Refresh/RefreshView.swift +++ b/Mist/Views/Refresh/RefreshView.swift @@ -7,6 +7,7 @@ import SwiftUI +// swiftlint:disable:next type_body_length struct RefreshView: View { @Environment(\.presentationMode) var presentationMode: Binding @Binding var firmwares: [Firmware] @@ -124,11 +125,11 @@ struct RefreshView: View { private func retrieveInstallers() throws -> [Installer] { var installers: [Installer] = [] - let catalogs: [String] = UserDefaults.standard.array(forKey: "catalogURLs") as? [String] ?? Catalog.urls + let catalogURLs: [String] = getCatalogURLs() - for catalog in catalogs { + for catalogURL in catalogURLs { - guard let url: URL = URL(string: catalog) else { + guard let url: URL = URL(string: catalogURL) else { continue } @@ -161,6 +162,49 @@ struct RefreshView: View { return installers } + private func getCatalogURLs() -> [String] { + + var catalogURLs: [String] = [] + var catalogs: [Catalog] = [] + let defaultCatalogs: [Catalog] = CatalogType.allCases.map { Catalog(type: $0, standard: true, customerSeed: false, developerSeed: false, publicSeed: false) } + + if let array: [[String: Any]] = UserDefaults.standard.array(forKey: "catalogs") as? [[String: Any]] { + do { + catalogs = try JSONDecoder().decode([Catalog].self, from: JSONSerialization.data(withJSONObject: array)) + let catalogTypes: [CatalogType] = catalogs.map { $0.type } + + for catalogType in CatalogType.allCases where !catalogTypes.contains(catalogType) { + let catalog: Catalog = Catalog(type: catalogType, standard: true, customerSeed: false, developerSeed: false, publicSeed: false) + catalogs.append(catalog) + } + } catch { + catalogs = defaultCatalogs + } + } else { + catalogs = defaultCatalogs + } + + for catalog in catalogs { + if catalog.standard { + catalogURLs.append(catalog.type.url(for: .standard)) + } + + if catalog.customerSeed { + catalogURLs.append(catalog.type.url(for: .customer)) + } + + if catalog.developerSeed { + catalogURLs.append(catalog.type.url(for: .developer)) + } + + if catalog.publicSeed { + catalogURLs.append(catalog.type.url(for: .public)) + } + } + + return catalogURLs + } + private func getInstallers(from dictionary: [String: Any]) -> [Installer] { var installers: [Installer] = [] diff --git a/Mist/Views/Settings/SettingsInstallersCatalogsView.swift b/Mist/Views/Settings/SettingsInstallersCatalogsView.swift index 7dc332e..6aa59bb 100644 --- a/Mist/Views/Settings/SettingsInstallersCatalogsView.swift +++ b/Mist/Views/Settings/SettingsInstallersCatalogsView.swift @@ -8,47 +8,81 @@ import SwiftUI struct SettingsInstallersCatalogsView: View { - @Binding var catalogRows: [CatalogRow] - @Binding var selectedCatalogRow: CatalogRow? + @Binding var catalogs: [Catalog] + // swiftlint:disable:next line_length + private let description: String = "Apple Software Update Catalogs are used to determine available macOS Installers.\n\n- **Standard:** The default catalog that ships with macOS\n- **Customer Seed:** The catalog available as part of the [AppleSeed Program](https://appleseed.apple.com/)\n- **Developer Seed:** The catalog available as part of the [Apple Developer Program](https://developer.apple.com/programs/)\n- **Public Seed:** The catalog available as part of the [Apple Beta Software Program](https://beta.apple.com/)\n\n**Note:** Catalogs from the Seed Programs may contain beta / unreleased versions of macOS. Ensure you are a member of these programs before proceeding." + private let height: CGFloat = 120 + private let width: CGFloat = 150 private let length: CGFloat = 16 - private let height: CGFloat = 200 var body: some View { VStack(alignment: .leading) { - Text("Catalog URLs:") - FooterText("Apple Software Update Catalogs are used to determine all available macOS Installers.") - List(selection: $selectedCatalogRow) { - ForEach($catalogRows) { catalogRow in - HStack { - ScaledSystemImage(systemName: "line.3.horizontal", length: length) - .foregroundColor(.secondary) - TextEditor(text: catalogRow.url) - } + Text("Software Update Catalogs:") + FooterText(description) + Table(catalogs) { + TableColumn("") { catalog in + ScaledImage(name: catalog.type.imageName, length: length) } - .onMove { indexSet, offset in - catalogRows.move(fromOffsets: indexSet, toOffset: offset) + .width(length) + TableColumn("Catalog Type") { catalog in + Text(catalog.type.description) } - .onDelete { indexSet in - catalogRows.remove(atOffsets: indexSet) - } - } - .frame(minHeight: height) - HStack { - Spacer() - Button("Add") { - addCatalog() + .width(width) + TableColumn(CatalogSeedType.standard.description) { catalog in + toggle(.standard, using: catalog) + } + TableColumn(CatalogSeedType.customer.description) { catalog in + toggle(.customer, using: catalog) + } + TableColumn(CatalogSeedType.developer.description) { catalog in + toggle(.developer, using: catalog) + } + TableColumn(CatalogSeedType.public.description) { catalog in + toggle(.public, using: catalog) } } + .tableStyle(.bordered) + .frame(minHeight: height, maxHeight: height) } } - private func addCatalog() { - catalogRows.append(CatalogRow(url: "https://")) + private func toggle(_ catalogSeedType: CatalogSeedType, using catalog: Catalog) -> some View { + Toggle(catalogSeedType.description, isOn: Binding( + get: { + switch catalogSeedType { + case .standard: + return catalog.standard + case .customer: + return catalog.customerSeed + case .developer: + return catalog.developerSeed + case .public: + return catalog.publicSeed + } + }, + set: { + guard let index: Int = catalogs.firstIndex(where: { $0.id == catalog.id }) else { + return + } + + switch catalogSeedType { + case .standard: + catalogs[index].standard = $0 + case .customer: + catalogs[index].customerSeed = $0 + case .developer: + catalogs[index].developerSeed = $0 + case .public: + catalogs[index].publicSeed = $0 + } + } + )) + .labelsHidden() } } struct SettingsInstallersCatalogsView_Previews: PreviewProvider { static var previews: some View { - SettingsInstallersCatalogsView(catalogRows: .constant([.example]), selectedCatalogRow: .constant(.example)) + SettingsInstallersCatalogsView(catalogs: .constant([.example])) } } diff --git a/Mist/Views/Settings/SettingsInstallersView.swift b/Mist/Views/Settings/SettingsInstallersView.swift index e9cf193..cbaa917 100644 --- a/Mist/Views/Settings/SettingsInstallersView.swift +++ b/Mist/Views/Settings/SettingsInstallersView.swift @@ -10,11 +10,10 @@ import SwiftUI struct SettingsInstallersView: View { @AppStorage("cacheDownloads") private var cacheDownloads: Bool = false @AppStorage("cacheDirectory") private var cacheDirectory: String = .cacheDirectory - @State private var catalogRows: [CatalogRow] = [] - @State private var selectedCatalogRow: CatalogRow? + @State private var catalogs: [Catalog] = [] private let cacheDownloadsDefault: Bool = false private let cacheDirectoryDefault: String = .cacheDirectory - private var defaultCatalogRows: [CatalogRow] = Catalog.urls.map { CatalogRow(url: $0) } + private let defaultCatalogs: [Catalog] = CatalogType.allCases.map { Catalog(type: $0, standard: true, customerSeed: false, developerSeed: false, publicSeed: false) } private let imageName: String = "Installer" private let title: String = "Installers" private let description: String = "macOS Installers are a collection of files that can be used to build macOS Installer **Applications**, **Disk Images**, **ISOs** and **Packages**." @@ -25,7 +24,7 @@ struct SettingsInstallersView: View { PaddedDivider() SettingsInstallersCacheView(cacheDownloads: $cacheDownloads, cacheDirectory: $cacheDirectory) PaddedDivider() - SettingsInstallersCatalogsView(catalogRows: $catalogRows, selectedCatalogRow: $selectedCatalogRow) + SettingsInstallersCatalogsView(catalogs: $catalogs) PaddedDivider() ResetToDefaultButton { reset() @@ -33,27 +32,38 @@ struct SettingsInstallersView: View { } .padding() .onAppear { - populateCatalogURLs() + catalogs = getCatalogs() } - .onChange(of: catalogRows) { catalogRows in - UserDefaults.standard.setValue(catalogRows.map { $0.url }, forKey: "catalogURLs") + .onChange(of: catalogs) { catalogs in + UserDefaults.standard.setValue(catalogs.map { $0.dictionary() }, forKey: "catalogs") } } - private func populateCatalogURLs() { + private func getCatalogs() -> [Catalog] { - guard let urls: [String] = UserDefaults.standard.array(forKey: "catalogURLs") as? [String] else { - catalogRows = defaultCatalogRows - return + guard let array: [[String: Any]] = UserDefaults.standard.array(forKey: "catalogs") as? [[String: Any]] else { + return defaultCatalogs } - catalogRows = urls.map { CatalogRow(url: $0) } + do { + var catalogs: [Catalog] = try JSONDecoder().decode([Catalog].self, from: JSONSerialization.data(withJSONObject: array)) + let catalogTypes: [CatalogType] = catalogs.map { $0.type } + + for catalogType in CatalogType.allCases where !catalogTypes.contains(catalogType) { + let catalog: Catalog = Catalog(type: catalogType, standard: true, customerSeed: false, developerSeed: false, publicSeed: false) + catalogs.append(catalog) + } + + return catalogs.sorted { $0.type < $1.type } + } catch { + return defaultCatalogs + } } private func reset() { cacheDownloads = cacheDownloadsDefault cacheDirectory = cacheDirectoryDefault - catalogRows = defaultCatalogRows + catalogs = defaultCatalogs } }