diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index b80dae84..b779cac3 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ 23148E302EE1CCE500F0DB2C /* MeshtasticAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23148E2F2EE1CCE500F0DB2C /* MeshtasticAPI.swift */; }; 2315D19D2EECB3DA00E0FAE7 /* UTI+UF2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2315D19C2EECB3D400E0FAE7 /* UTI+UF2.swift */; }; 2315D1A02EECB44800E0FAE7 /* UF2MassStorageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2315D19F2EECB44800E0FAE7 /* UF2MassStorageView.swift */; }; - 2315D1A22EECD2CB00E0FAE7 /* APIStructs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2315D1A12EECD2BE00E0FAE7 /* APIStructs.swift */; }; 2315D1A52EED94E800E0FAE7 /* FirmwareFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2315D1A42EED94E800E0FAE7 /* FirmwareFile.swift */; }; 2315D1A82EEF2ED400E0FAE7 /* SwiftDraw in Frameworks */ = {isa = PBXBuildFile; productRef = 2315D1A72EEF2ED400E0FAE7 /* SwiftDraw */; }; 231A53782E69ADB900216B99 /* NodeFilterParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231A53772E69ADB900216B99 /* NodeFilterParameters.swift */; }; @@ -361,7 +360,6 @@ 23148E2F2EE1CCE500F0DB2C /* MeshtasticAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAPI.swift; sourceTree = ""; }; 2315D19C2EECB3D400E0FAE7 /* UTI+UF2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UTI+UF2.swift"; sourceTree = ""; }; 2315D19F2EECB44800E0FAE7 /* UF2MassStorageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UF2MassStorageView.swift; sourceTree = ""; }; - 2315D1A12EECD2BE00E0FAE7 /* APIStructs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIStructs.swift; sourceTree = ""; }; 2315D1A42EED94E800E0FAE7 /* FirmwareFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirmwareFile.swift; sourceTree = ""; }; 231A53772E69ADB900216B99 /* NodeFilterParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeFilterParameters.swift; sourceTree = ""; }; 231B3F1F2D087A4C0069A07D /* MetricsColumnList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricsColumnList.swift; sourceTree = ""; }; @@ -820,7 +818,6 @@ 23148E2E2EE1CCB100F0DB2C /* API */ = { isa = PBXGroup; children = ( - 2315D1A12EECD2BE00E0FAE7 /* APIStructs.swift */, 23DC51382EE76DA20023838A /* Helpers */, 23148E2F2EE1CCE500F0DB2C /* MeshtasticAPI.swift */, ); @@ -2092,7 +2089,6 @@ B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */, 232ED4C32E2C5E89009DA392 /* TCPTransport.swift in Sources */, BC6B45FF2CB2F98900723CEB /* SaveChannelSettingsIntent.swift in Sources */, - 2315D1A22EECD2CB00E0FAE7 /* APIStructs.swift in Sources */, D93068D72B8146690066FBC8 /* MessageText.swift in Sources */, DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */, ); diff --git a/Meshtastic/API/APIStructs.swift b/Meshtastic/API/APIStructs.swift deleted file mode 100644 index 0147a595..00000000 --- a/Meshtastic/API/APIStructs.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// APIStructs.swift -// Meshtastic -// -// Created by jake on 12/12/25. -// - -/// Device Hardware API -struct DeviceHardware: Codable { - let hwModel: Int - let hwModelSlug: String - let platformioTarget: String - let architecture: Architecture - let activelySupported: Bool - let displayName: String - let supportLevel: Int? - let tags: [String]? - let images: [String]? - let requiresDfu: Bool? - let hasInkHud: Bool? - let partitionScheme: String? - let hasMui: Bool? -} -enum Architecture: String, Codable, Identifiable { - case esp32 = "esp32" - case esp32C3 = "esp32-c3" - case esp32S3 = "esp32-s3" - case nrf52840 = "nrf52840" - case rp2040 = "rp2040" - case esp32C6 = "esp32-c6" - - var id: String { rawValue } -} - -/// Firmware Release Lists -struct FirmwareReleases: Codable { - let releases: Releases - let pullRequests: [FirmwareRelease] -} -struct Releases: Codable { - let stable, alpha: [FirmwareRelease] -} -struct FirmwareRelease: Codable { - let id, title: String - let pageURL: String - let zipURL: String - let releaseNotes: String - - enum CodingKeys: String, CodingKey { - case id, title - case pageURL = "page_url" - case zipURL = "zip_url" - case releaseNotes = "release_notes" - } - - enum ReleaseType: String { - case stable = "Stable" - case alpha = "Alpha" - case unlisted = "Unlisted" - } -} diff --git a/Meshtastic/API/MeshtasticAPI.swift b/Meshtastic/API/MeshtasticAPI.swift index a2d41171..5b58bb06 100644 --- a/Meshtastic/API/MeshtasticAPI.swift +++ b/Meshtastic/API/MeshtasticAPI.swift @@ -10,6 +10,65 @@ import OSLog import SwiftUI import CoreData +// These structs are public becase tehy are used elsewhere in the app to represent +// fields in the Core Data database. +enum ReleaseType: String { + case stable = "Stable" + case alpha = "Alpha" + case unlisted = "Unlisted" +} + +enum Architecture: String, Codable, Identifiable { + case esp32 = "esp32" + case esp32C3 = "esp32-c3" + case esp32S3 = "esp32-s3" + case nrf52840 = "nrf52840" + case rp2040 = "rp2040" + case esp32C6 = "esp32-c6" + + var id: String { rawValue } +} + +// These structs are private because they are only used for decoding API responses. +// The rest of the app should be using Core Data entities. +private struct DeviceHardware: Codable { + let hwModel: Int + let hwModelSlug: String + let platformioTarget: String + let architecture: Architecture + let activelySupported: Bool + let displayName: String + let supportLevel: Int? + let tags: [String]? + let images: [String]? + let requiresDfu: Bool? + let hasInkHud: Bool? + let partitionScheme: String? + let hasMui: Bool? +} + +/// Firmware Release Lists +private struct FirmwareReleases: Codable { + let releases: Releases + let pullRequests: [FirmwareRelease] +} +private struct Releases: Codable { + let stable, alpha: [FirmwareRelease] +} +private struct FirmwareRelease: Codable { + let id, title: String + let pageURL: String + let zipURL: String + let releaseNotes: String + + enum CodingKeys: String, CodingKey { + case id, title + case pageURL = "page_url" + case zipURL = "zip_url" + case releaseNotes = "release_notes" + } +} + extension MeshtasticAPI { enum MeshtasticAPIError: Error, LocalizedError { case timedOut(TimeInterval) @@ -217,7 +276,7 @@ class MeshtasticAPI: ObservableObject, @unchecked Sendable { } - private func processFirmware(release: FirmwareRelease, releaseType: FirmwareRelease.ReleaseType) async { + private func processFirmware(release: FirmwareRelease, releaseType: ReleaseType) async { let context = container.newBackgroundContext() await context.perform { @@ -377,11 +436,11 @@ class MeshtasticAPI: ObservableObject, @unchecked Sendable { // Helper to build compound predicate for firmware deletion (selects orphans) static func firmwareCompoundPredicate(stableVersions: Set, alphaVersions: Set) -> NSPredicate { let stablePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [ - NSPredicate(format: "releaseType == %@", FirmwareRelease.ReleaseType.stable.rawValue), + NSPredicate(format: "releaseType == %@", ReleaseType.stable.rawValue), NSPredicate(format: "NOT (versionId IN %@)", stableVersions) ]) let alphaPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [ - NSPredicate(format: "releaseType == %@", FirmwareRelease.ReleaseType.alpha.rawValue), + NSPredicate(format: "releaseType == %@", ReleaseType.alpha.rawValue), NSPredicate(format: "NOT (versionId IN %@)", alphaVersions) ]) return NSCompoundPredicate(orPredicateWithSubpredicates: [stablePredicate, alphaPredicate]) diff --git a/Meshtastic/Model/Firmware/FirmwareFile.swift b/Meshtastic/Model/Firmware/FirmwareFile.swift index acb9c1b0..d23e0a7c 100644 --- a/Meshtastic/Model/Firmware/FirmwareFile.swift +++ b/Meshtastic/Model/Firmware/FirmwareFile.swift @@ -70,7 +70,7 @@ class FirmwareFile: ObservableObject, Hashable, Equatable { let remoteUrl: URL? let versionId: String let platformioTarget: String - let releaseType: FirmwareRelease.ReleaseType + let releaseType: ReleaseType @Published var status: DownloadStatus let firmwareType: FirmwareType let architecure: Architecture @@ -103,17 +103,17 @@ class FirmwareFile: ObservableObject, Hashable, Equatable { // Thread safe operation to get the versionf`rom the given FirmwareReleaseEntity var version: String? - var releaseType: FirmwareRelease.ReleaseType? + var releaseType: ReleaseType? var releaseNotes: String? if let context = firmware.managedObjectContext { context.performAndWait { version = firmware.versionId - releaseType = firmware.releaseType.flatMap { FirmwareRelease.ReleaseType(rawValue: $0) } + releaseType = firmware.releaseType.flatMap { ReleaseType(rawValue: $0) } releaseNotes = firmware.releaseNotes } } else { version = firmware.versionId - releaseType = firmware.releaseType.flatMap { FirmwareRelease.ReleaseType(rawValue: $0) } + releaseType = firmware.releaseType.flatMap { ReleaseType(rawValue: $0) } releaseNotes = firmware.releaseNotes } @@ -235,14 +235,14 @@ class FirmwareFile: ObservableObject, Hashable, Equatable { self.architecure = architecture // Determine release type - var releaseType: FirmwareRelease.ReleaseType = .unlisted + var releaseType: ReleaseType = .unlisted var releaseNotes: String? context.performAndWait { let firmwareFetchRequest = FirmwareReleaseEntity.fetchRequest() firmwareFetchRequest.predicate = NSPredicate(format: "versionId == %@", version) firmwareFetchRequest.fetchLimit = 1 if let firmware = try? context.fetch(firmwareFetchRequest).first { - releaseType = firmware.releaseType.flatMap { FirmwareRelease.ReleaseType(rawValue: $0) } ?? .unlisted + releaseType = firmware.releaseType.flatMap { ReleaseType(rawValue: $0) } ?? .unlisted releaseNotes = firmware.releaseNotes } } diff --git a/Meshtastic/Model/Firmware/FirmwareViewModel.swift b/Meshtastic/Model/Firmware/FirmwareViewModel.swift index 40b1701b..1f88cb5e 100644 --- a/Meshtastic/Model/Firmware/FirmwareViewModel.swift +++ b/Meshtastic/Model/Firmware/FirmwareViewModel.swift @@ -118,7 +118,7 @@ class FirmwareViewModel: ObservableObject { } } - func mostRecentFirmwareVersion(forReleaseType releaseType: FirmwareRelease.ReleaseType) -> String? { + func mostRecentFirmwareVersion(forReleaseType releaseType: ReleaseType) -> String? { let context = PersistenceController.shared.container.newBackgroundContext() var versionId: String? @@ -142,7 +142,7 @@ class FirmwareViewModel: ObservableObject { return firmwareFiles.filter({ $0.versionId == versionId }) } - func mostRecentFirmware(forReleaseType releaseType: FirmwareRelease.ReleaseType) -> [FirmwareFile] { + func mostRecentFirmware(forReleaseType releaseType: ReleaseType) -> [FirmwareFile] { if let versionId = mostRecentFirmwareVersion(forReleaseType: releaseType) { return firmwareFiles.filter { $0.releaseType == releaseType && $0.versionId == versionId } } else { @@ -167,7 +167,7 @@ class FirmwareViewModel: ObservableObject { return !downloadedFirmware(includeInProgressDownloads: false).isEmpty } - func delete(_ filesToDelete:[FirmwareFile]) { + func delete(_ filesToDelete: [FirmwareFile]) { // 1. Create a bucket for files that were actually deleted var deletedFiles = Set()