From 00cb819b2c49e052767e004aa9f00289a69a0e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1rady=20Mil=C3=A1n?= <61704770+MilanVarady@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:15:47 +0100 Subject: [PATCH] Update to Swift 6 Refactor the Cask view model for Swift 6 compatibility --- Applite.xcodeproj/project.pbxproj | 10 ++- .../Cask Models/Cask/Cask+BrewFunctions.swift | 40 +++++++----- .../Cask Models/Cask/Cask+LaunchApp.swift | 4 +- .../Cask/Cask+ProtocolConformances.swift | 4 ++ Applite/Model/Cask Models/Cask/Cask.swift | 58 ++++++----------- Applite/Model/Cask Models/CaskData.swift | 47 +++++++------- Applite/Model/Cask Models/CaskInfo.swift | 46 ++++++++++++++ .../Brew Installation/DependencyManager.swift | 4 +- .../Utilities/Import Export/ExportCasks.swift | 2 +- .../Utilities/Other/SendNotification.swift | 63 ++++++++++--------- .../App View/AppView+DownloadButton.swift | 8 +-- .../AppView+IconAndDescriptionView.swift | 10 +-- .../Views/App Views/App View/AppView.swift | 2 +- Applite/Views/App Views/AppGridView.swift | 2 +- .../DiscoverSectionView+AppRow.swift | 6 +- .../DiscoverSectionView+OffsetKey.swift | 2 +- .../Download/DownloadView+Search.swift | 6 +- .../Views/Detail Views/InstalledView.swift | 2 +- .../Detail Views/Update/UpdateView.swift | 2 +- 19 files changed, 183 insertions(+), 135 deletions(-) create mode 100644 Applite/Model/Cask Models/CaskInfo.swift diff --git a/Applite.xcodeproj/project.pbxproj b/Applite.xcodeproj/project.pbxproj index 1aa7090..9ec12a0 100644 --- a/Applite.xcodeproj/project.pbxproj +++ b/Applite.xcodeproj/project.pbxproj @@ -94,6 +94,7 @@ 4192566B2D1F286B00D9EF10 /* Cask+BrewFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4192566A2D1F286B00D9EF10 /* Cask+BrewFunctions.swift */; }; 4192566E2D1F293700D9EF10 /* Cask+LaunchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4192566D2D1F293700D9EF10 /* Cask+LaunchApp.swift */; }; 419256702D1F299E00D9EF10 /* Cask+ProtocolConformances.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4192566F2D1F299E00D9EF10 /* Cask+ProtocolConformances.swift */; }; + 419256832D22055200D9EF10 /* CaskInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419256822D22055200D9EF10 /* CaskInfo.swift */; }; 419506A42964A27F00FE5802 /* SetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419506A32964A27F00FE5802 /* SetupView.swift */; }; 419506A62964A5EF00FE5802 /* BrewPathSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419506A52964A5EF00FE5802 /* BrewPathSelectorView.swift */; }; 4196C8F528F9CB2600EADDDA /* DiscoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4196C8F428F9CB2600EADDDA /* DiscoverView.swift */; }; @@ -192,6 +193,7 @@ 4192566A2D1F286B00D9EF10 /* Cask+BrewFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cask+BrewFunctions.swift"; sourceTree = ""; }; 4192566D2D1F293700D9EF10 /* Cask+LaunchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cask+LaunchApp.swift"; sourceTree = ""; }; 4192566F2D1F299E00D9EF10 /* Cask+ProtocolConformances.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cask+ProtocolConformances.swift"; sourceTree = ""; }; + 419256822D22055200D9EF10 /* CaskInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaskInfo.swift; sourceTree = ""; }; 419506A32964A27F00FE5802 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = ""; }; 419506A52964A5EF00FE5802 /* BrewPathSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewPathSelectorView.swift; sourceTree = ""; }; 419506A729696A5300FE5802 /* Applite-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Applite-Info.plist"; sourceTree = SOURCE_ROOT; }; @@ -369,8 +371,9 @@ children = ( 419256692D1F284100D9EF10 /* Cask */, 418F332528EC921D0023D76F /* CaskData.swift */, - 4166EE7C28F73B2300CE305A /* BrewAnalytics.swift */, + 419256822D22055200D9EF10 /* CaskInfo.swift */, 4191392B29159B5C00F1D75D /* CaskDTO.swift */, + 4166EE7C28F73B2300CE305A /* BrewAnalytics.swift */, ); path = "Cask Models"; sourceTree = ""; @@ -749,6 +752,7 @@ 413E60C22BBFF98A00978F6A /* AppIconView.swift in Sources */, 418989AD2A33A5C4004AC23B /* BrewManagementView.swift in Sources */, 418989B22A35D651004AC23B /* isBrewPathValid.swift in Sources */, + 419256832D22055200D9EF10 /* CaskInfo.swift in Sources */, 4192560D2D1CA02C00D9EF10 /* ShellError.swift in Sources */, 4192566B2D1F286B00D9EF10 /* Cask+BrewFunctions.swift in Sources */, 4192561A2D1DEB2100D9EF10 /* AppView+OpenAndManageView.swift in Sources */, @@ -931,7 +935,7 @@ PRODUCT_BUNDLE_IDENTIFIER = dev.aerolite.Applite; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -966,7 +970,7 @@ PRODUCT_BUNDLE_IDENTIFIER = dev.aerolite.Applite; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Release; }; diff --git a/Applite/Model/Cask Models/Cask/Cask+BrewFunctions.swift b/Applite/Model/Cask Models/Cask/Cask+BrewFunctions.swift index a4286de..407d6ce 100644 --- a/Applite/Model/Cask Models/Cask/Cask+BrewFunctions.swift +++ b/Applite/Model/Cask Models/Cask/Cask+BrewFunctions.swift @@ -18,7 +18,7 @@ extension Cask { resetProgressState(caskData: caskData) } - Self.logger.info("Cask \"\(self.id)\" installation started") + Self.logger.info("Cask \"\(self.info.id)\" installation started") // Appdir argument let appdirOn = UserDefaults.standard.bool(forKey: Preferences.appdirOn.rawValue) @@ -45,7 +45,7 @@ extension Cask { let alertMessage = switch completeOutput { // Already installed case _ where completeOutput.contains("It seems there is already an App"): - String(localized: "\(self.name) is already installed. If you want to add it to \(Bundle.main.appName) click more options (chevron icon) and press Force Install.") + String(localized: "\(self.info.name) is already installed. If you want to add it to \(Bundle.main.appName) click more options (chevron icon) and press Force Install.") // Network error case _ where completeOutput.contains("Could not resolve host"): String(localized: "Couldn't download app. No internet connection, or host is unreachable.") @@ -56,7 +56,7 @@ extension Cask { showFailure( error: error, output: completeOutput, - alertTitle: "Failed to install \(self.name)", + alertTitle: "Failed to install \(self.info.name)", alertMessage: alertMessage ) return @@ -64,7 +64,7 @@ extension Cask { showSuccess( logMessage: "Successfully installed cask \(self.id)", - alertTitle: "\(self.name) successfully installed!" + alertTitle: "\(self.info.name) successfully installed!" ) // Update state @@ -83,7 +83,7 @@ extension Cask { progressState = .busy(withTask: "Uninstalling") caskData.busyCasks.insert(self) - var arguments: [String] = [self.id] + var arguments: [String] = [self.info.id] // Add -- zap argument if zap { @@ -98,15 +98,15 @@ extension Cask { showFailure( error: error, output: output, - alertTitle: "Failed to uninstall \(self.name)", + alertTitle: "Failed to uninstall \(self.info.name)", alertMessage: error.localizedDescription ) return } showSuccess( - logMessage: "Successfully uninstalled \(self.id)", - alertTitle: "\(self.name) successfully uninstalled" + logMessage: "Successfully uninstalled \(self.info.id)", + alertTitle: "\(self.info.name) successfully uninstalled" ) // Update state @@ -125,12 +125,12 @@ extension Cask { var output: String = "" do { - output = try await Shell.runBrewCommand("uninstall", arguments: [self.id]) + output = try await Shell.runBrewCommand("uninstall", arguments: [self.info.id]) } catch { showFailure( error: error, output: output, - alertTitle: "Failed to update \(self.name)", + alertTitle: "Failed to update \(self.info.name)", alertMessage: error.localizedDescription ) return @@ -138,7 +138,7 @@ extension Cask { showSuccess( logMessage: "Successfully updated \(self.id)", - alertTitle: "\(self.name) successfully updated" + alertTitle: "\(self.info.name) successfully updated" ) // Update state @@ -157,20 +157,20 @@ extension Cask { var output: String = "" do { - output = try await Shell.runBrewCommand("uninstall", arguments: [self.id]) + output = try await Shell.runBrewCommand("uninstall", arguments: [self.info.id]) } catch { showFailure( error: error, output: output, - alertTitle: "Failed to reinstall \(self.name)", + alertTitle: "Failed to reinstall \(self.info.name)", alertMessage: error.localizedDescription ) return } showSuccess( - logMessage: "Successfully reinstalled \(self.id)", - alertTitle: "\(self.name) successfully reinstalled" + logMessage: "Successfully reinstalled \(self.info.id)", + alertTitle: "\(self.info.name) successfully reinstalled" ) } @@ -208,7 +208,10 @@ extension Cask { alertMessage: String = "" ) { Self.logger.info("\(logMessage)") - sendNotification(title: alertTitle, body: alertMessage, reason: .success) + + Task { + await sendNotification(title: alertTitle, body: alertMessage, reason: .success) + } // Show success for 2 seconds progressState = .success @@ -239,7 +242,10 @@ extension Cask { // Send notification let notificationTitle = notificationTitle ?? alertTitle - sendNotification(title: notificationTitle, body: notificationMessage, reason: .failure) + + Task { + await sendNotification(title: notificationTitle, body: notificationMessage, reason: .failure) + } // Set progress state to failed progressState = .failed(output: output) diff --git a/Applite/Model/Cask Models/Cask/Cask+LaunchApp.swift b/Applite/Model/Cask Models/Cask/Cask+LaunchApp.swift index 8396454..084b85f 100644 --- a/Applite/Model/Cask Models/Cask/Cask+LaunchApp.swift +++ b/Applite/Model/Cask Models/Cask/Cask+LaunchApp.swift @@ -11,7 +11,7 @@ extension Cask { func launchApp() async throws { let appPath: String - if self.pkgInstaller { + if self.info.pkgInstaller { // Open PKG type app var applicationsDirectory = "/Applications" @@ -25,7 +25,7 @@ extension Cask { } } - appPath = "\"\(applicationsDirectory)/\(self.name).app\"" + appPath = "\"\(applicationsDirectory)/\(self.info.name).app\"" } else { // Open normal app let brewDirectory = BrewPaths.currentBrewDirectory diff --git a/Applite/Model/Cask Models/Cask/Cask+ProtocolConformances.swift b/Applite/Model/Cask Models/Cask/Cask+ProtocolConformances.swift index 944abf5..d894c97 100644 --- a/Applite/Model/Cask Models/Cask/Cask+ProtocolConformances.swift +++ b/Applite/Model/Cask Models/Cask/Cask+ProtocolConformances.swift @@ -8,6 +8,10 @@ import Foundation extension Cask { + nonisolated var id: String { + self.info.id + } + // Equatable nonisolated static func == (lhs: Cask, rhs: Cask) -> Bool { lhs.id == rhs.id diff --git a/Applite/Model/Cask Models/Cask/Cask.swift b/Applite/Model/Cask Models/Cask/Cask.swift index 3960d1c..6a1077a 100755 --- a/Applite/Model/Cask Models/Cask/Cask.swift +++ b/Applite/Model/Cask Models/Cask/Cask.swift @@ -8,25 +8,15 @@ import SwiftUI import os -/// Holds all essential data of a Homebrew cask and provides methods to run brew commands on it (e.g. install, uninstall, update) +/// A view model that holds all essential data of a Homebrew cask and provides methods to run brew commands on it (e.g. install, uninstall, update) @MainActor -final class Cask: Identifiable, Decodable, Hashable, ObservableObject { - // MARK: - Static properties +final class Cask: ObservableObject, Identifiable, Hashable { + /// Static cask information + let info: CaskInfo - /// Unique id of the class, this is the same name you would use to download the cask with brew - let id: String - /// Longer format cask name - let name: String - /// Short description - let description: String - let homepageURL: URL? /// Number of downloads in the last 365 days - var downloadsIn365days: Int = 0 - /// Description of any caveats with the app - let caveats: String? - /// If true app has a .pkg installer - let pkgInstaller: Bool - + let downloadsIn365days: Int + // MARK: - Published properties @Published var isInstalled: Bool = false @@ -51,27 +41,19 @@ final class Cask: Identifiable, Decodable, Hashable, ObservableObject { case failed(output: String) } - // MARK: - Initializers - - nonisolated init(from decoder: Decoder) throws { - let rawData = try? CaskDTO(from: decoder) - - let homepage: String = rawData?.homepage ?? "https://brew.sh/" - - self.id = rawData?.token ?? "N/A" - self.name = rawData?.nameArray[0] ?? "N/A" - self.description = rawData?.desc ?? "N/A" - self.homepageURL = URL(string: homepage) - self.caveats = rawData?.caveats - self.pkgInstaller = rawData?.url.hasSuffix("pkg") ?? false - } - - init() { - self.id = "test" - self.name = "Test app" - self.description = "An application to test this application" - self.homepageURL = URL(string: "https://aerolite.dev/") - self.caveats = nil - self.pkgInstaller = false + required init(info: CaskInfo, downloadsIn365days: Int, isInstalled: Bool = false, isOutdated: Bool = false) { + self.info = info + self.downloadsIn365days = downloadsIn365days + self.isInstalled = isInstalled + self.isOutdated = isOutdated } + + static let dummy = Cask(info: CaskInfo( + id: "test", + name: "Test", + description: "Test application", + homepageURL: URL(string: "https://aerolite.dev/"), + caveats: nil, + pkgInstaller: false + ), downloadsIn365days: 100) } diff --git a/Applite/Model/Cask Models/CaskData.swift b/Applite/Model/Cask Models/CaskData.swift index 98ebd88..f371688 100755 --- a/Applite/Model/Cask Models/CaskData.swift +++ b/Applite/Model/Cask Models/CaskData.swift @@ -46,7 +46,7 @@ final class CaskData: ObservableObject { /// Gets cask information from the Homebrew API and decodes it into a list of ``Cask`` objects /// - Returns: List of ``Cask`` objects @Sendable - func loadCaskObjects() async throws -> [Cask] { + func loadCaskInfo() async throws -> [CaskInfo] { // Get json data from api guard let casksURL = URL(string: "https://formulae.brew.sh/api/cask.json") else { return [] } @@ -68,8 +68,8 @@ final class CaskData: ObservableObject { // Chache json file await cacheData(data: caskData, to: Self.caskCacheURL) - // Decode data - return try JSONDecoder().decode([Cask].self, from: caskData) + // Decode static cask data + return try JSONDecoder().decode([CaskInfo].self, from: caskData) } /// Gets cask analytics information from the Homebrew API and decodes it into a dictionary @@ -182,7 +182,7 @@ final class CaskData: ObservableObject { for category in categories { // Filter casks let filteredCasks = casks.filter { - category.casks.contains($0.id) + category.casks.contains($0.info.id) } // Sort by number of downloads @@ -201,31 +201,26 @@ final class CaskData: ObservableObject { } // Get data components concurrently - async let caskData = loadCaskObjects() + async let caskInfo = loadCaskInfo() async let analyticsDict = loadAnalyticsData() async let installedCasks = getInstalledCasks() async let outdatedCaskIDs = getOutdatedCasks() - - // Combine data into a final list of `Cask` objects - do { - for i in try await caskData.indices { - try await caskData[i].downloadsIn365days = try await analyticsDict[try await caskData[i].id] ?? 0 - - if try await installedCasks.contains(try await caskData[i].id) { - try await caskData[i].isInstalled = true - - if try await outdatedCaskIDs.contains(try await caskData[i].id) { - try await caskData[i].isOutdated = true - self.outdatedCasks.insert(try await caskData[i]) - } - } - } - } catch { - Self.logger.error("Error while trying to combine cask data. Message: \(error.localizedDescription)") + + var casks: [Cask] = [] + + for caskInfo in try await caskInfo { + let cask = Cask( + info: caskInfo, + downloadsIn365days: try await analyticsDict[caskInfo.id] ?? 0, + isInstalled: try await installedCasks.contains(caskInfo.id), + isOutdated: try await outdatedCaskIDs.contains(caskInfo.id) + ) + + casks.append(cask) } - - self.casks = try await caskData - + + self.casks = casks + Self.logger.info("Cask data loaded successfully!") // Create category dicts @@ -242,7 +237,7 @@ final class CaskData: ObservableObject { .map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) }) // Trim whitespace for i in self.casks.indices { - if outdatedCaskIDs.contains(self.casks[i].id) && self.casks[i].isInstalled { + if outdatedCaskIDs.contains(self.casks[i].info.id) && self.casks[i].isInstalled { self.casks[i].isOutdated = true outdatedCasks.insert(casks[i]) } diff --git a/Applite/Model/Cask Models/CaskInfo.swift b/Applite/Model/Cask Models/CaskInfo.swift new file mode 100644 index 0000000..7b38199 --- /dev/null +++ b/Applite/Model/Cask Models/CaskInfo.swift @@ -0,0 +1,46 @@ +// +// CaskInfo.swift +// Applite +// +// Created by Milán Várady on 2024.12.29. +// + +import Foundation + +/// Holds all static information of a cask +struct CaskInfo: Codable, Identifiable, Hashable { + /// Unique id of the class, this is the same name you would use to download the cask with brew + let id: String + /// Longer format cask name + let name: String + /// Short description + let description: String + let homepageURL: URL? + /// Description of any caveats with the app + let caveats: String? + /// If true app has a .pkg installer + let pkgInstaller: Bool + + /// Initialize from a ``CaskDTO`` data transfer object + init(from decoder: Decoder) throws { + let rawData = try? CaskDTO(from: decoder) + + let homepage: String = rawData?.homepage ?? "https://brew.sh/" + + self.id = rawData?.token ?? "N/A" + self.name = rawData?.nameArray[0] ?? "N/A" + self.description = rawData?.desc ?? "N/A" + self.homepageURL = URL(string: homepage) + self.caveats = rawData?.caveats + self.pkgInstaller = rawData?.url.hasSuffix("pkg") ?? false + } + + init(id: String, name: String, description: String, homepageURL: URL?, caveats: String?, pkgInstaller: Bool) { + self.id = id + self.name = name + self.description = description + self.homepageURL = homepageURL + self.caveats = caveats + self.pkgInstaller = pkgInstaller + } +} diff --git a/Applite/Utilities/Brew Installation/DependencyManager.swift b/Applite/Utilities/Brew Installation/DependencyManager.swift index 2215f4d..937b9ed 100755 --- a/Applite/Utilities/Brew Installation/DependencyManager.swift +++ b/Applite/Utilities/Brew Installation/DependencyManager.swift @@ -18,8 +18,8 @@ struct DependencyManager { ) /// Message shown when brew path is broken - static var brokenPathOrIstallMessage = "Error. Broken brew path, or damaged installation. Check brew path in settings, or try reinstalling Homebrew (Manage Homebrew->Reinstall)" - + static let brokenPathOrIstallMessage = "Error. Broken brew path, or damaged installation. Check brew path in settings, or try reinstalling Homebrew (Manage Homebrew->Reinstall)" + /// Installs dependencies to `~/Library/Application Support/Applite/homebrew/` /// /// - Parameters: diff --git a/Applite/Utilities/Import Export/ExportCasks.swift b/Applite/Utilities/Import Export/ExportCasks.swift index 0417394..7262031 100644 --- a/Applite/Utilities/Import Export/ExportCasks.swift +++ b/Applite/Utilities/Import Export/ExportCasks.swift @@ -65,7 +65,7 @@ enum CaskToFileManager { } static func installImportedCasks(casks: [String], caskData: CaskData) async { - let casksToInstall: [Cask] = await caskData.casks.filter({ casks.contains($0.id) }) + let casksToInstall: [Cask] = await caskData.casks.filter({ casks.contains($0.info.id) }) await withTaskGroup(of: Void.self) { group in for cask in casksToInstall { diff --git a/Applite/Utilities/Other/SendNotification.swift b/Applite/Utilities/Other/SendNotification.swift index 6bbe319..d0c4031 100755 --- a/Applite/Utilities/Other/SendNotification.swift +++ b/Applite/Utilities/Other/SendNotification.swift @@ -7,6 +7,7 @@ import Foundation import UserNotifications +import OSLog enum NotificationReason { case success @@ -23,37 +24,43 @@ enum NotificationReason { /// - reason: Reason why the notification was sent, task success or failure /// /// - Returns: `Void` -func sendNotification(title: String, body: String = "", reason: NotificationReason) { +func sendNotification(title: String, body: String = "", reason: NotificationReason) async { + let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "sendNotification") + let center = UNUserNotificationCenter.current() - - center.getNotificationSettings { settings in - guard (settings.authorizationStatus == .authorized) || - (settings.authorizationStatus == .provisional) else { - + let settings = await center.notificationSettings() + + guard (settings.authorizationStatus == .authorized) || + (settings.authorizationStatus == .provisional) else { + // Ask for authorization - Task { - try await center.requestAuthorization(options: [.alert, .sound, .badge]) - } - return - } - - /// Return if notifications are desabled for selected reason - if (!UserDefaults.standard.bool(forKey: "notificationSuccess") && reason == .success) - || (!UserDefaults.standard.bool(forKey: "notificationFailure") && reason == .failure) { - return + do { + try await center.requestAuthorization(options: [.alert, .sound, .badge]) + } catch { + logger.error("Failed to request notification authorization. Error: \(error.localizedDescription)") } + + return + } - let content = UNMutableNotificationContent() - - content.title = title - content.body = body - content.sound = .default - - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) - let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) - - Task { - try await UNUserNotificationCenter.current().add(request) - } + /// Return if notifications are desabled for selected reason + if (!UserDefaults.standard.bool(forKey: "notificationSuccess") && reason == .success) + || (!UserDefaults.standard.bool(forKey: "notificationFailure") && reason == .failure) { + return + } + + let content = UNMutableNotificationContent() + + content.title = title + content.body = body + content.sound = .default + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) + let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) + + do { + try await UNUserNotificationCenter.current().add(request) + } catch { + logger.error("Failed to send notication. Error: \(error.localizedDescription)") } } diff --git a/Applite/Views/App Views/App View/AppView+DownloadButton.swift b/Applite/Views/App Views/App View/AppView+DownloadButton.swift index 9dd534f..ca96c5b 100644 --- a/Applite/Views/App Views/App View/AppView+DownloadButton.swift +++ b/Applite/Views/App Views/App View/AppView+DownloadButton.swift @@ -25,7 +25,7 @@ extension AppView { var body: some View { /// Download button Button { - if cask.caveats != nil { + if cask.info.caveats != nil { // Show caveats dialog showingCaveats = true return @@ -51,7 +51,7 @@ extension AppView { Button("Cancel", role: .cancel) { } } message: { - Text(cask.caveats ?? "") + Text(cask.info.caveats ?? "") } .alert("Broken Brew Path", isPresented: $showingBrewError) {} message: { Text(DependencyManager.brokenPathOrIstallMessage) @@ -68,7 +68,7 @@ extension AppView { .popover(isPresented: $showingPopover) { VStack(alignment: .leading, spacing: 6) { // Open homepage - if let homepageLink = cask.homepageURL { + if let homepageLink = cask.info.homepageURL { Link(destination: homepageLink, label: { Label("Homepage", systemImage: "house") }) @@ -88,7 +88,7 @@ extension AppView { .padding(8) .buttonStyle(.plain) } - .confirmationDialog("Are you sure you want to force install \(cask.name)? This will override any current installation!", isPresented: $showingForceInstallConfirmation) { + .confirmationDialog("Are you sure you want to force install \(cask.info.name)? This will override any current installation!", isPresented: $showingForceInstallConfirmation) { Button("Yes") { download(force: true) } diff --git a/Applite/Views/App Views/App View/AppView+IconAndDescriptionView.swift b/Applite/Views/App Views/App View/AppView+IconAndDescriptionView.swift index d14b377..7d4235f 100644 --- a/Applite/Views/App Views/App View/AppView+IconAndDescriptionView.swift +++ b/Applite/Views/App Views/App View/AppView+IconAndDescriptionView.swift @@ -10,22 +10,22 @@ import SwiftUI extension AppView { var iconAndDescriptionView: some View { return HStack { - if let iconURL = URL(string: "https://github.com/App-Fair/appcasks/releases/download/cask-\(cask.id)/AppIcon.png"), - let faviconURL = URL(string: "https://icon.horse/icon/\(cask.homepageURL?.host ?? "")") { + if let iconURL = URL(string: "https://github.com/App-Fair/appcasks/releases/download/cask-\(cask.info.id)/AppIcon.png"), + let faviconURL = URL(string: "https://icon.horse/icon/\(cask.info.homepageURL?.host ?? "")") { AppIconView( iconURL: iconURL, faviconURL: faviconURL, - cacheKey: cask.id + cacheKey: cask.info.id ) .padding(.leading, 5) } // Name and description VStack(alignment: .leading) { - Text(cask.name) + Text(cask.info.name) .font(.system(size: 16, weight: .bold)) - Text(cask.description) + Text(cask.info.description) .foregroundColor(.secondary) } diff --git a/Applite/Views/App Views/App View/AppView.swift b/Applite/Views/App Views/App View/AppView.swift index 49c336c..4398b68 100755 --- a/Applite/Views/App Views/App View/AppView.swift +++ b/Applite/Views/App Views/App View/AppView.swift @@ -53,6 +53,6 @@ struct AppView: View { struct AppView_Previews: PreviewProvider { static var previews: some View { - AppView(cask: Cask(), role: .installAndManage) + AppView(cask: Cask.dummy, role: .installAndManage) } } diff --git a/Applite/Views/App Views/AppGridView.swift b/Applite/Views/App Views/AppGridView.swift index ddc3efd..9fa10a0 100755 --- a/Applite/Views/App Views/AppGridView.swift +++ b/Applite/Views/App Views/AppGridView.swift @@ -28,7 +28,7 @@ struct AppGridView: View { ForEach(casks) { cask in // Filter out self - if cask.id != "applite" { + if cask.info.id != "applite" { AppView(cask: cask, role: appRole) } } diff --git a/Applite/Views/Detail Views/Discover/Discover Section/DiscoverSectionView+AppRow.swift b/Applite/Views/Detail Views/Discover/Discover Section/DiscoverSectionView+AppRow.swift index f0de471..ca11711 100644 --- a/Applite/Views/Detail Views/Discover/Discover Section/DiscoverSectionView+AppRow.swift +++ b/Applite/Views/Detail Views/Discover/Discover Section/DiscoverSectionView+AppRow.swift @@ -60,7 +60,11 @@ extension DiscoverSectionView { Color.clear.preference(key: ViewOffsetKey.self, value: -geometry.frame(in: .named("\(category.id)Scroll")).origin.x) }) - .onPreferenceChange(ViewOffsetKey.self) { scrollOffset = $0 } + .onPreferenceChange(ViewOffsetKey.self) { value in + Task { @MainActor in + scrollOffset = value + } + } } } diff --git a/Applite/Views/Detail Views/Discover/Discover Section/DiscoverSectionView+OffsetKey.swift b/Applite/Views/Detail Views/Discover/Discover Section/DiscoverSectionView+OffsetKey.swift index 91c28bf..be8a223 100644 --- a/Applite/Views/Detail Views/Discover/Discover Section/DiscoverSectionView+OffsetKey.swift +++ b/Applite/Views/Detail Views/Discover/Discover Section/DiscoverSectionView+OffsetKey.swift @@ -11,7 +11,7 @@ extension DiscoverSectionView { /// Preference key used to get the scroll offset of the app row struct ViewOffsetKey: PreferenceKey { typealias Value = CGFloat - static var defaultValue = CGFloat.zero + static let defaultValue = CGFloat.zero static func reduce(value: inout Value, nextValue: () -> Value) { value += nextValue() diff --git a/Applite/Views/Detail Views/Download/DownloadView+Search.swift b/Applite/Views/Detail Views/Download/DownloadView+Search.swift index a433c6d..086fb36 100644 --- a/Applite/Views/Detail Views/Download/DownloadView+Search.swift +++ b/Applite/Views/Detail Views/Download/DownloadView+Search.swift @@ -22,9 +22,9 @@ extension DownloadView { } else { // A score of 0 means a perfect match, a score of one matches everything casks = caskData.casks.filter { - ($0.name.lowercased().contains(searchText.lowercased()) || $0.description.lowercased().contains(searchText.lowercased())) || - (fuseSearch.search(searchText.lowercased(), in: $0.name.lowercased())?.score ?? 1) < 0.25 || - (fuseSearch.search(searchText.lowercased(), in: $0.description.lowercased())?.score ?? 1) < 0.25 + ($0.info.name.lowercased().contains(searchText.lowercased()) || $0.info.description.lowercased().contains(searchText.lowercased())) || + (fuseSearch.search(searchText.lowercased(), in: $0.info.name.lowercased())?.score ?? 1) < 0.25 || + (fuseSearch.search(searchText.lowercased(), in: $0.info.description.lowercased())?.score ?? 1) < 0.25 } } diff --git a/Applite/Views/Detail Views/InstalledView.swift b/Applite/Views/Detail Views/InstalledView.swift index 8dedae6..cf933cb 100755 --- a/Applite/Views/Detail Views/InstalledView.swift +++ b/Applite/Views/Detail Views/InstalledView.swift @@ -31,7 +31,7 @@ struct InstalledView: View { if !$searchText.wrappedValue.isEmpty { filteredCasks = filteredCasks.filter { - (fuseSearch.search(searchText, in: $0.name)?.score ?? 1) < 0.4 + (fuseSearch.search(searchText, in: $0.info.name)?.score ?? 1) < 0.4 } } diff --git a/Applite/Views/Detail Views/Update/UpdateView.swift b/Applite/Views/Detail Views/Update/UpdateView.swift index 45c9249..a3f8207 100755 --- a/Applite/Views/Detail Views/Update/UpdateView.swift +++ b/Applite/Views/Detail Views/Update/UpdateView.swift @@ -27,7 +27,7 @@ struct UpdateView: View { if !$searchText.wrappedValue.isEmpty { filteredCasks = filteredCasks.filter { - (fuseSearch.search(searchText, in: $0.name)?.score ?? 1) < 0.4 + (fuseSearch.search(searchText, in: $0.info.name)?.score ?? 1) < 0.4 } }