From 4efe1a2324700007439bbab8f56f724905c1280d Mon Sep 17 00:00:00 2001 From: Micha Date: Mon, 17 Nov 2025 15:42:55 +0100 Subject: [PATCH] refactored code structure --- NOTES.md | 7 + .../Extensions}/ByteFormatting.swift | 0 .../KeychainHelper.swift | 0 {Model => Sources/Model}/API/ApiFactory.swift | 0 {Model => Sources/Model}/API/ApiManager.swift | 0 {Model => Sources/Model}/API/BaseAPI.swift | 0 .../Model}/API/PingService.swift | 0 {Model => Sources/Model}/API/Server.swift | 0 {Model => Sources/Model}/API/ServerAPI.swift | 0 {Model => Sources/Model}/API/ServerInfo.swift | 59 +++++ .../Model}/API/ServerTypes.swift | 0 .../Model}/API/Versions/APIv2_12.swift | 47 ++++ {Views => Sources/Views}/Cells/InfoCell.swift | 0 .../Views}/Cells/LoadBarCell.swift | 0 .../Views}/Cells/UsageBarCell.swift | 0 {Views => Sources/Views}/MainView.swift | 0 Sources/Views/PreferencesView.swift | 242 ++++++++++++++++++ {Views => Sources/Views}/Rows/InfoRow.swift | 0 .../Views}/Rows/TableRowView.swift | 0 .../Views}/ServerDetailView.swift | 0 {Views => Sources/Views}/ServerFormView.swift | 0 .../Views}/Tabs/GeneralView.swift | 46 ++++ .../Views}/Tabs/ResourcesView.swift | 0 .../Views}/Tabs/ServicesView.swift | 0 iKeyMonApp.swift => Sources/iKeyMonApp.swift | 5 + iKeyMon.xcodeproj/project.pbxproj | 40 +-- 26 files changed, 417 insertions(+), 29 deletions(-) create mode 100644 NOTES.md rename {Extensions => Sources/Extensions}/ByteFormatting.swift (100%) rename KeychainHelper.swift => Sources/KeychainHelper.swift (100%) rename {Model => Sources/Model}/API/ApiFactory.swift (100%) rename {Model => Sources/Model}/API/ApiManager.swift (100%) rename {Model => Sources/Model}/API/BaseAPI.swift (100%) rename {Model => Sources/Model}/API/PingService.swift (100%) rename {Model => Sources/Model}/API/Server.swift (100%) rename {Model => Sources/Model}/API/ServerAPI.swift (100%) rename {Model => Sources/Model}/API/ServerInfo.swift (74%) rename {Model => Sources/Model}/API/ServerTypes.swift (100%) rename {Model => Sources/Model}/API/Versions/APIv2_12.swift (86%) rename {Views => Sources/Views}/Cells/InfoCell.swift (100%) rename {Views => Sources/Views}/Cells/LoadBarCell.swift (100%) rename {Views => Sources/Views}/Cells/UsageBarCell.swift (100%) rename {Views => Sources/Views}/MainView.swift (100%) create mode 100644 Sources/Views/PreferencesView.swift rename {Views => Sources/Views}/Rows/InfoRow.swift (100%) rename {Views => Sources/Views}/Rows/TableRowView.swift (100%) rename {Views => Sources/Views}/ServerDetailView.swift (100%) rename {Views => Sources/Views}/ServerFormView.swift (100%) rename {Views => Sources/Views}/Tabs/GeneralView.swift (51%) rename {Views => Sources/Views}/Tabs/ResourcesView.swift (100%) rename {Views => Sources/Views}/Tabs/ServicesView.swift (100%) rename iKeyMonApp.swift => Sources/iKeyMonApp.swift (80%) diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..7545e2d --- /dev/null +++ b/NOTES.md @@ -0,0 +1,7 @@ +- add tooltip for: + Ping (est time consideration) + dynamic Data + static Data + +move source to /src. like iKeyMon before +add a merker for "reboot required" diff --git a/Extensions/ByteFormatting.swift b/Sources/Extensions/ByteFormatting.swift similarity index 100% rename from Extensions/ByteFormatting.swift rename to Sources/Extensions/ByteFormatting.swift diff --git a/KeychainHelper.swift b/Sources/KeychainHelper.swift similarity index 100% rename from KeychainHelper.swift rename to Sources/KeychainHelper.swift diff --git a/Model/API/ApiFactory.swift b/Sources/Model/API/ApiFactory.swift similarity index 100% rename from Model/API/ApiFactory.swift rename to Sources/Model/API/ApiFactory.swift diff --git a/Model/API/ApiManager.swift b/Sources/Model/API/ApiManager.swift similarity index 100% rename from Model/API/ApiManager.swift rename to Sources/Model/API/ApiManager.swift diff --git a/Model/API/BaseAPI.swift b/Sources/Model/API/BaseAPI.swift similarity index 100% rename from Model/API/BaseAPI.swift rename to Sources/Model/API/BaseAPI.swift diff --git a/Model/API/PingService.swift b/Sources/Model/API/PingService.swift similarity index 100% rename from Model/API/PingService.swift rename to Sources/Model/API/PingService.swift diff --git a/Model/API/Server.swift b/Sources/Model/API/Server.swift similarity index 100% rename from Model/API/Server.swift rename to Sources/Model/API/Server.swift diff --git a/Model/API/ServerAPI.swift b/Sources/Model/API/ServerAPI.swift similarity index 100% rename from Model/API/ServerAPI.swift rename to Sources/Model/API/ServerAPI.swift diff --git a/Model/API/ServerInfo.swift b/Sources/Model/API/ServerInfo.swift similarity index 74% rename from Model/API/ServerInfo.swift rename to Sources/Model/API/ServerInfo.swift index 2859975..ac0620b 100644 --- a/Model/API/ServerInfo.swift +++ b/Sources/Model/API/ServerInfo.swift @@ -98,6 +98,43 @@ struct ServerInfo: Codable, Hashable, Equatable { } } + struct OperatingSystem: Codable, Hashable, Equatable { + struct UpdateStatus: Codable, Hashable, Equatable { + let updateCount: Int + let securityUpdateCount: Int + let rebootRequired: Bool + + init(updateCount: Int, securityUpdateCount: Int, rebootRequired: Bool) { + self.updateCount = updateCount + self.securityUpdateCount = securityUpdateCount + self.rebootRequired = rebootRequired + } + } + + let label: String + let distribution: String + let version: String + let architecture: String + let endOfLife: Bool + let updates: UpdateStatus? + + init( + label: String, + distribution: String, + version: String, + architecture: String, + endOfLife: Bool, + updates: UpdateStatus? + ) { + self.label = label + self.distribution = distribution + self.version = version + self.architecture = architecture + self.endOfLife = endOfLife + self.updates = updates + } + } + var hostname: String var ipAddresses: [String] var cpuCores: Int @@ -108,6 +145,7 @@ struct ServerInfo: Codable, Hashable, Equatable { var phpVersion: String var mysqlVersion: String? var mariadbVersion: String? + var operatingSystem: OperatingSystem? var ports: [ServicePort]? var load: Load var memory: Memory @@ -128,6 +166,15 @@ struct ServerInfo: Codable, Hashable, Equatable { } return ServerInfo.displayFormatter.string(from: date) } + + var operatingSystemSummary: String? { + guard let operatingSystem else { return nil } + let components = [ + operatingSystem.label, + operatingSystem.architecture + ].filter { !$0.isEmpty } + return components.isEmpty ? nil : components.joined(separator: " • ") + } } // MARK: - Helpers & Sample Data @@ -157,6 +204,18 @@ extension ServerInfo { phpVersion: "8.2.12", mysqlVersion: "8.0.33", mariadbVersion: nil, + operatingSystem: OperatingSystem( + label: "Debian 12.12 (64-bit)", + distribution: "Debian", + version: "12.12", + architecture: "x86_64", + endOfLife: false, + updates: OperatingSystem.UpdateStatus( + updateCount: 12, + securityUpdateCount: 8, + rebootRequired: true + ) + ), ports: [ ServicePort(service: "HTTP", status: "online", port: 80, proto: "tcp"), ServicePort(service: "HTTPS", status: "online", port: 443, proto: "tcp"), diff --git a/Model/API/ServerTypes.swift b/Sources/Model/API/ServerTypes.swift similarity index 100% rename from Model/API/ServerTypes.swift rename to Sources/Model/API/ServerTypes.swift diff --git a/Model/API/Versions/APIv2_12.swift b/Sources/Model/API/Versions/APIv2_12.swift similarity index 86% rename from Model/API/Versions/APIv2_12.swift rename to Sources/Model/API/Versions/APIv2_12.swift index 1188488..d8d3848 100644 --- a/Model/API/Versions/APIv2_12.swift +++ b/Sources/Model/API/Versions/APIv2_12.swift @@ -196,6 +196,7 @@ class APIv2_12: BaseAPIClient, ServerAPIProtocol { private extension APIv2_12 { struct ServerSummaryEnvelope: Decodable { let meta: Meta + let operatingSystem: OperatingSystem? let utilization: Utilization let components: Components let ports: [Port]? @@ -279,6 +280,36 @@ private extension APIv2_12 { let path: String? let configFile: String? } + + struct OperatingSystem: Decodable { + struct Updates: Decodable { + let updateCount: Int + let securityUpdateCount: Int + let rebootRequired: Bool + + enum CodingKeys: String, CodingKey { + case updateCount = "update_count" + case securityUpdateCount = "security_update_count" + case rebootRequired = "reboot_required" + } + } + + let label: String + let distribution: String + let version: String + let architecture: String + let endOfLife: Bool + let updates: Updates? + + enum CodingKeys: String, CodingKey { + case label + case distribution + case version + case architecture + case endOfLife = "end_of_life" + case updates + } + } func toDomain() -> ServerInfo { let load = utilization.load @@ -297,6 +328,22 @@ private extension APIv2_12 { phpVersion: components.php, mysqlVersion: components.mysql, mariadbVersion: components.mariadb, + operatingSystem: operatingSystem.map { + ServerInfo.OperatingSystem( + label: $0.label, + distribution: $0.distribution, + version: $0.version, + architecture: $0.architecture, + endOfLife: $0.endOfLife, + updates: $0.updates.map { + ServerInfo.OperatingSystem.UpdateStatus( + updateCount: $0.updateCount, + securityUpdateCount: $0.securityUpdateCount, + rebootRequired: $0.rebootRequired + ) + } + ) + }, ports: ports?.map { ServerInfo.ServicePort(service: $0.service, status: $0.status, port: $0.port, proto: $0.proto) }, diff --git a/Views/Cells/InfoCell.swift b/Sources/Views/Cells/InfoCell.swift similarity index 100% rename from Views/Cells/InfoCell.swift rename to Sources/Views/Cells/InfoCell.swift diff --git a/Views/Cells/LoadBarCell.swift b/Sources/Views/Cells/LoadBarCell.swift similarity index 100% rename from Views/Cells/LoadBarCell.swift rename to Sources/Views/Cells/LoadBarCell.swift diff --git a/Views/Cells/UsageBarCell.swift b/Sources/Views/Cells/UsageBarCell.swift similarity index 100% rename from Views/Cells/UsageBarCell.swift rename to Sources/Views/Cells/UsageBarCell.swift diff --git a/Views/MainView.swift b/Sources/Views/MainView.swift similarity index 100% rename from Views/MainView.swift rename to Sources/Views/MainView.swift diff --git a/Sources/Views/PreferencesView.swift b/Sources/Views/PreferencesView.swift new file mode 100644 index 0000000..cce0b9f --- /dev/null +++ b/Sources/Views/PreferencesView.swift @@ -0,0 +1,242 @@ +import SwiftUI + +struct PreferencesView: View { + private enum Tab: CaseIterable { + case monitor, notifications, alerts + + var title: String { + switch self { + case .monitor: return "Monitor" + case .notifications: return "Notifications" + case .alerts: return "Alerts" + } + } + + var icon: String { + switch self { + case .monitor: return "waveform.path.ecg" + case .notifications: return "bell.badge" + case .alerts: return "exclamationmark.triangle" + } + } + } + + @AppStorage("pingInterval") private var storedPingInterval: Int = 10 + @AppStorage("refreshInterval") private var storedRefreshInterval: Int = 60 + @AppStorage("showIntervalIndicator") private var showIntervalIndicator: Bool = true + + @State private var pingIntervalSlider: Double = 10 + @State private var refreshIntervalSlider: Double = 60 + @State private var selection: Tab = .monitor + + private let minimumInterval: Double = 10 + private let maximumPingInterval: Double = 600 + private let maximumRefreshInterval: Double = 600 + + var body: some View { + HStack(spacing: 0) { + sidebar + Divider() + ScrollView { + detailContent(for: selection) + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + .frame(minWidth: 360, maxWidth: .infinity, maxHeight: .infinity) + } + .frame(minWidth: 560, minHeight: 360) + .onAppear { + pingIntervalSlider = Double(storedPingInterval) + refreshIntervalSlider = Double(storedRefreshInterval) + } + .onChange(of: pingIntervalSlider) { _, newValue in + storedPingInterval = Int(newValue) + } + .onChange(of: refreshIntervalSlider) { _, newValue in + storedRefreshInterval = Int(newValue) + } + } + + private var sidebar: some View { + VStack(alignment: .leading, spacing: 12) { + Text("Preferences") + .font(.headline) + .padding(.bottom, 8) + + ForEach(Tab.allCases, id: \.self) { tab in + Button { + selection = tab + } label: { + HStack(spacing: 10) { + Image(systemName: tab.icon) + .frame(width: 20) + Text(tab.title) + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 6) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(selection == tab ? Color.accentColor.opacity(0.25) : Color.clear) + ) + } + .buttonStyle(.plain) + } + + Spacer() + } + .padding() + .frame(width: 180, alignment: .top) + } + + @ViewBuilder + private func detailContent(for tab: Tab) -> some View { + VStack(alignment: .leading, spacing: 12) { + Text(tab.title) + .font(.title2) + .padding(.bottom, 12) + + switch tab { + case .monitor: + MonitorPreferencesView( + pingIntervalSlider: $pingIntervalSlider, + refreshIntervalSlider: $refreshIntervalSlider, + showIntervalIndicator: $showIntervalIndicator, + minimumInterval: minimumInterval, + maximumPingInterval: maximumPingInterval, + maximumRefreshInterval: maximumRefreshInterval, + pingChanged: handlePingSliderEditing(_:), + refreshChanged: handleRefreshSliderEditing(_:) + ) + case .notifications: + NotificationsPreferencesView() + case .alerts: + AlertsPreferencesView() + } + } + } + + private func handlePingSliderEditing(_ editing: Bool) { + if !editing { + storedPingInterval = Int(pingIntervalSlider) + } + } + + private func handleRefreshSliderEditing(_ editing: Bool) { + if !editing { + storedRefreshInterval = Int(refreshIntervalSlider) + } + } +} + +private struct MonitorPreferencesView: View { + @Binding var pingIntervalSlider: Double + @Binding var refreshIntervalSlider: Double + @Binding var showIntervalIndicator: Bool + + let minimumInterval: Double + let maximumPingInterval: Double + let maximumRefreshInterval: Double + let pingChanged: (Bool) -> Void + let refreshChanged: (Bool) -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 18) { + Group { + Text("Ping interval") + .font(.headline) + Slider( + value: $pingIntervalSlider, + in: minimumInterval...maximumPingInterval, + step: 5 + ) { + Text("Ping interval") + } minimumValueLabel: { + Text("\(Int(minimumInterval))s") + .font(.caption) + .foregroundColor(.secondary) + } maximumValueLabel: { + Text("\(Int(maximumPingInterval))s") + .font(.caption) + .foregroundColor(.secondary) + } onEditingChanged: { editing in + pingChanged(editing) + } + Text("Current: \(Int(pingIntervalSlider)) seconds") + .font(.caption) + .foregroundColor(.secondary) + } + + Group { + Text("Refresh interval") + .font(.headline) + Slider( + value: $refreshIntervalSlider, + in: minimumInterval...maximumRefreshInterval, + step: 5 + ) { + Text("Refresh interval") + } minimumValueLabel: { + Text("\(Int(minimumInterval))s") + .font(.caption) + .foregroundColor(.secondary) + } maximumValueLabel: { + Text("\(Int(maximumRefreshInterval))s") + .font(.caption) + .foregroundColor(.secondary) + } onEditingChanged: { editing in + refreshChanged(editing) + } + Text("Current: \(Int(refreshIntervalSlider)) seconds") + .font(.caption) + .foregroundColor(.secondary) + } + + Divider() + + Toggle("Show interval indicator", isOn: $showIntervalIndicator) + .toggleStyle(.switch) + + Spacer() + } + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +private struct NotificationsPreferencesView: View { + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text("Notifications") + .font(.headline) + .padding(.bottom) + + Text("Configure notification behavior here.") + .foregroundColor(.secondary) + + Spacer() + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } +} + +private struct AlertsPreferencesView: View { + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text("Alerts") + .font(.headline) + .padding(.bottom) + + Text("Configure alert thresholds and behavior.") + .foregroundColor(.secondary) + + Spacer() + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } +} + +#Preview { + PreferencesView() +} diff --git a/Views/Rows/InfoRow.swift b/Sources/Views/Rows/InfoRow.swift similarity index 100% rename from Views/Rows/InfoRow.swift rename to Sources/Views/Rows/InfoRow.swift diff --git a/Views/Rows/TableRowView.swift b/Sources/Views/Rows/TableRowView.swift similarity index 100% rename from Views/Rows/TableRowView.swift rename to Sources/Views/Rows/TableRowView.swift diff --git a/Views/ServerDetailView.swift b/Sources/Views/ServerDetailView.swift similarity index 100% rename from Views/ServerDetailView.swift rename to Sources/Views/ServerDetailView.swift diff --git a/Views/ServerFormView.swift b/Sources/Views/ServerFormView.swift similarity index 100% rename from Views/ServerFormView.swift rename to Sources/Views/ServerFormView.swift diff --git a/Views/Tabs/GeneralView.swift b/Sources/Views/Tabs/GeneralView.swift similarity index 51% rename from Views/Tabs/GeneralView.swift rename to Sources/Views/Tabs/GeneralView.swift index faca5ac..8e9fe23 100644 --- a/Views/Tabs/GeneralView.swift +++ b/Sources/Views/Tabs/GeneralView.swift @@ -44,6 +44,52 @@ struct GeneralView: View { InfoCell(value: [server.info?.formattedVersion ?? ""], monospaced: true) } + TableRowView { + Text("Operating system") + } value: { + InfoCell( + value: { + guard let os = server.info?.operatingSystem else { return [] } + var rows: [String] = [] + + let distro = [os.distribution, os.version] + .filter { !$0.isEmpty } + .joined(separator: " ") + .trimmingCharacters(in: .whitespacesAndNewlines) + var description = os.label.trimmingCharacters(in: .whitespacesAndNewlines) + if description.isEmpty { + description = distro + } else if !distro.isEmpty && distro.caseInsensitiveCompare(description) != .orderedSame { + description += " • \(distro)" + } + if !os.architecture.isEmpty { + description += " (\(os.architecture))" + } + if !description.isEmpty { + rows.append(description) + } + + if let updates = os.updates { + var updateDescription = "Updates: \(updates.updateCount)" + if updates.securityUpdateCount > 0 { + updateDescription += " • \(updates.securityUpdateCount) security" + } + rows.append(updateDescription) + if updates.rebootRequired { + rows.append("Reboot required") + } + } + + if os.endOfLife { + rows.append("End-of-life release") + } + + return rows + }(), + monospaced: true + ) + } + TableRowView { Text("Sytem PHP version") } value: { diff --git a/Views/Tabs/ResourcesView.swift b/Sources/Views/Tabs/ResourcesView.swift similarity index 100% rename from Views/Tabs/ResourcesView.swift rename to Sources/Views/Tabs/ResourcesView.swift diff --git a/Views/Tabs/ServicesView.swift b/Sources/Views/Tabs/ServicesView.swift similarity index 100% rename from Views/Tabs/ServicesView.swift rename to Sources/Views/Tabs/ServicesView.swift diff --git a/iKeyMonApp.swift b/Sources/iKeyMonApp.swift similarity index 80% rename from iKeyMonApp.swift rename to Sources/iKeyMonApp.swift index 31b741f..16965e7 100644 --- a/iKeyMonApp.swift +++ b/Sources/iKeyMonApp.swift @@ -17,5 +17,10 @@ struct iKeyMonApp: App { } } .windowResizability(.contentMinSize) + + Settings { + PreferencesView() + .padding() + } } } diff --git a/iKeyMon.xcodeproj/project.pbxproj b/iKeyMon.xcodeproj/project.pbxproj index 3fdac3c..0750660 100644 --- a/iKeyMon.xcodeproj/project.pbxproj +++ b/iKeyMon.xcodeproj/project.pbxproj @@ -7,42 +7,30 @@ objects = { /* Begin PBXBuildFile section */ - 52A9B79B2EC8E7EE004DD4A2 /* iKeyMonApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A9B7892EC8E7EE004DD4A2 /* iKeyMonApp.swift */; }; - 52A9B79C2EC8E7EE004DD4A2 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A9B78A2EC8E7EE004DD4A2 /* KeychainHelper.swift */; }; 52A9B79F2EC8E7EE004DD4A2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52A9B7872EC8E7EE004DD4A2 /* Assets.xcassets */; }; 52A9B8222EC8FA8A004DD4A2 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = 52A9B8212EC8FA8A004DD4A2 /* CHANGELOG.md */; }; + 52A9B8BB2ECA3605004DD4A2 /* NOTES.md in Sources */ = {isa = PBXBuildFile; fileRef = 52A9B8BA2ECA35FB004DD4A2 /* NOTES.md */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 5203C24D2D997D2800576D4A /* iKeyMon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iKeyMon.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52A9B7872EC8E7EE004DD4A2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52A9B7882EC8E7EE004DD4A2 /* iKeyMon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iKeyMon.entitlements; sourceTree = ""; }; - 52A9B7892EC8E7EE004DD4A2 /* iKeyMonApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iKeyMonApp.swift; sourceTree = ""; }; - 52A9B78A2EC8E7EE004DD4A2 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = ""; }; 52A9B8212EC8FA8A004DD4A2 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + 52A9B8BA2ECA35FB004DD4A2 /* NOTES.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = NOTES.md; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ - 52A9B7A12EC8E84F004DD4A2 /* Extensions */ = { + 52A9B8BE2ECB68B5004DD4A2 /* Sources */ = { isa = PBXFileSystemSynchronizedRootGroup; - path = Extensions; + path = Sources; sourceTree = ""; }; - 52A9B7A72EC8E857004DD4A2 /* Model */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = Model; - sourceTree = ""; - }; - 52A9B7AC2EC8E85E004DD4A2 /* Preview Content */ = { + 52A9B8F72ECB6B8A004DD4A2 /* Preview Content */ = { isa = PBXFileSystemSynchronizedRootGroup; path = "Preview Content"; sourceTree = ""; }; - 52A9B7BC2EC8E86C004DD4A2 /* Views */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = Views; - sourceTree = ""; - }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -59,14 +47,11 @@ 5203C2442D997D2800576D4A = { isa = PBXGroup; children = ( + 52A9B8BE2ECB68B5004DD4A2 /* Sources */, + 52A9B8BA2ECA35FB004DD4A2 /* NOTES.md */, 52A9B7872EC8E7EE004DD4A2 /* Assets.xcassets */, - 52A9B7A72EC8E857004DD4A2 /* Model */, 52A9B7882EC8E7EE004DD4A2 /* iKeyMon.entitlements */, - 52A9B7A12EC8E84F004DD4A2 /* Extensions */, - 52A9B7AC2EC8E85E004DD4A2 /* Preview Content */, - 52A9B7892EC8E7EE004DD4A2 /* iKeyMonApp.swift */, - 52A9B7BC2EC8E86C004DD4A2 /* Views */, - 52A9B78A2EC8E7EE004DD4A2 /* KeychainHelper.swift */, + 52A9B8F72ECB6B8A004DD4A2 /* Preview Content */, 5203C24E2D997D2800576D4A /* Products */, 52A9B8212EC8FA8A004DD4A2 /* CHANGELOG.md */, ); @@ -96,10 +81,8 @@ dependencies = ( ); fileSystemSynchronizedGroups = ( - 52A9B7A12EC8E84F004DD4A2 /* Extensions */, - 52A9B7A72EC8E857004DD4A2 /* Model */, - 52A9B7AC2EC8E85E004DD4A2 /* Preview Content */, - 52A9B7BC2EC8E86C004DD4A2 /* Views */, + 52A9B8BE2ECB68B5004DD4A2 /* Sources */, + 52A9B8F72ECB6B8A004DD4A2 /* Preview Content */, ); name = iKeyMon; packageProductDependencies = ( @@ -159,8 +142,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 52A9B79B2EC8E7EE004DD4A2 /* iKeyMonApp.swift in Sources */, - 52A9B79C2EC8E7EE004DD4A2 /* KeychainHelper.swift in Sources */, + 52A9B8BB2ECA3605004DD4A2 /* NOTES.md in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };