From 66e434e9a8581f87c0b81a9eb741da22c18ba307 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 08:04:21 -0700 Subject: [PATCH 01/39] feat: UpdateManager --- SwiftDiffusion.xcodeproj/project.pbxproj | 4 + SwiftDiffusion/SwiftDiffusionApp.swift | 2 + .../MainViews/ContentView/ContentView.swift | 1 + .../UpdatesView/UpdateManager.swift | 92 +++++++++++++++++++ .../WindowViews/UpdatesView/UpdatesView.swift | 19 ++-- 5 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift diff --git a/SwiftDiffusion.xcodeproj/project.pbxproj b/SwiftDiffusion.xcodeproj/project.pbxproj index ea81f8be..448e4bde 100644 --- a/SwiftDiffusion.xcodeproj/project.pbxproj +++ b/SwiftDiffusion.xcodeproj/project.pbxproj @@ -108,6 +108,7 @@ 29C06C282B93CC8F00F950C4 /* URLParserUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C272B93CC8F00F950C4 /* URLParserUtility.swift */; }; 29C06C2B2B93DD0F00F950C4 /* BetaOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C2A2B93DD0F00F950C4 /* BetaOnboardingView.swift */; }; 29C06C2D2B940D8200F950C4 /* BlueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C2C2B940D8200F950C4 /* BlueButton.swift */; }; + 29C06C2F2B94C55900F950C4 /* UpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */; }; 29C0F35F2B79BDA80017DD6B /* NotificationUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F35E2B79BDA80017DD6B /* NotificationUtility.swift */; }; 29C0F3632B79C8020017DD6B /* FullscreenImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3622B79C8020017DD6B /* FullscreenImageView.swift */; }; 29C0F3652B79C81F0017DD6B /* ImageWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3642B79C81F0017DD6B /* ImageWindowManager.swift */; }; @@ -248,6 +249,7 @@ 29C06C272B93CC8F00F950C4 /* URLParserUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLParserUtility.swift; sourceTree = ""; }; 29C06C2A2B93DD0F00F950C4 /* BetaOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetaOnboardingView.swift; sourceTree = ""; }; 29C06C2C2B940D8200F950C4 /* BlueButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueButton.swift; sourceTree = ""; }; + 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateManager.swift; sourceTree = ""; }; 29C0F35E2B79BDA80017DD6B /* NotificationUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationUtility.swift; sourceTree = ""; }; 29C0F3622B79C8020017DD6B /* FullscreenImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenImageView.swift; sourceTree = ""; }; 29C0F3642B79C81F0017DD6B /* ImageWindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageWindowManager.swift; sourceTree = ""; }; @@ -916,6 +918,7 @@ isa = PBXGroup; children = ( 29C0F36E2B7A5C3D0017DD6B /* UpdatesView.swift */, + 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */, ); path = UpdatesView; sourceTree = ""; @@ -1117,6 +1120,7 @@ 29145E6C2B77CD1600BFA64B /* StateDebugInfo.swift in Sources */, 29FB923F2B8DAA660023C7C5 /* ShareButton.swift in Sources */, 2916F3372B72859D001D14B9 /* DetailView.swift in Sources */, + 29C06C2F2B94C55900F950C4 /* UpdateManager.swift in Sources */, 29C0F3822B7ABF210017DD6B /* FilesSection.swift in Sources */, 29169D102B8EADDF0099A9C5 /* SidebarFolder.swift in Sources */, 29145E6F2B77D82A00BFA64B /* DebugPromptStatusView.swift in Sources */, diff --git a/SwiftDiffusion/SwiftDiffusionApp.swift b/SwiftDiffusion/SwiftDiffusionApp.swift index 21686c14..f19d84fa 100644 --- a/SwiftDiffusion/SwiftDiffusionApp.swift +++ b/SwiftDiffusion/SwiftDiffusionApp.swift @@ -13,6 +13,7 @@ struct SwiftDiffusionApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var modelContainer: ModelContainer var scriptManager = ScriptManager.shared + let updateManager = UpdateManager() let sidebarModel = SidebarModel() let checkpointsManager = CheckpointsManager() let currentPrompt = PromptModel() @@ -48,6 +49,7 @@ struct SwiftDiffusionApp: App { .frame(minWidth: 720, idealWidth: 900, maxWidth: .infinity, minHeight: 500, idealHeight: 800, maxHeight: .infinity) .environmentObject(scriptManager) + .environmentObject(updateManager) .environmentObject(sidebarModel) .environmentObject(checkpointsManager) .environmentObject(currentPrompt) diff --git a/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift b/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift index 629fb982..5c841d56 100644 --- a/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift +++ b/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift @@ -30,6 +30,7 @@ extension ViewManager: Hashable, Identifiable { struct ContentView: View { @Environment(\.modelContext) var modelContext + @EnvironmentObject var updateManager: UpdateManager @EnvironmentObject var sidebarModel: SidebarModel @EnvironmentObject var checkpointsManager: CheckpointsManager @EnvironmentObject var currentPrompt: PromptModel diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift new file mode 100644 index 00000000..9dda944e --- /dev/null +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -0,0 +1,92 @@ +// +// UpdateManager.swift +// SwiftDiffusion +// +// Created by Justin Bush on 3/3/24. +// + +import Foundation +import SwiftUI + +enum UpdateFrequency: String, CaseIterable { + case everyAppLaunch = "Every App Launch" + case daily = "Daily" + case weekly = "Weekly" + case everyOtherWeek = "Every Other Week" + case monthly = "Monthly" +} + + +class UpdateManager: ObservableObject { + @Published var lastCheckedTimestamp: Date? + @Published var isCheckingForUpdate: Bool = false + private var updateCheckFrequency: UpdateFrequency = .everyAppLaunch // Default value, load this from user preferences + + init() { + // Initialize from saved settings, if available + loadSettings() + } + + func loadSettings() { + // Load your saved frequency and last checked timestamp here + // For demonstration, using UserDefaults (not recommended for production) + if let frequencyRawValue = UserDefaults.standard.string(forKey: "updateCheckFrequency"), + let frequency = UpdateFrequency(rawValue: frequencyRawValue) { + updateCheckFrequency = frequency + } + lastCheckedTimestamp = UserDefaults.standard.object(forKey: "lastCheckedTimestamp") as? Date + } + + func saveSettings() { + // Save your frequency and last checked timestamp here + UserDefaults.standard.set(updateCheckFrequency.rawValue, forKey: "updateCheckFrequency") + UserDefaults.standard.set(lastCheckedTimestamp, forKey: "lastCheckedTimestamp") + } + + func checkForUpdatesIfNeeded() async { + guard shouldCheckForUpdates() else { return } + + isCheckingForUpdate = true // Indicate that checking for updates has started + let updateAvailable = await checkForUpdates() + isCheckingForUpdate = false // Reset the flag after checking + + if updateAvailable { + // If true, there is an update available. Open the UpdatesView with download button. + // This part will depend on your app's architecture. + // You might post a notification, use a state manager, etc., to switch the view. + } + + // Update the last checked timestamp regardless of update availability + lastCheckedTimestamp = Date() + saveSettings() + } + + private func shouldCheckForUpdates() -> Bool { + guard let lastChecked = lastCheckedTimestamp else { return true } + + let calendar = Calendar.current + let now = Date() + + switch updateCheckFrequency { + case .everyAppLaunch: + return true + case .daily: + return calendar.isDate(lastChecked, inSameDayAs: now) == false + case .weekly: + return !calendar.isDate(lastChecked, equalTo: now, toGranularity: .weekOfYear) + case .everyOtherWeek: + if let nextCheckDate = calendar.date(byAdding: .weekOfYear, value: 2, to: lastChecked) { + return nextCheckDate <= now + } + return false + case .monthly: + return !calendar.isDate(lastChecked, equalTo: now, toGranularity: .month) + } + } + + private func checkForUpdates() async -> Bool { + // Here, you would check for updates. This is a placeholder for your update logic. + // Return true if an update is available, false otherwise. + return false // Placeholder return value + } +} diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index 68ce9566..d4ff1357 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -11,21 +11,20 @@ struct AppInfo { static var version: String { Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "" } - static var buildString: String { Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "" } - static var buildInt: Int { Int(buildString) ?? 0 } - static var versionAndBuild: String { "v\(version) (\(buildString))" } } struct UpdatesView: View { + @EnvironmentObject var updateManager: UpdateManager + @State private var showUpdateFrequencySection: Bool = false var body: some View { VStack { @@ -38,7 +37,7 @@ struct UpdatesView: View { .foregroundStyle(Color.green) .padding(.trailing, 2) VStack(alignment: .leading) { - Text("You are running the latest version.") + Text("You are on the latest version.") .bold() .padding(.bottom, 1) Text("SwiftDiffusion \(AppInfo.versionAndBuild)") @@ -47,6 +46,12 @@ struct UpdatesView: View { } } + if showUpdateFrequencySection { + Spacer() + + // MenuFrequency + } + Spacer() Button(action: { @@ -73,9 +78,11 @@ struct UpdatesView: View { .scaleEffect(0.5) Button(action: { - Debug.log("Button") + withAnimation { + showUpdateFrequencySection.toggle() + } }) { - Image(systemName: "info.circle") + Image(systemName: "clock") } } From 9ebb269bafb6de7c39159e77c4328f603b4897c5 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 08:44:23 -0700 Subject: [PATCH 02/39] refactor: showUpdatesWindow with environment object --- SwiftDiffusion/SwiftDiffusionApp.swift | 2 +- SwiftDiffusion/WindowManager.swift | 40 ++++++++------------------ 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/SwiftDiffusion/SwiftDiffusionApp.swift b/SwiftDiffusion/SwiftDiffusionApp.swift index f19d84fa..c81d4182 100644 --- a/SwiftDiffusion/SwiftDiffusionApp.swift +++ b/SwiftDiffusion/SwiftDiffusionApp.swift @@ -63,7 +63,7 @@ struct SwiftDiffusionApp: App { Divider() Button("Check for Updates...") { - WindowManager.shared.showUpdatesWindow() + WindowManager.shared.showUpdatesWindow(updateManager: updateManager) } .keyboardShortcut("U", modifiers: [.command]) diff --git a/SwiftDiffusion/WindowManager.swift b/SwiftDiffusion/WindowManager.swift index 09c107e4..c5fd7563 100644 --- a/SwiftDiffusion/WindowManager.swift +++ b/SwiftDiffusion/WindowManager.swift @@ -36,28 +36,24 @@ class WindowManager: NSObject, ObservableObject { backing: .buffered, defer: false) betaOnboardingWindow?.center() betaOnboardingWindow?.contentView = NSHostingView(rootView: BetaOnboardingView()) - betaOnboardingWindow?.isReleasedWhenClosed = false betaOnboardingWindow?.delegate = self - betaOnboardingWindow?.standardWindowButton(.zoomButton)?.isHidden = true } betaOnboardingWindow?.makeKeyAndOrderFront(nil) } /// Shows the updates window containing UpdatesView. If the window does not exist, it creates and configures a new window before displaying it. - func showUpdatesWindow() { + func showUpdatesWindow(updateManager: UpdateManager) { if updatesWindow == nil { updatesWindow = NSWindow( - contentRect: NSRect(x: 20, y: 20, width: 480, height: 300), + contentRect: NSRect(x: 20, y: 20, width: 400, height: 250), styleMask: [.titled, .closable, .resizable, .miniaturizable], backing: .buffered, defer: false) updatesWindow?.center() - updatesWindow?.contentView = NSHostingView(rootView: UpdatesView()) - + updatesWindow?.contentView = NSHostingView(rootView: UpdatesView().environmentObject(updateManager)) updatesWindow?.isReleasedWhenClosed = false updatesWindow?.delegate = self - updatesWindow?.standardWindowButton(.zoomButton)?.isHidden = true } updatesWindow?.makeKeyAndOrderFront(nil) @@ -74,10 +70,8 @@ class WindowManager: NSObject, ObservableObject { settingsWindow?.center() settingsWindow?.setFrameAutosaveName("Settings") settingsWindow?.contentView = NSHostingView(rootView: SettingsView(openWithTab: tab)) - settingsWindow?.isReleasedWhenClosed = false settingsWindow?.delegate = self - settingsWindow?.standardWindowButton(.zoomButton)?.isHidden = true if withPreferenceStyle { @@ -100,14 +94,9 @@ class WindowManager: NSObject, ObservableObject { styleMask: [.titled, .closable, .resizable, .miniaturizable], backing: .buffered, defer: false) checkpointManagerWindow?.center() - - //let rootView = CheckpointManagerView() - checkpointManagerWindow?.contentView = NSHostingView(rootView: CheckpointManagerView(scriptManager: scriptManager, currentPrompt: currentPrompt, checkpointsManager: checkpointsManager)) - checkpointManagerWindow?.isReleasedWhenClosed = false checkpointManagerWindow?.delegate = self - checkpointManagerWindow?.standardWindowButton(.zoomButton)?.isHidden = true } checkpointManagerWindow?.makeKeyAndOrderFront(nil) @@ -122,16 +111,13 @@ class WindowManager: NSObject, ObservableObject { checkpointManagerWindow?.center() let rootView = DebugApiView() - .environmentObject(scriptManager) - .environmentObject(checkpointsManager) - .environmentObject(currentPrompt) - .environmentObject(loraModelsManager) - - checkpointManagerWindow?.contentView = NSHostingView(rootView: rootView) - + .environmentObject(scriptManager) + .environmentObject(checkpointsManager) + .environmentObject(currentPrompt) + .environmentObject(loraModelsManager) + checkpointManagerWindow?.contentView = NSHostingView(rootView: rootView) checkpointManagerWindow?.isReleasedWhenClosed = false checkpointManagerWindow?.delegate = self - checkpointManagerWindow?.standardWindowButton(.zoomButton)?.isHidden = true } checkpointManagerWindow?.makeKeyAndOrderFront(nil) @@ -144,12 +130,10 @@ extension WindowManager: NSWindowDelegate { func windowWillClose(_ notification: Notification) { if let window = notification.object as? NSWindow { switch window { - case updatesWindow: - updatesWindow = nil - case settingsWindow: - settingsWindow = nil - case checkpointManagerWindow: - checkpointManagerWindow = nil + case updatesWindow: updatesWindow = nil + case settingsWindow: settingsWindow = nil + case checkpointManagerWindow: checkpointManagerWindow = nil + case debugApiView: debugApiView = nil default: break } From b6ec15dd86c4533445cdd000204d59cfe334f95d Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 08:44:41 -0700 Subject: [PATCH 03/39] refactor: remove checkpointsManager from SettingsView --- SwiftDiffusion/Views/WindowViews/SettingsView/SettingsView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/SwiftDiffusion/Views/WindowViews/SettingsView/SettingsView.swift b/SwiftDiffusion/Views/WindowViews/SettingsView/SettingsView.swift index 04fe4c4e..e153af10 100644 --- a/SwiftDiffusion/Views/WindowViews/SettingsView/SettingsView.swift +++ b/SwiftDiffusion/Views/WindowViews/SettingsView/SettingsView.swift @@ -16,7 +16,6 @@ extension Constants.WindowSize { struct SettingsView: View { @ObservedObject var userSettings = UserSettings.shared - @EnvironmentObject var checkpointsManager: CheckpointsManager @Environment(\.presentationMode) var presentationMode From 7ee63bcfc57052afa663a546df82643360130b34 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 08:44:57 -0700 Subject: [PATCH 04/39] refactor: UpdatesView frequency Menu section --- .../UpdatesView/UpdateManager.swift | 24 ++++--- .../WindowViews/UpdatesView/UpdatesView.swift | 68 ++++++++++++++----- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index 9dda944e..2c40c78f 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -20,16 +20,13 @@ enum UpdateFrequency: String, CaseIterable { class UpdateManager: ObservableObject { @Published var lastCheckedTimestamp: Date? @Published var isCheckingForUpdate: Bool = false - private var updateCheckFrequency: UpdateFrequency = .everyAppLaunch // Default value, load this from user preferences + var updateCheckFrequency: UpdateFrequency = .everyAppLaunch init() { - // Initialize from saved settings, if available loadSettings() } func loadSettings() { - // Load your saved frequency and last checked timestamp here - // For demonstration, using UserDefaults (not recommended for production) if let frequencyRawValue = UserDefaults.standard.string(forKey: "updateCheckFrequency"), let frequency = UpdateFrequency(rawValue: frequencyRawValue) { updateCheckFrequency = frequency @@ -38,7 +35,6 @@ class UpdateManager: ObservableObject { } func saveSettings() { - // Save your frequency and last checked timestamp here UserDefaults.standard.set(updateCheckFrequency.rawValue, forKey: "updateCheckFrequency") UserDefaults.standard.set(lastCheckedTimestamp, forKey: "lastCheckedTimestamp") } @@ -46,9 +42,9 @@ class UpdateManager: ObservableObject { func checkForUpdatesIfNeeded() async { guard shouldCheckForUpdates() else { return } - isCheckingForUpdate = true // Indicate that checking for updates has started + isCheckingForUpdate = true let updateAvailable = await checkForUpdates() - isCheckingForUpdate = false // Reset the flag after checking + isCheckingForUpdate = false if updateAvailable { // If true, there is an update available. Open the UpdatesView with download button. @@ -56,7 +52,6 @@ class UpdateManager: ObservableObject { // You might post a notification, use a state manager, etc., to switch the view. } - // Update the last checked timestamp regardless of update availability lastCheckedTimestamp = Date() saveSettings() } @@ -90,3 +85,16 @@ class UpdateManager: ObservableObject { return false // Placeholder return value } } + +extension UpdateManager { + var lastCheckedTimestampFormatted: String { + if let lastChecked = lastCheckedTimestamp { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .short + return formatter.string(from: lastChecked) + } else { + return "Never" + } + } +} diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index d4ff1357..6f207d68 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -25,10 +25,33 @@ struct AppInfo { struct UpdatesView: View { @EnvironmentObject var updateManager: UpdateManager @State private var showUpdateFrequencySection: Bool = false + private let updateFrequencySectionHeight: CGFloat = 28 + private let initialFrameHeight: CGFloat = 250 + var expandedFrameHeight: CGFloat { + initialFrameHeight + updateFrequencySectionHeight + } var body: some View { VStack { + ToggleWithLabel(isToggled: .constant(true), header: "Automatically check for updates", description: "Checks for new releases on GitHub", showAllDescriptions: true) + .padding(.top, 10) + + if showUpdateFrequencySection { + VStack { + Menu { + Picker("Update Frequency", selection: $updateManager.updateCheckFrequency) { + ForEach(UpdateFrequency.allCases, id: \.self) { frequency in + Text(frequency.rawValue).tag(frequency) + } + } + } label: { + Label("Update Frequency", systemImage: "calendar") + } + } + .frame(width: 250, height: updateFrequencySectionHeight) + .transition(.opacity.combined(with: .slide)) + } Spacer() @@ -46,27 +69,28 @@ struct UpdatesView: View { } } - if showUpdateFrequencySection { - Spacer() - - // MenuFrequency - } - Spacer() - Button(action: { - Debug.log("Button") - }) { - Text("Check for Updates") + Button("Check for Updates") { + Task { + await updateManager.checkForUpdatesIfNeeded() + } } .padding(.bottom, 10) - Text("Last checked: Today, 2:34 PM") - .font(.footnote) - .foregroundStyle(Color.secondary) + if let lastChecked = updateManager.lastCheckedTimestamp { + Text("Last checked: \(lastChecked, formatter: itemFormatter)") + .font(.footnote) + .foregroundStyle(Color.secondary) + } else { + Text("Last checked: Never") + .font(.footnote) + .foregroundStyle(Color.secondary) + } } .padding() - .frame(width: 400, height: 250) + .frame(width: 400, height: showUpdateFrequencySection ? expandedFrameHeight : initialFrameHeight) + .animation(.easeInOut, value: showUpdateFrequencySection) .navigationTitle("Updates") .toolbar { @@ -76,6 +100,7 @@ struct UpdatesView: View { ProgressView() .progressViewStyle(CircularProgressViewStyle()) .scaleEffect(0.5) + .opacity(updateManager.isCheckingForUpdate ? 1 : 0) Button(action: { withAnimation { @@ -83,18 +108,29 @@ struct UpdatesView: View { } }) { Image(systemName: "clock") + .foregroundStyle(showUpdateFrequencySection ? .blue : .secondary) } } } } } + private var itemFormatter: DateFormatter { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .short + return formatter + } } #Preview { - UpdatesView() - .frame(width: 400, height: 250) + let updateManager = UpdateManager() + updateManager.loadSettings() + + return UpdatesView() + .frame(idealWidth: 400, idealHeight: 250) + .environmentObject(updateManager) } // MARK: ToggleWithLabel From 248e95014fc8f744977ea251dbff7207c5d2420b Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 08:47:51 -0700 Subject: [PATCH 05/39] refactor: slide up/down frequency menu --- SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index 6f207d68..6a3d5ede 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -50,7 +50,8 @@ struct UpdatesView: View { } } .frame(width: 250, height: updateFrequencySectionHeight) - .transition(.opacity.combined(with: .slide)) + .transition(.asymmetric(insertion: .move(edge: .top).combined(with: .opacity), + removal: .move(edge: .top).combined(with: .opacity))) } Spacer() From ebc7981c4846254b75af118e4ea6418d86e1ba04 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 08:53:01 -0700 Subject: [PATCH 06/39] fix: UpdateFrequency @Published menu item on selection --- .../Views/WindowViews/UpdatesView/UpdateManager.swift | 2 +- .../Views/WindowViews/UpdatesView/UpdatesView.swift | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index 2c40c78f..3732605e 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -20,7 +20,7 @@ enum UpdateFrequency: String, CaseIterable { class UpdateManager: ObservableObject { @Published var lastCheckedTimestamp: Date? @Published var isCheckingForUpdate: Bool = false - var updateCheckFrequency: UpdateFrequency = .everyAppLaunch + @Published var updateCheckFrequency: UpdateFrequency = .everyAppLaunch init() { loadSettings() diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index 6a3d5ede..7a536f28 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -39,6 +39,16 @@ struct UpdatesView: View { if showUpdateFrequencySection { VStack { + Menu(updateManager.updateCheckFrequency.rawValue) { + ForEach(UpdateFrequency.allCases, id: \.self) { frequency in + Button(frequency.rawValue) { + updateManager.updateCheckFrequency = frequency + updateManager.saveSettings() + } + } + } + + /* Menu { Picker("Update Frequency", selection: $updateManager.updateCheckFrequency) { ForEach(UpdateFrequency.allCases, id: \.self) { frequency in @@ -48,6 +58,7 @@ struct UpdatesView: View { } label: { Label("Update Frequency", systemImage: "calendar") } + */ } .frame(width: 250, height: updateFrequencySectionHeight) .transition(.asymmetric(insertion: .move(edge: .top).combined(with: .opacity), From 87f6530ac9a5e75a4e6235de73c127dc6db786ca Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 08:54:19 -0700 Subject: [PATCH 07/39] refactor: Label(updateCheckFrequency) --- .../Views/WindowViews/UpdatesView/UpdatesView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index 7a536f28..9b23e93c 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -39,13 +39,15 @@ struct UpdatesView: View { if showUpdateFrequencySection { VStack { - Menu(updateManager.updateCheckFrequency.rawValue) { + Menu { ForEach(UpdateFrequency.allCases, id: \.self) { frequency in Button(frequency.rawValue) { updateManager.updateCheckFrequency = frequency updateManager.saveSettings() } } + } label: { + Label(updateManager.updateCheckFrequency.rawValue, systemImage: "calendar") } /* From 69820c78397e91c3f2f9e842328dcec18e7262c4 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 08:54:41 -0700 Subject: [PATCH 08/39] refactor: clean old menu style --- .../Views/WindowViews/UpdatesView/UpdatesView.swift | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index 9b23e93c..249bff86 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -49,18 +49,6 @@ struct UpdatesView: View { } label: { Label(updateManager.updateCheckFrequency.rawValue, systemImage: "calendar") } - - /* - Menu { - Picker("Update Frequency", selection: $updateManager.updateCheckFrequency) { - ForEach(UpdateFrequency.allCases, id: \.self) { frequency in - Text(frequency.rawValue).tag(frequency) - } - } - } label: { - Label("Update Frequency", systemImage: "calendar") - } - */ } .frame(width: 250, height: updateFrequencySectionHeight) .transition(.asymmetric(insertion: .move(edge: .top).combined(with: .opacity), From 218d8d732d4c8a3a45d974b619906462c7f276e3 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 09:56:17 -0700 Subject: [PATCH 09/39] feat: GitHub release page parsing --- SwiftDiffusion.xcodeproj/project.pbxproj | 8 +- .../UpdatesView/UpdateManager.swift | 74 +++++++++++++++++-- .../WindowViews/UpdatesView/UpdatesView.swift | 2 +- 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/SwiftDiffusion.xcodeproj/project.pbxproj b/SwiftDiffusion.xcodeproj/project.pbxproj index 448e4bde..c3d59f34 100644 --- a/SwiftDiffusion.xcodeproj/project.pbxproj +++ b/SwiftDiffusion.xcodeproj/project.pbxproj @@ -1315,7 +1315,7 @@ CODE_SIGN_ENTITLEMENTS = SwiftDiffusion/SwiftDiffusion.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 100; + CURRENT_PROJECT_VERSION = 1100; DEVELOPMENT_ASSET_PATHS = "\"SwiftDiffusion/Preview Content\""; DEVELOPMENT_TEAM = 85N3S3DG8M; ENABLE_HARDENED_RUNTIME = YES; @@ -1327,7 +1327,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.0.1; + MARKETING_VERSION = 0.1.1; PRODUCT_BUNDLE_IDENTIFIER = com.buzsh.SwiftDiffusion; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1343,7 +1343,7 @@ CODE_SIGN_ENTITLEMENTS = SwiftDiffusion/SwiftDiffusion.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 100; + CURRENT_PROJECT_VERSION = 1100; DEVELOPMENT_ASSET_PATHS = "\"SwiftDiffusion/Preview Content\""; DEVELOPMENT_TEAM = 85N3S3DG8M; ENABLE_HARDENED_RUNTIME = YES; @@ -1355,7 +1355,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.0.1; + MARKETING_VERSION = 0.1.1; PRODUCT_BUNDLE_IDENTIFIER = com.buzsh.SwiftDiffusion; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index 3732605e..925885aa 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -39,14 +39,15 @@ class UpdateManager: ObservableObject { UserDefaults.standard.set(lastCheckedTimestamp, forKey: "lastCheckedTimestamp") } - func checkForUpdatesIfNeeded() async { - guard shouldCheckForUpdates() else { return } + func checkForUpdatesIfNeeded(force: Bool = false) async { + guard force || shouldCheckForUpdates() else { return } isCheckingForUpdate = true let updateAvailable = await checkForUpdates() isCheckingForUpdate = false if updateAvailable { + Debug.log("updateAvailable!") // If true, there is an update available. Open the UpdatesView with download button. // This part will depend on your app's architecture. // You might post a notification, use a state manager, etc., to switch the view. @@ -79,10 +80,25 @@ class UpdateManager: ObservableObject { } } - private func checkForUpdates() async -> Bool { - // Here, you would check for updates. This is a placeholder for your update logic. - // Return true if an update is available, false otherwise. - return false // Placeholder return value + func checkForUpdates() async -> Bool { + guard let url = URL(string: "https://github.com/revblaze/ReleaseParsingTest/releases") else { + Debug.log("Invalid URL") + return false + } + + guard let html = await fetchReleasesPage(url: url) else { + Debug.log("Failed to fetch releases page") + return false + } + + let releases = parseReleases(from: html) + for release in releases { + Debug.log("Title: \(release.releaseTitle), Build Number: \(release.releaseBuildNumber)") + } + + // Placeholder logic to determine if an update is available + // You would compare the fetched releases against the current app version and build number + return !releases.isEmpty } } @@ -98,3 +114,49 @@ extension UpdateManager { } } } + +struct GitRelease { + var releaseTitle: String + var releaseDate: String + var releaseTag: String + var releaseBuildNumber: Int + var releaseAssets: [String: String] +} + + +extension UpdateManager { + func fetchReleasesPage(url: URL) async -> String? { + do { + let (data, _) = try await URLSession.shared.data(from: url) + return String(decoding: data, as: UTF8.self) + } catch { + Debug.log("Error fetching releases page: \(error)") + return nil + } + } + + func parseReleases(from html: String) -> [GitRelease] { + let sections = html.components(separatedBy: "
"), + let titleEndRange = section.range(of: "") { + let title = String(section[titleStartRange.upperBound..b"), + let buildNumberEndRange = section.range(of: "", range: buildNumberStartRange.lowerBound.. Date: Sun, 3 Mar 2024 10:10:23 -0700 Subject: [PATCH 10/39] refactor: UI updates with MainActor --- .../UpdatesView/UpdateManager.swift | 30 +++++++++++-------- .../WindowViews/UpdatesView/UpdatesView.swift | 1 + 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index 925885aa..f28541a9 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -21,6 +21,7 @@ class UpdateManager: ObservableObject { @Published var lastCheckedTimestamp: Date? @Published var isCheckingForUpdate: Bool = false @Published var updateCheckFrequency: UpdateFrequency = .everyAppLaunch + @Published var checkForUpdatesErrorMessage: String? = nil init() { loadSettings() @@ -42,19 +43,24 @@ class UpdateManager: ObservableObject { func checkForUpdatesIfNeeded(force: Bool = false) async { guard force || shouldCheckForUpdates() else { return } - isCheckingForUpdate = true - let updateAvailable = await checkForUpdates() - isCheckingForUpdate = false + await MainActor.run { self.isCheckingForUpdate = true } - if updateAvailable { - Debug.log("updateAvailable!") - // If true, there is an update available. Open the UpdatesView with download button. - // This part will depend on your app's architecture. - // You might post a notification, use a state manager, etc., to switch the view. + do { + let updateAvailable = try await checkForUpdates() + await MainActor.run { + self.isCheckingForUpdate = false + if updateAvailable { + Debug.log("updateAvailable!") + } + self.lastCheckedTimestamp = Date() + self.saveSettings() + } + } catch { + await MainActor.run { + self.checkForUpdatesErrorMessage = error.localizedDescription + self.isCheckingForUpdate = false + } } - - lastCheckedTimestamp = Date() - saveSettings() } private func shouldCheckForUpdates() -> Bool { @@ -80,7 +86,7 @@ class UpdateManager: ObservableObject { } } - func checkForUpdates() async -> Bool { + func checkForUpdates() async throws -> Bool { guard let url = URL(string: "https://github.com/revblaze/ReleaseParsingTest/releases") else { Debug.log("Invalid URL") return false diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index ceadc73c..9bc64572 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -79,6 +79,7 @@ struct UpdatesView: View { } } .padding(.bottom, 10) + .disabled(updateManager.isCheckingForUpdate) if let lastChecked = updateManager.lastCheckedTimestamp { Text("Last checked: \(lastChecked, formatter: itemFormatter)") From f6fb6f6619f8a721b2161f44ac8ca70a57a1510f Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 10:13:08 -0700 Subject: [PATCH 11/39] refactor: implement checkForUpdateErrorMessage --- .../Views/WindowViews/UpdatesView/UpdatesView.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index 9bc64572..e8b05cff 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -90,6 +90,16 @@ struct UpdatesView: View { .font(.footnote) .foregroundStyle(Color.secondary) } + + if let checkForUpdateError = updateManager.checkForUpdatesErrorMessage { + Text(checkForUpdateError) + .padding(.vertical, 4) + .font(.footnote) + .foregroundStyle(Color.red) + .onAppear { + Delay.by(7) { updateManager.checkForUpdatesErrorMessage = nil } + } + } } .padding() .frame(width: 400, height: showUpdateFrequencySection ? expandedFrameHeight : initialFrameHeight) From b7ad959dd79935017dda946d0df887a33342555e Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 10:42:48 -0700 Subject: [PATCH 12/39] fix: section header parsing --- .../UpdatesView/UpdateManager.swift | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index f28541a9..e3281398 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -92,20 +92,26 @@ class UpdateManager: ObservableObject { return false } - guard let html = await fetchReleasesPage(url: url) else { - Debug.log("Failed to fetch releases page") + do { + let html = try await fetchReleasesPage(url: url) + Debug.log("Fetched HTML successfully") + + let releases = parseReleases(from: html) + if !releases.isEmpty { + for release in releases { + Debug.log("GitReleases\n > Title: \(release.releaseTitle)\n > Build Number: \(release.releaseBuildNumber)") + } + return true + } else { + Debug.log("No releases found or parsing failed") + return false + } + } catch { + Debug.log("Failed to fetch releases page or parse HTML: \(error)") return false } - - let releases = parseReleases(from: html) - for release in releases { - Debug.log("Title: \(release.releaseTitle), Build Number: \(release.releaseBuildNumber)") - } - - // Placeholder logic to determine if an update is available - // You would compare the fetched releases against the current app version and build number - return !releases.isEmpty } + } extension UpdateManager { @@ -131,34 +137,27 @@ struct GitRelease { extension UpdateManager { - func fetchReleasesPage(url: URL) async -> String? { - do { - let (data, _) = try await URLSession.shared.data(from: url) - return String(decoding: data, as: UTF8.self) - } catch { - Debug.log("Error fetching releases page: \(error)") - return nil - } + func fetchReleasesPage(url: URL) async throws -> String { + let (data, _) = try await URLSession.shared.data(from: url) + return String(decoding: data, as: UTF8.self) } func parseReleases(from html: String) -> [GitRelease] { - let sections = html.components(separatedBy: "
"), - let titleEndRange = section.range(of: "") { - let title = String(section[titleStartRange.upperBound..", range: titleStartRange.upperBound..", range: titleCloseTagRange.upperBound..b"), - let buildNumberEndRange = section.range(of: "", range: buildNumberStartRange.lowerBound..", range: buildNumberStartRange.upperBound.. Date: Sun, 3 Mar 2024 10:51:36 -0700 Subject: [PATCH 13/39] refactor: extractTitle, extractBuildNumber --- .../UpdatesView/UpdateManager.swift | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index e3281398..ce42a1e6 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -147,21 +147,33 @@ extension UpdateManager { var releases: [GitRelease] = [] for section in sections { - if let titleStartRange = section.range(of: "", range: titleStartRange.upperBound..", range: titleCloseTagRange.upperBound..b"), - let buildNumberEndRange = section.range(of: "", range: buildNumberStartRange.upperBound.. String? { + guard let titleStartRange = section.range(of: "", range: titleStartRange.upperBound..", range: titleCloseTagRange.upperBound.. Int? { + guard let buildNumberStartRange = section.range(of: "b"), + let buildNumberEndRange = section.range(of: "", range: buildNumberStartRange.upperBound.. Date: Sun, 3 Mar 2024 11:27:32 -0700 Subject: [PATCH 14/39] refactor: extractReleaseDate, extractReleaseTag --- .../UpdatesView/UpdateManager.swift | 63 +++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index ce42a1e6..9cac864a 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -99,7 +99,7 @@ class UpdateManager: ObservableObject { let releases = parseReleases(from: html) if !releases.isEmpty { for release in releases { - Debug.log("GitReleases\n > Title: \(release.releaseTitle)\n > Build Number: \(release.releaseBuildNumber)") + Debug.log("GitReleases\n > Title: \(release.releaseTitle)\n > Build Number: \(release.releaseBuildNumber)\n > Release Date: \(release.releaseDate)\n Release Tag: \(release.releaseTag)") } return true } else { @@ -132,7 +132,6 @@ struct GitRelease { var releaseDate: String var releaseTag: String var releaseBuildNumber: Int - var releaseAssets: [String: String] } @@ -147,9 +146,16 @@ extension UpdateManager { var releases: [GitRelease] = [] for section in sections { - if let title = extractTitle(from: section), - let buildNumber = extractBuildNumber(from: section) { - let release = GitRelease(releaseTitle: title, releaseDate: "", releaseTag: "", releaseBuildNumber: buildNumber, releaseAssets: [:]) + let title = extractTitle(from: section) + let buildNumber = extractBuildNumber(from: section) + let releaseDate = extractReleaseDate(from: section) + let releaseTag = extractReleaseTag(from: section) + + if let title = title, let buildNumber = buildNumber { + let release = GitRelease(releaseTitle: title, + releaseDate: releaseDate ?? "Unknown Date", + releaseTag: releaseTag ?? "Unknown Tag", + releaseBuildNumber: buildNumber) releases.append(release) } } @@ -158,6 +164,7 @@ extension UpdateManager { } + func extractTitle(from section: String) -> String? { guard let titleStartRange = section.range(of: "", range: titleStartRange.upperBound.. String? { + Debug.log("Attempting to extract release date from section: \(section.prefix(500))") + + guard let dateStartRange = section.range(of: " tag found in section.") + return nil + } + + let dateStartIndex = section.index(dateStartRange.upperBound, offsetBy: 0) + if let dateEndIndex = section[dateStartIndex...].firstIndex(of: "\"") { + let date = section[dateStartIndex.. String? { + Debug.log("Attempting to extract release tag from section: \(section.prefix(2000))") + + if let tagUrlStartRange = section.range(of: "href=\"/revblaze/ReleaseParsingTest/tree/") { + let tagUrlEndIndex = section[tagUrlStartRange.upperBound...].firstIndex(of: "\"") ?? section.endIndex + let tagUrl = section[tagUrlStartRange.upperBound.. tag with release tag found in section.") + } + + return nil + } + + + + + } From 5246fc20c9aa496ee4a8f9380d28ca4612297292 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 11:52:38 -0700 Subject: [PATCH 15/39] refactor: extractReleaseDownloadUrl --- .../UpdatesView/UpdateManager.swift | 69 ++++++++++++------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index 9cac864a..8b657751 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -99,7 +99,7 @@ class UpdateManager: ObservableObject { let releases = parseReleases(from: html) if !releases.isEmpty { for release in releases { - Debug.log("GitReleases\n > Title: \(release.releaseTitle)\n > Build Number: \(release.releaseBuildNumber)\n > Release Date: \(release.releaseDate)\n Release Tag: \(release.releaseTag)") + Debug.log("GitReleases\n > Title: \(String(describing: release.releaseTitle))\n > Build Number: \(String(describing: release.releaseBuildNumber))\n > Release Date: \(String(describing: release.releaseDate))\n > Release Tag: \(String(describing: release.releaseTag))\n > Download URL: \(String(describing: release.releaseDownloadUrlString))") } return true } else { @@ -128,10 +128,11 @@ extension UpdateManager { } struct GitRelease { - var releaseTitle: String - var releaseDate: String - var releaseTag: String - var releaseBuildNumber: Int + var releaseTitle: String? + var releaseDate: String? + var releaseTag: String? + var releaseBuildNumber: Int? + var releaseDownloadUrlString: String? } @@ -150,12 +151,14 @@ extension UpdateManager { let buildNumber = extractBuildNumber(from: section) let releaseDate = extractReleaseDate(from: section) let releaseTag = extractReleaseTag(from: section) + let releaseDownloadUrlString = extractReleaseDownloadUrl(from: section) if let title = title, let buildNumber = buildNumber { let release = GitRelease(releaseTitle: title, - releaseDate: releaseDate ?? "Unknown Date", - releaseTag: releaseTag ?? "Unknown Tag", - releaseBuildNumber: buildNumber) + releaseDate: releaseDate, + releaseTag: releaseTag, + releaseBuildNumber: buildNumber, + releaseDownloadUrlString: releaseDownloadUrlString) releases.append(release) } } @@ -184,7 +187,6 @@ extension UpdateManager { } func extractReleaseDate(from section: String) -> String? { - Debug.log("Attempting to extract release date from section: \(section.prefix(500))") guard let dateStartRange = section.range(of: " tag found in section.") @@ -205,28 +207,47 @@ extension UpdateManager { func extractReleaseTag(from section: String) -> String? { - Debug.log("Attempting to extract release tag from section: \(section.prefix(2000))") + let pattern = #"]+octicon-tag[^>]+>\s*]*>\s*([^<]+)"# - if let tagUrlStartRange = section.range(of: "href=\"/revblaze/ReleaseParsingTest/tree/") { - let tagUrlEndIndex = section[tagUrlStartRange.upperBound...].firstIndex(of: "\"") ?? section.endIndex - let tagUrl = section[tagUrlStartRange.upperBound.. tag with release tag found in section.") + } catch { + Debug.log("Regex error: \(error)") } + Debug.log("Failed to extract release tag.") return nil } - - - + func extractReleaseDownloadUrl(from section: String) -> String? { + let pattern = #"Download SwiftDiffusion.app"# + + do { + let regex = try NSRegularExpression(pattern: pattern, options: []) + let nsRange = NSRange(section.startIndex.. Date: Sun, 3 Mar 2024 12:02:50 -0700 Subject: [PATCH 16/39] feat: GitHubReleaseFetcher --- SwiftDiffusion.xcodeproj/project.pbxproj | 4 + .../UpdatesView/GitHubReleaseFetcher.swift | 151 ++++++++++++++++++ .../UpdatesView/UpdateManager.swift | 151 +----------------- 3 files changed, 163 insertions(+), 143 deletions(-) create mode 100644 SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift diff --git a/SwiftDiffusion.xcodeproj/project.pbxproj b/SwiftDiffusion.xcodeproj/project.pbxproj index c3d59f34..294ff8b9 100644 --- a/SwiftDiffusion.xcodeproj/project.pbxproj +++ b/SwiftDiffusion.xcodeproj/project.pbxproj @@ -109,6 +109,7 @@ 29C06C2B2B93DD0F00F950C4 /* BetaOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C2A2B93DD0F00F950C4 /* BetaOnboardingView.swift */; }; 29C06C2D2B940D8200F950C4 /* BlueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C2C2B940D8200F950C4 /* BlueButton.swift */; }; 29C06C2F2B94C55900F950C4 /* UpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */; }; + 29C06C312B95003600F950C4 /* GitHubReleaseFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C302B95003600F950C4 /* GitHubReleaseFetcher.swift */; }; 29C0F35F2B79BDA80017DD6B /* NotificationUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F35E2B79BDA80017DD6B /* NotificationUtility.swift */; }; 29C0F3632B79C8020017DD6B /* FullscreenImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3622B79C8020017DD6B /* FullscreenImageView.swift */; }; 29C0F3652B79C81F0017DD6B /* ImageWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3642B79C81F0017DD6B /* ImageWindowManager.swift */; }; @@ -250,6 +251,7 @@ 29C06C2A2B93DD0F00F950C4 /* BetaOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BetaOnboardingView.swift; sourceTree = ""; }; 29C06C2C2B940D8200F950C4 /* BlueButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueButton.swift; sourceTree = ""; }; 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateManager.swift; sourceTree = ""; }; + 29C06C302B95003600F950C4 /* GitHubReleaseFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubReleaseFetcher.swift; sourceTree = ""; }; 29C0F35E2B79BDA80017DD6B /* NotificationUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationUtility.swift; sourceTree = ""; }; 29C0F3622B79C8020017DD6B /* FullscreenImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenImageView.swift; sourceTree = ""; }; 29C0F3642B79C81F0017DD6B /* ImageWindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageWindowManager.swift; sourceTree = ""; }; @@ -919,6 +921,7 @@ children = ( 29C0F36E2B7A5C3D0017DD6B /* UpdatesView.swift */, 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */, + 29C06C302B95003600F950C4 /* GitHubReleaseFetcher.swift */, ); path = UpdatesView; sourceTree = ""; @@ -1146,6 +1149,7 @@ 2916F33D2B72867C001D14B9 /* FileRowView.swift in Sources */, 2952BAD72B71AEFA002FC885 /* PromptView.swift in Sources */, 29B64A482B8D480D00B0012D /* CachedPreviewImageView.swift in Sources */, + 29C06C312B95003600F950C4 /* GitHubReleaseFetcher.swift in Sources */, 290A34B42B7D283600963C6E /* SidebarMockDataController.swift in Sources */, 292A30DD2B9017F700344DA8 /* WorkspaceItemView.swift in Sources */, 29A7BDE12B8A40F200F9144B /* ApiCheckpointRow.swift in Sources */, diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift new file mode 100644 index 00000000..252b3e88 --- /dev/null +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift @@ -0,0 +1,151 @@ +// +// GitHubReleaseFetcher.swift +// SwiftDiffusion +// +// Created by Justin Bush on 3/3/24. +// + +import Foundation + +struct GitRelease { + var releaseTitle: String? + var releaseDate: String? + var releaseTag: String? + var releaseBuildNumber: Int? + var releaseDownloadUrlString: String? +} + +class GitHubReleaseFetcher { + let urlString: String + + init(urlString: String) { + self.urlString = urlString + } + + func fetchReleasesPage() async throws -> String { + guard let url = URL(string: urlString) else { + throw URLError(.badURL) + } + + let (data, _) = try await URLSession.shared.data(from: url) + return String(decoding: data, as: UTF8.self) + } + + func checkForUpdates() async throws -> [GitRelease] { + let html = try await fetchReleasesPage() + return parseReleases(from: html) + } + + func fetchReleasesPage(url: URL) async throws -> String { + let (data, _) = try await URLSession.shared.data(from: url) + return String(decoding: data, as: UTF8.self) + } + + func parseReleases(from html: String) -> [GitRelease] { + let sections = html.components(separatedBy: "
", range: titleStartRange.upperBound..", range: titleCloseTagRange.upperBound.. Int? { + guard let buildNumberStartRange = section.range(of: "b"), + let buildNumberEndRange = section.range(of: "", range: buildNumberStartRange.upperBound.. String? { + + guard let dateStartRange = section.range(of: " tag found in section.") + return nil + } + + let dateStartIndex = section.index(dateStartRange.upperBound, offsetBy: 0) + if let dateEndIndex = section[dateStartIndex...].firstIndex(of: "\"") { + let date = section[dateStartIndex.. String? { + let pattern = #"]+octicon-tag[^>]+>\s*]*>\s*([^<]+)"# + + do { + let regex = try NSRegularExpression(pattern: pattern, options: []) + let nsRange = NSRange(section.startIndex.. String? { + let pattern = #"Download SwiftDiffusion.app"# + + do { + let regex = try NSRegularExpression(pattern: pattern, options: []) + let nsRange = NSRange(section.startIndex.. Bool { - guard let url = URL(string: "https://github.com/revblaze/ReleaseParsingTest/releases") else { - Debug.log("Invalid URL") - return false - } + let fetcher = GitHubReleaseFetcher(urlString: "https://github.com/revblaze/ReleaseParsingTest/releases") + let releases = try await fetcher.checkForUpdates() - do { - let html = try await fetchReleasesPage(url: url) - Debug.log("Fetched HTML successfully") + if !releases.isEmpty { - let releases = parseReleases(from: html) - if !releases.isEmpty { - for release in releases { - Debug.log("GitReleases\n > Title: \(String(describing: release.releaseTitle))\n > Build Number: \(String(describing: release.releaseBuildNumber))\n > Release Date: \(String(describing: release.releaseDate))\n > Release Tag: \(String(describing: release.releaseTag))\n > Download URL: \(String(describing: release.releaseDownloadUrlString))") - } - return true - } else { - Debug.log("No releases found or parsing failed") - return false + for release in releases { + Debug.log("GitReleases\n > Title: \(String(describing: release.releaseTitle))\n > Build Number: \(String(describing: release.releaseBuildNumber))\n > Release Date: \(String(describing: release.releaseDate))\n > Release Tag: \(String(describing: release.releaseTag))\n > Download URL: \(String(describing: release.releaseDownloadUrlString))") } - } catch { - Debug.log("Failed to fetch releases page or parse HTML: \(error)") + + return true + } else { return false } } @@ -126,128 +116,3 @@ extension UpdateManager { } } } - -struct GitRelease { - var releaseTitle: String? - var releaseDate: String? - var releaseTag: String? - var releaseBuildNumber: Int? - var releaseDownloadUrlString: String? -} - - -extension UpdateManager { - func fetchReleasesPage(url: URL) async throws -> String { - let (data, _) = try await URLSession.shared.data(from: url) - return String(decoding: data, as: UTF8.self) - } - - func parseReleases(from html: String) -> [GitRelease] { - let sections = html.components(separatedBy: "
", range: titleStartRange.upperBound..", range: titleCloseTagRange.upperBound.. Int? { - guard let buildNumberStartRange = section.range(of: "b"), - let buildNumberEndRange = section.range(of: "", range: buildNumberStartRange.upperBound.. String? { - - guard let dateStartRange = section.range(of: " tag found in section.") - return nil - } - - let dateStartIndex = section.index(dateStartRange.upperBound, offsetBy: 0) - if let dateEndIndex = section[dateStartIndex...].firstIndex(of: "\"") { - let date = section[dateStartIndex.. String? { - let pattern = #"]+octicon-tag[^>]+>\s*]*>\s*([^<]+)"# - - do { - let regex = try NSRegularExpression(pattern: pattern, options: []) - let nsRange = NSRange(section.startIndex.. String? { - let pattern = #"Download SwiftDiffusion.app"# - - do { - let regex = try NSRegularExpression(pattern: pattern, options: []) - let nsRange = NSRange(section.startIndex.. Date: Sun, 3 Mar 2024 12:16:28 -0700 Subject: [PATCH 17/39] refactor: compareCurrentAppBuildToGitHubReleases(), latestRelease --- .../UpdatesView/GitHubReleaseFetcher.swift | 20 +++++++-------- .../UpdatesView/UpdateManager.swift | 25 ++++++++++++++++--- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift index 252b3e88..fd5107eb 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift @@ -7,7 +7,7 @@ import Foundation -struct GitRelease { +struct GitHubRelease { var releaseTitle: String? var releaseDate: String? var releaseTag: String? @@ -31,7 +31,7 @@ class GitHubReleaseFetcher { return String(decoding: data, as: UTF8.self) } - func checkForUpdates() async throws -> [GitRelease] { + func checkForUpdates() async throws -> [GitHubRelease] { let html = try await fetchReleasesPage() return parseReleases(from: html) } @@ -41,9 +41,9 @@ class GitHubReleaseFetcher { return String(decoding: data, as: UTF8.self) } - func parseReleases(from html: String) -> [GitRelease] { + func parseReleases(from html: String) -> [GitHubRelease] { let sections = html.components(separatedBy: "
", range: titleStartRange.upperBound.. Title: \(String(describing: release.releaseTitle))\n > Build Number: \(String(describing: release.releaseBuildNumber))\n > Release Date: \(String(describing: release.releaseDate))\n > Release Tag: \(String(describing: release.releaseTag))\n > Download URL: \(String(describing: release.releaseDownloadUrlString))") + } else { + Debug.log("latestRelease: GitRelease = nil") + } } self.lastCheckedTimestamp = Date() self.saveSettings() @@ -91,17 +98,29 @@ class UpdateManager: ObservableObject { let releases = try await fetcher.checkForUpdates() if !releases.isEmpty { + githubReleases = releases - for release in releases { + for release in githubReleases { Debug.log("GitReleases\n > Title: \(String(describing: release.releaseTitle))\n > Build Number: \(String(describing: release.releaseBuildNumber))\n > Release Date: \(String(describing: release.releaseDate))\n > Release Tag: \(String(describing: release.releaseTag))\n > Download URL: \(String(describing: release.releaseDownloadUrlString))") } - return true + return compareCurrentAppBuildToGitHubReleases() } else { return false } } + func compareCurrentAppBuildToGitHubReleases() -> Bool { + guard let highestBuildRelease = githubReleases + .filter({ $0.releaseBuildNumber ?? 0 > AppInfo.buildInt }) + .max(by: { ($0.releaseBuildNumber ?? 0) < ($1.releaseBuildNumber ?? 0) }) else { + return false + } + + latestRelease = highestBuildRelease + return true + } + } extension UpdateManager { From d17661204bf3a1234c1b187ac63885c83966cc09 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 12:33:16 -0700 Subject: [PATCH 18/39] fix: super sneaky SidebarFolder equatable condition --- SwiftDiffusion/Models/SidebarModels/SidebarFolder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwiftDiffusion/Models/SidebarModels/SidebarFolder.swift b/SwiftDiffusion/Models/SidebarModels/SidebarFolder.swift index 5a659742..57bd7337 100644 --- a/SwiftDiffusion/Models/SidebarModels/SidebarFolder.swift +++ b/SwiftDiffusion/Models/SidebarModels/SidebarFolder.swift @@ -32,7 +32,7 @@ class SidebarFolder: Identifiable { extension SidebarFolder: Equatable { static func == (lhs: SidebarFolder, rhs: SidebarFolder) -> Bool { - lhs.name == rhs.name + lhs.id == rhs.id } } From 33441be848660840217a30a5fd950726279d2583 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 12:33:29 -0700 Subject: [PATCH 19/39] refactor: extend GitHubRelease to be equatable --- .../WindowViews/UpdatesView/GitHubReleaseFetcher.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift index fd5107eb..56b91b55 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift @@ -15,6 +15,12 @@ struct GitHubRelease { var releaseDownloadUrlString: String? } +extension GitHubRelease: Equatable { + static func == (lhs: GitHubRelease, rhs: GitHubRelease) -> Bool { + lhs.releaseBuildNumber == rhs.releaseBuildNumber + } +} + class GitHubReleaseFetcher { let urlString: String From d54300fd219c17a1efa5a33be5b6b28308bf45db Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 12:46:33 -0700 Subject: [PATCH 20/39] refactor: UpdateViewState, latestVersion --- .../UpdatesView/UpdateManager.swift | 15 +++- .../WindowViews/UpdatesView/UpdatesView.swift | 74 ++++++++++++++++++- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index 84ec3740..a743e691 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -25,6 +25,18 @@ class UpdateManager: ObservableObject { @Published var githubReleases: [GitHubRelease] = [] @Published var latestRelease: GitHubRelease? = nil + var currentBuildIsLatestVersion: Bool? { + guard let latestBuildNumber = latestRelease?.releaseBuildNumber else { + return nil + } + if latestBuildNumber > AppInfo.buildInt { + return false + } else if latestBuildNumber <= AppInfo.buildInt { + return true + } + return nil + } + init() { loadSettings() } @@ -103,7 +115,6 @@ class UpdateManager: ObservableObject { for release in githubReleases { Debug.log("GitReleases\n > Title: \(String(describing: release.releaseTitle))\n > Build Number: \(String(describing: release.releaseBuildNumber))\n > Release Date: \(String(describing: release.releaseDate))\n > Release Tag: \(String(describing: release.releaseTag))\n > Download URL: \(String(describing: release.releaseDownloadUrlString))") } - return compareCurrentAppBuildToGitHubReleases() } else { return false @@ -112,7 +123,7 @@ class UpdateManager: ObservableObject { func compareCurrentAppBuildToGitHubReleases() -> Bool { guard let highestBuildRelease = githubReleases - .filter({ $0.releaseBuildNumber ?? 0 > AppInfo.buildInt }) + .filter({ $0.releaseBuildNumber ?? 0 >= AppInfo.buildInt }) .max(by: { ($0.releaseBuildNumber ?? 0) < ($1.releaseBuildNumber ?? 0) }) else { return false } diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index e8b05cff..babc5949 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -22,6 +22,50 @@ struct AppInfo { } } +enum UpdateViewState { + case defaultState + case latestVersion + case checkingForUpdate + case newVersionAvailable + + var updateStatusText: String { + switch self { + case .defaultState: "" + case .latestVersion: "You are on the latest version." + case .checkingForUpdate: "Checking for new update..." + case .newVersionAvailable: "There's a new version available!" + } + } + + var updateSymbol: String { + switch self { + case .defaultState: "icloud.slash.fill" + case .latestVersion: "checkmark.circle.fill" + case .checkingForUpdate: "arrow.triangle.2.circlepath.icloud.fill" + case .newVersionAvailable: "icloud.and.arrow.down.fill" + } + } + + var updateSymbolColor: Color { + switch self { + case .defaultState: Color.secondary + case .latestVersion: Color.green + case .checkingForUpdate: Color.yellow + case .newVersionAvailable: Color.blue + } + } + + var mainButtonText: String { + switch self { + case .defaultState: "Check for Updates" + case .latestVersion: "Check for Updates" + case .checkingForUpdate: "Checking for Updates..." + case .newVersionAvailable: "Download Now" + } + } + +} + struct UpdatesView: View { @EnvironmentObject var updateManager: UpdateManager @State private var showUpdateFrequencySection: Bool = false @@ -31,10 +75,12 @@ struct UpdatesView: View { initialFrameHeight + updateFrequencySectionHeight } + @State var updateViewState: UpdateViewState = .defaultState + var body: some View { VStack { - ToggleWithLabel(isToggled: .constant(true), header: "Automatically check for updates", description: "Checks for new releases on GitHub", showAllDescriptions: true) + ToggleWithLabel(isToggled: .constant(true), header: "Automatically check for new updates", description: "Checks for new releases on GitHub", showAllDescriptions: true) .padding(.top, 10) if showUpdateFrequencySection { @@ -73,13 +119,33 @@ struct UpdatesView: View { Spacer() - Button("Check for Updates") { - Task { - await updateManager.checkForUpdatesIfNeeded(force: true) + Button(updateViewState.mainButtonText) { + if updateViewState == .newVersionAvailable { + if let latestRelease = updateManager.latestRelease, let releaseUrl = latestRelease.releaseDownloadUrlString, let url = URL(string: releaseUrl) { + NSWorkspace.shared.open(url) + } + } else { + Task { + await updateManager.checkForUpdatesIfNeeded(force: true) + } } } .padding(.bottom, 10) .disabled(updateManager.isCheckingForUpdate) + .onChange(of: updateManager.isCheckingForUpdate) { + updateViewState = .checkingForUpdate + } + .onChange(of: updateManager.latestRelease) { + if let currentBuildIsLatestVersion = updateManager.currentBuildIsLatestVersion { + if currentBuildIsLatestVersion == false { + updateViewState = .newVersionAvailable + } else { + updateViewState = .latestVersion + } + } else { + //updateViewState = .defaultState + } + } if let lastChecked = updateManager.lastCheckedTimestamp { Text("Last checked: \(lastChecked, formatter: itemFormatter)") From 324fb5cba711037c8cfa5dcff98355102641ce8b Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 13:05:34 -0700 Subject: [PATCH 21/39] refactor: updateStateBasedOnManager, updateManager.isCheckingForUpdate --- .../WindowViews/UpdatesView/UpdatesView.swift | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index babc5949..af1fa9d4 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -28,25 +28,25 @@ enum UpdateViewState { case checkingForUpdate case newVersionAvailable - var updateStatusText: String { + var statusText: String { switch self { - case .defaultState: "" + case .defaultState: "Haven't checked for updates." case .latestVersion: "You are on the latest version." case .checkingForUpdate: "Checking for new update..." case .newVersionAvailable: "There's a new version available!" } } - var updateSymbol: String { + var symbol: String { switch self { case .defaultState: "icloud.slash.fill" case .latestVersion: "checkmark.circle.fill" case .checkingForUpdate: "arrow.triangle.2.circlepath.icloud.fill" - case .newVersionAvailable: "icloud.and.arrow.down.fill" + case .newVersionAvailable: "exclamationmark.circle.fill" } } - var updateSymbolColor: Color { + var symbolColor: Color { switch self { case .defaultState: Color.secondary case .latestVersion: Color.green @@ -104,11 +104,11 @@ struct UpdatesView: View { Spacer() HStack(alignment: .top) { - Image(systemName: "checkmark.circle.fill") - .foregroundStyle(Color.green) + Image(systemName: updateViewState.symbol) + .foregroundStyle(updateViewState.symbolColor) .padding(.trailing, 2) VStack(alignment: .leading) { - Text("You are on the latest version.") + Text(updateViewState.statusText) .bold() .padding(.bottom, 1) Text("SwiftDiffusion \(AppInfo.versionAndBuild)") @@ -133,17 +133,19 @@ struct UpdatesView: View { .padding(.bottom, 10) .disabled(updateManager.isCheckingForUpdate) .onChange(of: updateManager.isCheckingForUpdate) { - updateViewState = .checkingForUpdate + if updateManager.isCheckingForUpdate { + updateViewState = .checkingForUpdate + } } - .onChange(of: updateManager.latestRelease) { + .onChange(of: updateManager.currentBuildIsLatestVersion) { + Debug.log(".onChange(of: updateManager.currentBuildIsLatestVersion): \(String(describing: updateManager.currentBuildIsLatestVersion))") + if let currentBuildIsLatestVersion = updateManager.currentBuildIsLatestVersion { if currentBuildIsLatestVersion == false { updateViewState = .newVersionAvailable } else { updateViewState = .latestVersion } - } else { - //updateViewState = .defaultState } } @@ -170,6 +172,9 @@ struct UpdatesView: View { .padding() .frame(width: 400, height: showUpdateFrequencySection ? expandedFrameHeight : initialFrameHeight) .animation(.easeInOut, value: showUpdateFrequencySection) + .onAppear { + + } .navigationTitle("Updates") .toolbar { @@ -194,6 +199,20 @@ struct UpdatesView: View { } } } + func updateStateBasedOnManager() { + if updateManager.isCheckingForUpdate { + updateViewState = .checkingForUpdate + } else if let currentBuildIsLatestVersion = updateManager.currentBuildIsLatestVersion { + if currentBuildIsLatestVersion == false { + updateViewState = .newVersionAvailable + } else { + updateViewState = .latestVersion + } + } else { + updateViewState = .defaultState + } + } + private var itemFormatter: DateFormatter { let formatter = DateFormatter() formatter.dateStyle = .medium From 02177d4706b753a2275e50f8114e5d378d10a61d Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 13:07:45 -0700 Subject: [PATCH 22/39] refactor: call updateViewStateBasedOnManager() on observable change --- .../WindowViews/UpdatesView/UpdatesView.swift | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index af1fa9d4..24dfb494 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -133,20 +133,10 @@ struct UpdatesView: View { .padding(.bottom, 10) .disabled(updateManager.isCheckingForUpdate) .onChange(of: updateManager.isCheckingForUpdate) { - if updateManager.isCheckingForUpdate { - updateViewState = .checkingForUpdate - } + updateViewStateBasedOnManager() } .onChange(of: updateManager.currentBuildIsLatestVersion) { - Debug.log(".onChange(of: updateManager.currentBuildIsLatestVersion): \(String(describing: updateManager.currentBuildIsLatestVersion))") - - if let currentBuildIsLatestVersion = updateManager.currentBuildIsLatestVersion { - if currentBuildIsLatestVersion == false { - updateViewState = .newVersionAvailable - } else { - updateViewState = .latestVersion - } - } + updateViewStateBasedOnManager() } if let lastChecked = updateManager.lastCheckedTimestamp { @@ -173,7 +163,7 @@ struct UpdatesView: View { .frame(width: 400, height: showUpdateFrequencySection ? expandedFrameHeight : initialFrameHeight) .animation(.easeInOut, value: showUpdateFrequencySection) .onAppear { - + updateViewStateBasedOnManager() } .navigationTitle("Updates") @@ -199,7 +189,7 @@ struct UpdatesView: View { } } } - func updateStateBasedOnManager() { + func updateViewStateBasedOnManager() { if updateManager.isCheckingForUpdate { updateViewState = .checkingForUpdate } else if let currentBuildIsLatestVersion = updateManager.currentBuildIsLatestVersion { From d94cef72039bbb140321c11cba544edd6c365333 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 13:47:00 -0700 Subject: [PATCH 23/39] refactor: checkForUpdatesIfAutomaticUpdatesAreEnabled onAppear --- SwiftDiffusion.xcodeproj/project.pbxproj | 8 +++---- .../MainViews/ContentView/ContentView.swift | 14 +++++++++++++ .../UpdatesView/UpdateManager.swift | 9 +++++--- .../WindowViews/UpdatesView/UpdatesView.swift | 21 ++++++++++--------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/SwiftDiffusion.xcodeproj/project.pbxproj b/SwiftDiffusion.xcodeproj/project.pbxproj index 294ff8b9..5a8d828b 100644 --- a/SwiftDiffusion.xcodeproj/project.pbxproj +++ b/SwiftDiffusion.xcodeproj/project.pbxproj @@ -1319,7 +1319,7 @@ CODE_SIGN_ENTITLEMENTS = SwiftDiffusion/SwiftDiffusion.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1100; + CURRENT_PROJECT_VERSION = 1000; DEVELOPMENT_ASSET_PATHS = "\"SwiftDiffusion/Preview Content\""; DEVELOPMENT_TEAM = 85N3S3DG8M; ENABLE_HARDENED_RUNTIME = YES; @@ -1331,7 +1331,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.1.1; + MARKETING_VERSION = 0.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.buzsh.SwiftDiffusion; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1347,7 +1347,7 @@ CODE_SIGN_ENTITLEMENTS = SwiftDiffusion/SwiftDiffusion.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1100; + CURRENT_PROJECT_VERSION = 1000; DEVELOPMENT_ASSET_PATHS = "\"SwiftDiffusion/Preview Content\""; DEVELOPMENT_TEAM = 85N3S3DG8M; ENABLE_HARDENED_RUNTIME = YES; @@ -1359,7 +1359,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 0.1.1; + MARKETING_VERSION = 0.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.buzsh.SwiftDiffusion; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift b/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift index 5c841d56..288fd0f2 100644 --- a/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift +++ b/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift @@ -90,6 +90,10 @@ struct ContentView: View { .background(VisualEffectBlurView(material: .headerView, blendingMode: .behindWindow)) .navigationSplitViewStyle(.automatic) .onAppear { + if hasLaunchedBefore { + checkForUpdatesIfAutomaticUpdatesAreEnabled() + } + scriptManagerObserver = ScriptManagerObserver(scriptManager: scriptManager, userSettings: userSettings, checkpointsManager: checkpointsManager, loraModelsManager: loraModelsManager, vaeModelsManager: vaeModelsManager) if let directoryPath = userSettings.outputDirectoryUrl?.path { @@ -298,6 +302,16 @@ struct ContentView: View { } } + func checkForUpdatesIfAutomaticUpdatesAreEnabled() { + Task { + await updateManager.checkForUpdatesIfNeeded() + if let currentBuildIsLatestVersion = updateManager.currentBuildIsLatestVersion, + currentBuildIsLatestVersion == false { + WindowManager.shared.showUpdatesWindow(updateManager: updateManager) + } + } + } + } #Preview { diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift index a743e691..6636d2da 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift @@ -9,7 +9,8 @@ import Foundation import SwiftUI enum UpdateFrequency: String, CaseIterable { - case everyAppLaunch = "Every App Launch" + case everyAppLaunch = "On Launch" + case hourly = "Hourly" case daily = "Daily" case weekly = "Weekly" case everyOtherWeek = "Every Other Week" @@ -91,8 +92,11 @@ class UpdateManager: ObservableObject { switch updateCheckFrequency { case .everyAppLaunch: return true + case .hourly: + let hourAgo = calendar.date(byAdding: .hour, value: -1, to: now)! + return lastChecked < hourAgo case .daily: - return calendar.isDate(lastChecked, inSameDayAs: now) == false + return !calendar.isDate(lastChecked, inSameDayAs: now) case .weekly: return !calendar.isDate(lastChecked, equalTo: now, toGranularity: .weekOfYear) case .everyOtherWeek: @@ -131,7 +135,6 @@ class UpdateManager: ObservableObject { latestRelease = highestBuildRelease return true } - } extension UpdateManager { diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index 24dfb494..6c7905ca 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -184,22 +184,24 @@ struct UpdatesView: View { Image(systemName: "clock") .foregroundStyle(showUpdateFrequencySection ? .blue : .secondary) } - } } } } + func updateViewStateBasedOnManager() { - if updateManager.isCheckingForUpdate { - updateViewState = .checkingForUpdate - } else if let currentBuildIsLatestVersion = updateManager.currentBuildIsLatestVersion { - if currentBuildIsLatestVersion == false { - updateViewState = .newVersionAvailable + withAnimation { + if updateManager.isCheckingForUpdate { + updateViewState = .checkingForUpdate + } else if let currentBuildIsLatestVersion = updateManager.currentBuildIsLatestVersion { + if currentBuildIsLatestVersion == false { + updateViewState = .newVersionAvailable + } else { + updateViewState = .latestVersion + } } else { - updateViewState = .latestVersion + updateViewState = .defaultState } - } else { - updateViewState = .defaultState } } @@ -209,7 +211,6 @@ struct UpdatesView: View { formatter.timeStyle = .short return formatter } - } #Preview { From 173485971221f2636018d4e32e38554d7ecee210 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 13:48:32 -0700 Subject: [PATCH 24/39] refactor: UpdateViewState --- SwiftDiffusion.xcodeproj/project.pbxproj | 4 ++ .../UpdatesView/UpdateViewState.swift | 52 +++++++++++++++++++ .../WindowViews/UpdatesView/UpdatesView.swift | 44 ---------------- 3 files changed, 56 insertions(+), 44 deletions(-) create mode 100644 SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateViewState.swift diff --git a/SwiftDiffusion.xcodeproj/project.pbxproj b/SwiftDiffusion.xcodeproj/project.pbxproj index 5a8d828b..9eff4032 100644 --- a/SwiftDiffusion.xcodeproj/project.pbxproj +++ b/SwiftDiffusion.xcodeproj/project.pbxproj @@ -110,6 +110,7 @@ 29C06C2D2B940D8200F950C4 /* BlueButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C2C2B940D8200F950C4 /* BlueButton.swift */; }; 29C06C2F2B94C55900F950C4 /* UpdateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */; }; 29C06C312B95003600F950C4 /* GitHubReleaseFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C302B95003600F950C4 /* GitHubReleaseFetcher.swift */; }; + 29C06C332B95196200F950C4 /* UpdateViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C06C322B95196200F950C4 /* UpdateViewState.swift */; }; 29C0F35F2B79BDA80017DD6B /* NotificationUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F35E2B79BDA80017DD6B /* NotificationUtility.swift */; }; 29C0F3632B79C8020017DD6B /* FullscreenImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3622B79C8020017DD6B /* FullscreenImageView.swift */; }; 29C0F3652B79C81F0017DD6B /* ImageWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3642B79C81F0017DD6B /* ImageWindowManager.swift */; }; @@ -252,6 +253,7 @@ 29C06C2C2B940D8200F950C4 /* BlueButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueButton.swift; sourceTree = ""; }; 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateManager.swift; sourceTree = ""; }; 29C06C302B95003600F950C4 /* GitHubReleaseFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubReleaseFetcher.swift; sourceTree = ""; }; + 29C06C322B95196200F950C4 /* UpdateViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateViewState.swift; sourceTree = ""; }; 29C0F35E2B79BDA80017DD6B /* NotificationUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationUtility.swift; sourceTree = ""; }; 29C0F3622B79C8020017DD6B /* FullscreenImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenImageView.swift; sourceTree = ""; }; 29C0F3642B79C81F0017DD6B /* ImageWindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageWindowManager.swift; sourceTree = ""; }; @@ -919,6 +921,7 @@ 29C0F37B2B7AB37E0017DD6B /* UpdatesView */ = { isa = PBXGroup; children = ( + 29C06C322B95196200F950C4 /* UpdateViewState.swift */, 29C0F36E2B7A5C3D0017DD6B /* UpdatesView.swift */, 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */, 29C06C302B95003600F950C4 /* GitHubReleaseFetcher.swift */, @@ -1096,6 +1099,7 @@ 29A1A38A2B919E7100CC3D1B /* Sidebar+Preload.swift in Sources */, 297196652B719C32000F7960 /* ScriptManager+ServiceAvailability.swift in Sources */, 292CE8282B82430000116D60 /* SoundUtility.swift in Sources */, + 29C06C332B95196200F950C4 /* UpdateViewState.swift in Sources */, 2916F3342B7278FD001D14B9 /* SettingsView.swift in Sources */, 29F0D3032B76C9DD00BF56CE /* ModelLoadState.swift in Sources */, 29169D252B8FCB780099A9C5 /* Sidebar.swift in Sources */, diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateViewState.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateViewState.swift new file mode 100644 index 00000000..56469539 --- /dev/null +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateViewState.swift @@ -0,0 +1,52 @@ +// +// UpdateViewState.swift +// SwiftDiffusion +// +// Created by Justin Bush on 3/3/24. +// + +import SwiftUI + +enum UpdateViewState { + case defaultState + case latestVersion + case checkingForUpdate + case newVersionAvailable + + var statusText: String { + switch self { + case .defaultState: "Haven't checked for updates." + case .latestVersion: "You are on the latest version." + case .checkingForUpdate: "Checking for new update..." + case .newVersionAvailable: "There's a new version available!" + } + } + + var symbol: String { + switch self { + case .defaultState: "icloud.slash.fill" + case .latestVersion: "checkmark.circle.fill" + case .checkingForUpdate: "arrow.triangle.2.circlepath.icloud.fill" + case .newVersionAvailable: "exclamationmark.circle.fill" + } + } + + var symbolColor: Color { + switch self { + case .defaultState: Color.secondary + case .latestVersion: Color.green + case .checkingForUpdate: Color.yellow + case .newVersionAvailable: Color.blue + } + } + + var mainButtonText: String { + switch self { + case .defaultState: "Check for Updates" + case .latestVersion: "Check for Updates" + case .checkingForUpdate: "Checking for Updates..." + case .newVersionAvailable: "Download Now" + } + } + +} diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift index 6c7905ca..02fa454d 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift @@ -22,50 +22,6 @@ struct AppInfo { } } -enum UpdateViewState { - case defaultState - case latestVersion - case checkingForUpdate - case newVersionAvailable - - var statusText: String { - switch self { - case .defaultState: "Haven't checked for updates." - case .latestVersion: "You are on the latest version." - case .checkingForUpdate: "Checking for new update..." - case .newVersionAvailable: "There's a new version available!" - } - } - - var symbol: String { - switch self { - case .defaultState: "icloud.slash.fill" - case .latestVersion: "checkmark.circle.fill" - case .checkingForUpdate: "arrow.triangle.2.circlepath.icloud.fill" - case .newVersionAvailable: "exclamationmark.circle.fill" - } - } - - var symbolColor: Color { - switch self { - case .defaultState: Color.secondary - case .latestVersion: Color.green - case .checkingForUpdate: Color.yellow - case .newVersionAvailable: Color.blue - } - } - - var mainButtonText: String { - switch self { - case .defaultState: "Check for Updates" - case .latestVersion: "Check for Updates" - case .checkingForUpdate: "Checking for Updates..." - case .newVersionAvailable: "Download Now" - } - } - -} - struct UpdatesView: View { @EnvironmentObject var updateManager: UpdateManager @State private var showUpdateFrequencySection: Bool = false From 5b1deaa97dca76e00b018cdc6e5e3d4439bf4ce9 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 13:49:29 -0700 Subject: [PATCH 25/39] refactor: Update(s)View --- SwiftDiffusion.xcodeproj/project.pbxproj | 8 ++++---- .../UpdatesView/{UpdatesView.swift => UpdateView.swift} | 6 +++--- SwiftDiffusion/WindowManager.swift | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) rename SwiftDiffusion/Views/WindowViews/UpdatesView/{UpdatesView.swift => UpdateView.swift} (98%) diff --git a/SwiftDiffusion.xcodeproj/project.pbxproj b/SwiftDiffusion.xcodeproj/project.pbxproj index 9eff4032..8400961f 100644 --- a/SwiftDiffusion.xcodeproj/project.pbxproj +++ b/SwiftDiffusion.xcodeproj/project.pbxproj @@ -116,7 +116,7 @@ 29C0F3652B79C81F0017DD6B /* ImageWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3642B79C81F0017DD6B /* ImageWindowManager.swift */; }; 29C0F36B2B79D2CA0017DD6B /* FileNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F36A2B79D2CA0017DD6B /* FileNode.swift */; }; 29C0F36D2B79D2F80017DD6B /* FileHierarchy+Control.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F36C2B79D2F80017DD6B /* FileHierarchy+Control.swift */; }; - 29C0F36F2B7A5C3D0017DD6B /* UpdatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F36E2B7A5C3D0017DD6B /* UpdatesView.swift */; }; + 29C0F36F2B7A5C3D0017DD6B /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F36E2B7A5C3D0017DD6B /* UpdateView.swift */; }; 29C0F3712B7A62180017DD6B /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3702B7A62180017DD6B /* WindowManager.swift */; }; 29C0F3752B7A75960017DD6B /* CopyPasteUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3742B7A75960017DD6B /* CopyPasteUtility.swift */; }; 29C0F3772B7AA6310017DD6B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C0F3762B7AA6310017DD6B /* AppDelegate.swift */; }; @@ -259,7 +259,7 @@ 29C0F3642B79C81F0017DD6B /* ImageWindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageWindowManager.swift; sourceTree = ""; }; 29C0F36A2B79D2CA0017DD6B /* FileNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileNode.swift; sourceTree = ""; }; 29C0F36C2B79D2F80017DD6B /* FileHierarchy+Control.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileHierarchy+Control.swift"; sourceTree = ""; }; - 29C0F36E2B7A5C3D0017DD6B /* UpdatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatesView.swift; sourceTree = ""; }; + 29C0F36E2B7A5C3D0017DD6B /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = ""; }; 29C0F3702B7A62180017DD6B /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = ""; }; 29C0F3742B7A75960017DD6B /* CopyPasteUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyPasteUtility.swift; sourceTree = ""; }; 29C0F3762B7AA6310017DD6B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -922,7 +922,7 @@ isa = PBXGroup; children = ( 29C06C322B95196200F950C4 /* UpdateViewState.swift */, - 29C0F36E2B7A5C3D0017DD6B /* UpdatesView.swift */, + 29C0F36E2B7A5C3D0017DD6B /* UpdateView.swift */, 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */, 29C06C302B95003600F950C4 /* GitHubReleaseFetcher.swift */, ); @@ -1072,7 +1072,7 @@ 29169D0E2B8EAD740099A9C5 /* ImageInfo.swift in Sources */, 29C0F36B2B79D2CA0017DD6B /* FileNode.swift in Sources */, 29145E7D2B78111200BFA64B /* ToolbarButton.swift in Sources */, - 29C0F36F2B7A5C3D0017DD6B /* UpdatesView.swift in Sources */, + 29C0F36F2B7A5C3D0017DD6B /* UpdateView.swift in Sources */, 29708F942B803350004A95AD /* PasteGenerationDataStatusBar.swift in Sources */, 290A34B02B7C616A00963C6E /* MapModelData.swift in Sources */, 29F91D332B8D42390047B3C9 /* NSImage+Extensions.swift in Sources */, diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateView.swift similarity index 98% rename from SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift rename to SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateView.swift index 02fa454d..7024809e 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdatesView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateView.swift @@ -1,5 +1,5 @@ // -// UpdatesView.swift +// UpdateView.swift // SwiftDiffusion // // Created by Justin Bush on 2/12/24. @@ -22,7 +22,7 @@ struct AppInfo { } } -struct UpdatesView: View { +struct UpdateView: View { @EnvironmentObject var updateManager: UpdateManager @State private var showUpdateFrequencySection: Bool = false private let updateFrequencySectionHeight: CGFloat = 28 @@ -173,7 +173,7 @@ struct UpdatesView: View { let updateManager = UpdateManager() updateManager.loadSettings() - return UpdatesView() + return UpdateView() .frame(idealWidth: 400, idealHeight: 250) .environmentObject(updateManager) } diff --git a/SwiftDiffusion/WindowManager.swift b/SwiftDiffusion/WindowManager.swift index c5fd7563..05ea9e90 100644 --- a/SwiftDiffusion/WindowManager.swift +++ b/SwiftDiffusion/WindowManager.swift @@ -43,7 +43,7 @@ class WindowManager: NSObject, ObservableObject { betaOnboardingWindow?.makeKeyAndOrderFront(nil) } - /// Shows the updates window containing UpdatesView. If the window does not exist, it creates and configures a new window before displaying it. + /// Shows the updates window containing UpdateView. If the window does not exist, it creates and configures a new window before displaying it. func showUpdatesWindow(updateManager: UpdateManager) { if updatesWindow == nil { updatesWindow = NSWindow( @@ -51,7 +51,7 @@ class WindowManager: NSObject, ObservableObject { styleMask: [.titled, .closable, .resizable, .miniaturizable], backing: .buffered, defer: false) updatesWindow?.center() - updatesWindow?.contentView = NSHostingView(rootView: UpdatesView().environmentObject(updateManager)) + updatesWindow?.contentView = NSHostingView(rootView: UpdateView().environmentObject(updateManager)) updatesWindow?.isReleasedWhenClosed = false updatesWindow?.delegate = self updatesWindow?.standardWindowButton(.zoomButton)?.isHidden = true From bd60d0146c1a74ef779011ac01e3186cf9bc02ef Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 13:50:06 -0700 Subject: [PATCH 26/39] refactor: naming scheme for UpdateView --- SwiftDiffusion.xcodeproj/project.pbxproj | 6 +++--- .../{UpdatesView => UpdateView}/GitHubReleaseFetcher.swift | 0 .../{UpdatesView => UpdateView}/UpdateManager.swift | 0 .../{UpdatesView => UpdateView}/UpdateView.swift | 0 .../{UpdatesView => UpdateView}/UpdateViewState.swift | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename SwiftDiffusion/Views/WindowViews/{UpdatesView => UpdateView}/GitHubReleaseFetcher.swift (100%) rename SwiftDiffusion/Views/WindowViews/{UpdatesView => UpdateView}/UpdateManager.swift (100%) rename SwiftDiffusion/Views/WindowViews/{UpdatesView => UpdateView}/UpdateView.swift (100%) rename SwiftDiffusion/Views/WindowViews/{UpdatesView => UpdateView}/UpdateViewState.swift (100%) diff --git a/SwiftDiffusion.xcodeproj/project.pbxproj b/SwiftDiffusion.xcodeproj/project.pbxproj index 8400961f..947db228 100644 --- a/SwiftDiffusion.xcodeproj/project.pbxproj +++ b/SwiftDiffusion.xcodeproj/project.pbxproj @@ -484,7 +484,7 @@ isa = PBXGroup; children = ( 29C06C292B93DCEF00F950C4 /* BetaOnboardingView */, - 29C0F37B2B7AB37E0017DD6B /* UpdatesView */, + 29C0F37B2B7AB37E0017DD6B /* UpdateView */, 29D3623A2B72ED3B006C57B3 /* CheckpointManagerView */, 2916F3322B7278D4001D14B9 /* SettingsView */, ); @@ -918,7 +918,7 @@ path = ContentView; sourceTree = ""; }; - 29C0F37B2B7AB37E0017DD6B /* UpdatesView */ = { + 29C0F37B2B7AB37E0017DD6B /* UpdateView */ = { isa = PBXGroup; children = ( 29C06C322B95196200F950C4 /* UpdateViewState.swift */, @@ -926,7 +926,7 @@ 29C06C2E2B94C55900F950C4 /* UpdateManager.swift */, 29C06C302B95003600F950C4 /* GitHubReleaseFetcher.swift */, ); - path = UpdatesView; + path = UpdateView; sourceTree = ""; }; 29C0F37E2B7ABED50017DD6B /* SettingsSections */ = { diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/GitHubReleaseFetcher.swift similarity index 100% rename from SwiftDiffusion/Views/WindowViews/UpdatesView/GitHubReleaseFetcher.swift rename to SwiftDiffusion/Views/WindowViews/UpdateView/GitHubReleaseFetcher.swift diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift similarity index 100% rename from SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateManager.swift rename to SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateView.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift similarity index 100% rename from SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateView.swift rename to SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift diff --git a/SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateViewState.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateViewState.swift similarity index 100% rename from SwiftDiffusion/Views/WindowViews/UpdatesView/UpdateViewState.swift rename to SwiftDiffusion/Views/WindowViews/UpdateView/UpdateViewState.swift From 3206acadc088ea64ab7dee71dfda70b6b60c4e31 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 14:01:15 -0700 Subject: [PATCH 27/39] refactor: UpdateView, OutlineButton, BlueButton --- .../UpdateView/UpdateManager.swift | 5 +++- .../WindowViews/UpdateView/UpdateView.swift | 27 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift index 6636d2da..e2cee6bf 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift @@ -17,6 +17,9 @@ enum UpdateFrequency: String, CaseIterable { case monthly = "Monthly" } +extension Constants { + static let releasesUrl: String = "https://github.com/revblaze/ReleaseParsingTest/releases" +} class UpdateManager: ObservableObject { @Published var lastCheckedTimestamp: Date? @@ -110,7 +113,7 @@ class UpdateManager: ObservableObject { } func checkForUpdates() async throws -> Bool { - let fetcher = GitHubReleaseFetcher(urlString: "https://github.com/revblaze/ReleaseParsingTest/releases") + let fetcher = GitHubReleaseFetcher(urlString: Constants.releasesUrl) let releases = try await fetcher.checkForUpdates() if !releases.isEmpty { diff --git a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift index 7024809e..5999ae8e 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift @@ -75,16 +75,32 @@ struct UpdateView: View { Spacer() - Button(updateViewState.mainButtonText) { + HStack { if updateViewState == .newVersionAvailable { - if let latestRelease = updateManager.latestRelease, let releaseUrl = latestRelease.releaseDownloadUrlString, let url = URL(string: releaseUrl) { - NSWorkspace.shared.open(url) + OutlineButton(title: "Open Releases") { + if let releasesUrl = URL(string: Constants.releasesUrl) { + NSWorkspace.shared.open(releasesUrl) + } + } + BlueButton(title: "Download Now") { + if let latestRelease = updateManager.latestRelease, let releaseUrl = latestRelease.releaseDownloadUrlString, let url = URL(string: releaseUrl) { + NSWorkspace.shared.open(url) + } } } else { - Task { - await updateManager.checkForUpdatesIfNeeded(force: true) + OutlineButton(title: updateViewState.mainButtonText) { + if updateViewState == .newVersionAvailable { + if let latestRelease = updateManager.latestRelease, let releaseUrl = latestRelease.releaseDownloadUrlString, let url = URL(string: releaseUrl) { + NSWorkspace.shared.open(url) + } + } else { + Task { + await updateManager.checkForUpdatesIfNeeded(force: true) + } + } } } + } .padding(.bottom, 10) .disabled(updateManager.isCheckingForUpdate) @@ -95,6 +111,7 @@ struct UpdateView: View { updateViewStateBasedOnManager() } + if let lastChecked = updateManager.lastCheckedTimestamp { Text("Last checked: \(lastChecked, formatter: itemFormatter)") .font(.footnote) From c554f5f3d320131a9e520fbe5eb4c352d4f4cfd7 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 14:31:51 -0700 Subject: [PATCH 28/39] refactor: fixes, adjustments --- .../Checkpoints/Views/DebugApiView.swift | 4 -- .../UpdateView/UpdateManager.swift | 2 +- .../WindowViews/UpdateView/UpdateView.swift | 55 +++++++++++-------- .../UpdateView/UpdateViewState.swift | 2 +- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/SwiftDiffusion/Models/AutomaticModels/Custom/Checkpoints/Views/DebugApiView.swift b/SwiftDiffusion/Models/AutomaticModels/Custom/Checkpoints/Views/DebugApiView.swift index 56ca90a4..2d641354 100644 --- a/SwiftDiffusion/Models/AutomaticModels/Custom/Checkpoints/Views/DebugApiView.swift +++ b/SwiftDiffusion/Models/AutomaticModels/Custom/Checkpoints/Views/DebugApiView.swift @@ -128,10 +128,6 @@ struct DebugApiView: View { } } - - - - #Preview { let scriptManagerPreview: ScriptManager = .preview(withState: .active) let promptModelPreview = PromptModel() diff --git a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift index e2cee6bf..1af26525 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift @@ -18,7 +18,7 @@ enum UpdateFrequency: String, CaseIterable { } extension Constants { - static let releasesUrl: String = "https://github.com/revblaze/ReleaseParsingTest/releases" + static let releasesUrl: String = "https://github.com/buzsh/SwiftDiffusion/releases" // "https://github.com/revblaze/ReleaseParsingTest/releases" } class UpdateManager: ObservableObject { diff --git a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift index 5999ae8e..d2dea562 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateView.swift @@ -26,7 +26,7 @@ struct UpdateView: View { @EnvironmentObject var updateManager: UpdateManager @State private var showUpdateFrequencySection: Bool = false private let updateFrequencySectionHeight: CGFloat = 28 - private let initialFrameHeight: CGFloat = 250 + private let initialFrameHeight: CGFloat = 280 var expandedFrameHeight: CGFloat { initialFrameHeight + updateFrequencySectionHeight } @@ -49,7 +49,7 @@ struct UpdateView: View { } } } label: { - Label(updateManager.updateCheckFrequency.rawValue, systemImage: "calendar") + Label("Check " + updateManager.updateCheckFrequency.rawValue, systemImage: "calendar") } } .frame(width: 250, height: updateFrequencySectionHeight) @@ -59,10 +59,23 @@ struct UpdateView: View { Spacer() - HStack(alignment: .top) { - Image(systemName: updateViewState.symbol) - .foregroundStyle(updateViewState.symbolColor) - .padding(.trailing, 2) + HStack(alignment: .center) { + VStack { + if updateViewState == .checkingForUpdate { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(0.5) + .opacity(updateViewState == .checkingForUpdate ? 1 : 0) + } else { + Image(systemName: updateViewState.symbol) + .foregroundStyle(updateViewState.symbolColor) + .font(.system(size: 18)) + } + } + .frame(width: 24, height: 24) + .padding(.trailing, 2) + + VStack(alignment: .leading) { Text(updateViewState.statusText) .bold() @@ -111,16 +124,18 @@ struct UpdateView: View { updateViewStateBasedOnManager() } - - if let lastChecked = updateManager.lastCheckedTimestamp { - Text("Last checked: \(lastChecked, formatter: itemFormatter)") - .font(.footnote) - .foregroundStyle(Color.secondary) - } else { - Text("Last checked: Never") - .font(.footnote) - .foregroundStyle(Color.secondary) + HStack { + if let lastChecked = updateManager.lastCheckedTimestamp { + Text("Last checked: \(lastChecked, formatter: itemFormatter)") + .font(.footnote) + .foregroundStyle(Color.secondary) + } else { + Text("Last checked: Never") + .font(.footnote) + .foregroundStyle(Color.secondary) + } } + .padding(.bottom, 2) if let checkForUpdateError = updateManager.checkForUpdatesErrorMessage { Text(checkForUpdateError) @@ -139,16 +154,10 @@ struct UpdateView: View { updateViewStateBasedOnManager() } - .navigationTitle("Updates") + .navigationTitle("Check for Updates") .toolbar { ToolbarItemGroup(placement: .automatic) { HStack { - - ProgressView() - .progressViewStyle(CircularProgressViewStyle()) - .scaleEffect(0.5) - .opacity(updateManager.isCheckingForUpdate ? 1 : 0) - Button(action: { withAnimation { showUpdateFrequencySection.toggle() @@ -173,7 +182,7 @@ struct UpdateView: View { updateViewState = .latestVersion } } else { - updateViewState = .defaultState + updateViewState = .latestVersion // .defaultState } } } diff --git a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateViewState.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateViewState.swift index 56469539..291b11ab 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateViewState.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateViewState.swift @@ -24,7 +24,7 @@ enum UpdateViewState { var symbol: String { switch self { - case .defaultState: "icloud.slash.fill" + case .defaultState: "exclamationmark.arrow.triangle.2.circlepath" //"icloud.slash.fill" case .latestVersion: "checkmark.circle.fill" case .checkingForUpdate: "arrow.triangle.2.circlepath.icloud.fill" case .newVersionAvailable: "exclamationmark.circle.fill" From dde71fe4cc88c5b31ccfb126417a24df69b696e7 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 14:45:39 -0700 Subject: [PATCH 29/39] refactor: clean up remainder --- .../Views/MainViews/ContentView/ContentView.swift | 4 +--- .../BetaOnboardingView/BetaOnboardingView.swift | 8 +++++++- .../Views/WindowViews/UpdateView/UpdateManager.swift | 5 ++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift b/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift index 288fd0f2..09c1ee94 100644 --- a/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift +++ b/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift @@ -42,7 +42,7 @@ struct ContentView: View { @State private var scriptManagerObserver: ScriptManagerObserver? - @AppStorage("hasLaunchedBeforeTest4") var hasLaunchedBefore: Bool = false + @AppStorage("hasLaunchedBeforeTest") var hasLaunchedBefore: Bool = false @State private var showingBetaOnboardingSheetView: Bool = false // RequiredInputPaths @@ -59,12 +59,10 @@ struct ContentView: View { @State var selectedImage: NSImage? = nil @AppStorage("lastSelectedImagePath") var lastSelectedImagePath: String = "" @State var lastSavedImageUrls: [URL] = [] - @State var imageCountToGenerate: Int = 0 @State private var columnVisibility = NavigationSplitViewVisibility.all // .doubleColumn (hide by default) - var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { Sidebar(selectedImage: $selectedImage, lastSavedImageUrls: $lastSavedImageUrls) diff --git a/SwiftDiffusion/Views/WindowViews/BetaOnboardingView/BetaOnboardingView.swift b/SwiftDiffusion/Views/WindowViews/BetaOnboardingView/BetaOnboardingView.swift index f04bdf64..0a52a169 100644 --- a/SwiftDiffusion/Views/WindowViews/BetaOnboardingView/BetaOnboardingView.swift +++ b/SwiftDiffusion/Views/WindowViews/BetaOnboardingView/BetaOnboardingView.swift @@ -162,7 +162,13 @@ struct SetupStepView: View { CapsuleTextView(text: "Coming Soon") .opacity(0.6) } - .padding(.vertical, 6) + .padding(.top, 6) + HStack { + Text("Forge") + CapsuleTextView(text: "Coming Soon") + .opacity(0.6) + } + .padding(.top, 6) } .font(.system(size: 14, weight: .medium)) } diff --git a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift index 1af26525..7df62d0e 100644 --- a/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift +++ b/SwiftDiffusion/Views/WindowViews/UpdateView/UpdateManager.swift @@ -18,7 +18,8 @@ enum UpdateFrequency: String, CaseIterable { } extension Constants { - static let releasesUrl: String = "https://github.com/buzsh/SwiftDiffusion/releases" // "https://github.com/revblaze/ReleaseParsingTest/releases" + //static let releasesUrl: String = "https://github.com/revblaze/ReleaseParsingTest/releases" + static let releasesUrl: String = "https://github.com/buzsh/SwiftDiffusion/releases" } class UpdateManager: ObservableObject { @@ -61,8 +62,6 @@ class UpdateManager: ObservableObject { func checkForUpdatesIfNeeded(force: Bool = false) async { guard force || shouldCheckForUpdates() else { return } - await MainActor.run { self.isCheckingForUpdate = true } - do { let updateAvailable = try await checkForUpdates() await MainActor.run { From 002d8cf2abf49efc2d32899335cbe5a8d0ba7b4a Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 15:48:13 -0700 Subject: [PATCH 30/39] fix: sampler_index = samplingMethod --- .../Checkpoints/Managers/CheckpointsManager.swift | 1 - .../PromptView+ParseCivitai.swift | 5 +++-- .../ContentView/ContentView+Generate.swift | 13 +++++++++++-- .../DebugPromptViews/ApiCheckpointRow.swift | 5 +++++ 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/SwiftDiffusion/Models/AutomaticModels/Custom/Checkpoints/Managers/CheckpointsManager.swift b/SwiftDiffusion/Models/AutomaticModels/Custom/Checkpoints/Managers/CheckpointsManager.swift index 0de80b05..96c563c9 100644 --- a/SwiftDiffusion/Models/AutomaticModels/Custom/Checkpoints/Managers/CheckpointsManager.swift +++ b/SwiftDiffusion/Models/AutomaticModels/Custom/Checkpoints/Managers/CheckpointsManager.swift @@ -54,7 +54,6 @@ class CheckpointsManager: ObservableObject { func stopObservingDirectory() { directoryObserver?.stopObserving() - updateFlagForHasLoadedInitialCheckpointDataFromApi(to: false) } } diff --git a/SwiftDiffusion/Utilities/ParseGenerationData/PromptView+ParseCivitai.swift b/SwiftDiffusion/Utilities/ParseGenerationData/PromptView+ParseCivitai.swift index f3215902..fb105e8a 100644 --- a/SwiftDiffusion/Utilities/ParseGenerationData/PromptView+ParseCivitai.swift +++ b/SwiftDiffusion/Utilities/ParseGenerationData/PromptView+ParseCivitai.swift @@ -69,11 +69,12 @@ extension PromptView { } } /// Asynchronously checks the system pasteboard for generation data and updates a flag accordingly. Intended to be used on the main actor to ensure UI updates are handled correctly. - @MainActor func checkPasteboardAndUpdateFlag() async { if let pasteboardContent = getPasteboardString() { let hasData = userHasGenerationDataInPasteboard(from: pasteboardContent) - generationDataInPasteboard = hasData + await MainActor.run { + generationDataInPasteboard = hasData + } } } /// Determines if the pasteboard content contains generation data by looking for specific keywords. diff --git a/SwiftDiffusion/Views/MainViews/ContentView/ContentView+Generate.swift b/SwiftDiffusion/Views/MainViews/ContentView/ContentView+Generate.swift index 0826e2a9..a4ef81df 100644 --- a/SwiftDiffusion/Views/MainViews/ContentView/ContentView+Generate.swift +++ b/SwiftDiffusion/Views/MainViews/ContentView/ContentView+Generate.swift @@ -32,6 +32,7 @@ extension Constants { enum PayloadKey: String { case prompt case negativePrompt = "negative_prompt" + case samplingMethod = "sampler_index" case width case height case cfgScale = "cfg_scale" @@ -49,9 +50,17 @@ enum PayloadKey: String { extension ContentView { func prepareImageGenerationPayloadFromPrompt() -> [String: Any] { + let sanitizedPositivePrompt = currentPrompt.positivePrompt + .replacingOccurrences(of: "\n", with: " ") + .replacingOccurrences(of: "\"", with: "'") + let sanitizedNegativePrompt = currentPrompt.negativePrompt + .replacingOccurrences(of: "\n", with: " ") + .replacingOccurrences(of: "\"", with: "'") + var payload: [String: Any] = [ - PayloadKey.prompt.rawValue: currentPrompt.positivePrompt, - PayloadKey.negativePrompt.rawValue: currentPrompt.negativePrompt, + PayloadKey.prompt.rawValue: sanitizedPositivePrompt, + PayloadKey.negativePrompt.rawValue: sanitizedNegativePrompt, + PayloadKey.samplingMethod.rawValue: currentPrompt.samplingMethod ?? "DPM++ SDE Karras", PayloadKey.width.rawValue: Int(currentPrompt.width), PayloadKey.height.rawValue: Int(currentPrompt.height), PayloadKey.cfgScale.rawValue: Int(currentPrompt.cfgScale), diff --git a/SwiftDiffusion/Views/MainViews/PromptView/DebugPromptViews/ApiCheckpointRow.swift b/SwiftDiffusion/Views/MainViews/PromptView/DebugPromptViews/ApiCheckpointRow.swift index 2d87f3ea..3277f55e 100644 --- a/SwiftDiffusion/Views/MainViews/PromptView/DebugPromptViews/ApiCheckpointRow.swift +++ b/SwiftDiffusion/Views/MainViews/PromptView/DebugPromptViews/ApiCheckpointRow.swift @@ -37,6 +37,11 @@ struct ApiCheckpointRow: View { } } Spacer() + Button("Load") { + Task { + await checkpointsManager.getLoadedCheckpointModelFromApi() + } + } } TextEditor(text: $selectedSidebarItemApiPayload) From 0e6b4013875a7cd01b68c2b95ea153945d59b234 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 16:12:31 -0700 Subject: [PATCH 31/39] refactor: persistent newWorkspaceItemPlaceholderButton, cleanUpEmptyWorkspaceItemsIfNecessary --- SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift | 4 +--- .../Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift b/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift index 222c9993..67cfa6fe 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift @@ -55,6 +55,7 @@ struct Sidebar: View { ensureNecessaryFoldersExist() sidebarModel.setCurrentFolder(to: sidebarModel.rootFolder) sidebarModel.updateStorableSidebarItemsInWorkspace() + cleanUpEmptyWorkspaceItemsIfNecessary() sidebarModel.createNewWorkspaceItem(in: modelContext) } .onChange(of: sidebarModel.selectedItemID) { currentItemID, newItemID in @@ -122,9 +123,6 @@ struct Sidebar: View { } else { Debug.log("[Sidebar] No newlySelectedFolder") } - - cleanUpEmptyWorkspaceItemsIfNecessary() - } func handleNewlySelected(sidebarItem: SidebarItem, withID newItemID: UUID?) { diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift index e07fcd32..54a290d3 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift @@ -28,10 +28,6 @@ struct WorkspaceFolderView: View { sidebarModel.setSelectedSidebarItem(to: sidebarItem) } } - - if showWorkspaceItemsPlaceholderButton { - newWorkspaceItemPlaceholderButton - } } .onChange(of: sidebarModel.workspaceFolder?.items) { if let workspaceItems = sidebarModel.workspaceFolder?.items { @@ -42,6 +38,7 @@ struct WorkspaceFolderView: View { } } } + newWorkspaceItemPlaceholderButton } private var newWorkspaceItemPlaceholderButton: some View { From 4a313e4bfcc31a0307f375bf9bbca2ed445fc021 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 17:06:53 -0700 Subject: [PATCH 32/39] refactor: WorkspaceItem background progressView --- .../MainViews/PromptView/PromptView.swift | 3 ++- .../MainViews/Sidebar/SidebarModel.swift | 12 +++++++---- .../WorkspaceFolderView.swift | 1 + .../WorkspaceItemView.swift | 21 +++++++++++++++++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/SwiftDiffusion/Views/MainViews/PromptView/PromptView.swift b/SwiftDiffusion/Views/MainViews/PromptView/PromptView.swift index 986848dc..5a7d4839 100644 --- a/SwiftDiffusion/Views/MainViews/PromptView/PromptView.swift +++ b/SwiftDiffusion/Views/MainViews/PromptView/PromptView.swift @@ -75,7 +75,8 @@ struct PromptView: View { await checkPasteboardAndUpdateFlag() } } - .disabled(!sidebarModel.workspaceFolderContainsSelectedSidebarItem()) + //.disabled(!sidebarModel.workspaceFolderContainsSelectedSidebarItem()) + .disabled(sidebarModel.disablePromptView) } PasteGenerationDataStatusBar( diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift b/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift index e3997fd3..f3dc3b1f 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift @@ -42,6 +42,10 @@ class SidebarModel: ObservableObject { @Published var sidebarIsVisible: Bool = true + var disablePromptView: Bool { + selectedSidebarItemIsCurrentlyGenerating() || (workspaceFolderContainsSelectedSidebarItem() == false) + } + enum SortingOrder: String { case mostRecent = "Most Recent" case leastRecent = "Least Recent" @@ -132,15 +136,12 @@ class SidebarModel: ObservableObject { } guard let currentItem = selectedSidebarItem, let currentIndex = sortedItems.firstIndex(of: currentItem) else { - // If no current selection or the current item is not found, - // select the first or last item based on the sorting order. setSelectedSidebarItem(to: sortingOrder == .leastRecent ? sortedItems.first : sortedItems.last) return } switch sortingOrder { case .leastRecent: - // Priority for least recent: try to select the next item "down" first, then "up". if currentIndex + 1 < sortedItems.count { setSelectedSidebarItem(to: sortedItems[currentIndex + 1]) } else if currentIndex > 0 { @@ -149,7 +150,6 @@ class SidebarModel: ObservableObject { setSelectedSidebarItem(to: nil) } case .mostRecent: - // Priority for most recent: try to select an earlier item "up" first, then "down". if currentIndex > 0 { setSelectedSidebarItem(to: sortedItems[currentIndex - 1]) } else if currentIndex + 1 < sortedItems.count { @@ -167,6 +167,10 @@ class SidebarModel: ObservableObject { workspaceFolderContains(sidebarItem: selectedSidebarItem) } + func selectedSidebarItemIsCurrentlyGenerating() -> Bool { + selectedSidebarItem == currentlyGeneratingSidebarItem + } + func workspaceFolderContains(sidebarItem: SidebarItem?) -> Bool { if let workspaceFolder = workspaceFolder, workspaceFolder.items.contains(where: { $0.id == sidebarItem?.id }) { return true diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift index 54a290d3..5b153a35 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift @@ -27,6 +27,7 @@ struct WorkspaceFolderView: View { .onTapGesture { sidebarModel.setSelectedSidebarItem(to: sidebarItem) } + //.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } } .onChange(of: sidebarModel.workspaceFolder?.items) { diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift index 7932e314..b1e0a938 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift @@ -16,6 +16,7 @@ struct WorkspaceItemView: View { @Environment(\.modelContext) private var modelContext @EnvironmentObject var currentPrompt: PromptModel @EnvironmentObject var sidebarModel: SidebarModel + @EnvironmentObject var scriptManager: ScriptManager let sidebarItem: SidebarItem @@ -26,8 +27,28 @@ struct WorkspaceItemView: View { } else { formattedTitleView(sidebarItem.title) } + Spacer() } + .frame(height: 30) + .padding(.horizontal, 4) + .contentShape(Rectangle()) + .cornerRadius(4) + // if sidebarItem == sidebarModel.currentlyGeneratingSidebarItem || sidebarItem == sidebarModel.selectedSidebarItem { + .background( + GeometryReader { geometry in + ZStack(alignment: .leading) { + if sidebarItem == sidebarModel.currentlyGeneratingSidebarItem { + let progressWidth = geometry.size.width * (CGFloat(scriptManager.genProgress)) + RoundedRectangle(cornerRadius: 4) + .fill(Color.blue.opacity(0.9)) + .frame(width: progressWidth) + } + } + } + ) + .animation(.linear(duration: 0.5), value: scriptManager.genProgress) + .onChange(of: currentPrompt.positivePrompt) { if sidebarItem.id == sidebarModel.selectedSidebarItem?.id { let trimmedPrompt = currentPrompt.positivePrompt.trimmingCharacters(in: .whitespaces) From bb2992944b2d2922525c672873ede361f14572f8 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 17:42:20 -0700 Subject: [PATCH 33/39] feat: sidebar background as progress animated --- .../ScriptManager/ScriptManager.swift | 4 +- .../MainViews/ContentView/ContentView.swift | 7 ++- .../Views/MainViews/Sidebar/Sidebar.swift | 6 ++- .../MainViews/Sidebar/SidebarModel.swift | 6 ++- .../WorkspaceItemView.swift | 53 ++++++++++++++++--- 5 files changed, 63 insertions(+), 13 deletions(-) diff --git a/SwiftDiffusion/ScriptManager/ScriptManager/ScriptManager.swift b/SwiftDiffusion/ScriptManager/ScriptManager/ScriptManager.swift index 511543b1..3940d4c2 100644 --- a/SwiftDiffusion/ScriptManager/ScriptManager/ScriptManager.swift +++ b/SwiftDiffusion/ScriptManager/ScriptManager/ScriptManager.swift @@ -53,13 +53,13 @@ class ScriptManager: ObservableObject { var shouldTrimOutput: Bool = false @Published var genStatus: GenerationStatus = .idle - @Published var genProgress: Double = 0 + @Published var genProgress: Double = 0.0 @Published var genIterationPerSecond: String = "" @Published var genCurrentStepOutOfTotalSteps: String = "" @Published var modelLoadState: ModelLoadState = .idle - @Published var modelLoadTime: Double = 0 + @Published var modelLoadTime: Double = 0.0 @Published var modelLoadStateShouldExpire: Bool = false @Published var modelLoadTypeErrorThrown: Bool = false diff --git a/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift b/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift index 09c1ee94..72abec03 100644 --- a/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift +++ b/SwiftDiffusion/Views/MainViews/ContentView/ContentView.swift @@ -259,9 +259,13 @@ struct ContentView: View { handleScriptOnLaunch() } .onChange(of: scriptManager.genStatus) { + if scriptManager.genStatus == .preparingToGenerate { + sidebarModel.currentlyGeneratingSidebarItem = sidebarModel.selectedSidebarItem + } + if scriptManager.genStatus == .generating { imageCountToGenerate = Int(currentPrompt.batchSize * currentPrompt.batchCount) - sidebarModel.currentlyGeneratingSidebarItem = sidebarModel.selectedSidebarItem + //sidebarModel.currentlyGeneratingSidebarItem = sidebarModel.selectedSidebarItem } else if scriptManager.genStatus == .done { imagesDidGenerateSuccessfully() @@ -281,6 +285,7 @@ struct ContentView: View { if let storableSidebarItem = sidebarModel.currentlyGeneratingSidebarItem { sidebarModel.addToStorableSidebarItems(sidebarItem: storableSidebarItem, withImageUrls: lastSavedImageUrls) + sidebarModel.currentlyGeneratingSidebarItem = nil } Task { diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift b/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift index 67cfa6fe..5c3efe30 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift @@ -205,14 +205,18 @@ extension Sidebar { if let workspaceFolder = sidebarModel.workspaceFolder { let emptyPromptItems = workspaceFolder.items.filter { $0.prompt?.isEmptyPrompt ?? false } - let latestEmptyPromptItem = emptyPromptItems.max(by: { $0.timestamp < $1.timestamp }) + //let latestEmptyPromptItem = emptyPromptItems.max(by: { $0.timestamp < $1.timestamp }) for item in emptyPromptItems { + workspaceFolder.remove(item: item) + + /* if let latestItem = latestEmptyPromptItem, item != latestItem { withAnimation { workspaceFolder.remove(item: item) } } + */ } } } diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift b/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift index f3dc3b1f..7ba21267 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift @@ -6,6 +6,7 @@ // import Foundation +import SwiftUI import SwiftData @@ -80,6 +81,7 @@ class SidebarModel: ObservableObject { storableSidebarItems.removeAll(where: { $0 == sidebarItem }) let mapModelData = MapModelData() sidebarItem.prompt = mapModelData.toStored(promptModel: prompt) + sidebarItem.timestamp = Date() currentFolder?.add(item: sidebarItem) workspaceFolder?.remove(item: sidebarItem) PreviewImageProcessingManager.shared.createImagePreviewsAndThumbnails(for: sidebarItem, in: modelContext) @@ -88,7 +90,9 @@ class SidebarModel: ObservableObject { func deleteFromWorkspace(sidebarItem: SidebarItem, in modelContext: ModelContext) { selectNextClosestSidebarItemIfApplicable(sortedItems: sortedWorkspaceFolderItems, sortingOrder: .mostRecent) - workspaceFolder?.remove(item: sidebarItem) + withAnimation { + workspaceFolder?.remove(item: sidebarItem) + } saveData(in: modelContext) } diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift index b1e0a938..87e0f27c 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift @@ -17,37 +17,45 @@ struct WorkspaceItemView: View { @EnvironmentObject var currentPrompt: PromptModel @EnvironmentObject var sidebarModel: SidebarModel @EnvironmentObject var scriptManager: ScriptManager + @State private var animatedWidth: CGFloat = 0.0 let sidebarItem: SidebarItem var body: some View { + HStack { if sidebarItem.title.isEmpty { formattedTitleView("Untitled") + .padding(.leading, 4) } else { formattedTitleView(sidebarItem.title) + .padding(.leading, 4) } Spacer() } + .frame(height: 28) - .frame(height: 30) - .padding(.horizontal, 4) .contentShape(Rectangle()) .cornerRadius(4) - // if sidebarItem == sidebarModel.currentlyGeneratingSidebarItem || sidebarItem == sidebarModel.selectedSidebarItem { .background( - GeometryReader { geometry in + GeometryReader { outerGeometry in ZStack(alignment: .leading) { if sidebarItem == sidebarModel.currentlyGeneratingSidebarItem { - let progressWidth = geometry.size.width * (CGFloat(scriptManager.genProgress)) RoundedRectangle(cornerRadius: 4) - .fill(Color.blue.opacity(0.9)) - .frame(width: progressWidth) + .fill(Color(/*0x195AC2*/0x1C5EC8))//.fill(Color.blue.opacity(0.9)) + .frame(width: animatedWidth) + .listRowInsets(EdgeInsets(top: -8, leading: -20, bottom: -8, trailing: -20)) + .onChange(of: scriptManager.genProgress) { + withAnimation(.linear(duration: 0.5)) { + animatedWidth = outerGeometry.size.width * CGFloat(scriptManager.genProgress) + } + } } } + .listRowInsets(EdgeInsets(top: -8, leading: -20, bottom: -8, trailing: -20)) } ) - .animation(.linear(duration: 0.5), value: scriptManager.genProgress) + .onChange(of: currentPrompt.positivePrompt) { if sidebarItem.id == sidebarModel.selectedSidebarItem?.id { @@ -84,3 +92,32 @@ extension SidebarModel { return false } } + +struct WidthAnimatableModifier: AnimatableModifier { + var width: CGFloat + var color: Color = Color.blue.opacity(0.9) + + // AnimatableData must be a single, animatable value. SwiftUI will interpolate this value. + var animatableData: CGFloat { + get { width } + set { width = newValue } + } + + func body(content: Content) -> some View { + content + .frame(width: width) + .background(RoundedRectangle(cornerRadius: 4).fill(color)) + } +} + +extension Color { + init(_ hex: UInt, alpha: Double = 1) { + self.init( + .sRGB, + red: Double((hex >> 16) & 0xFF) / 255, + green: Double((hex >> 8) & 0xFF) / 255, + blue: Double(hex & 0xFF) / 255, + opacity: alpha + ) + } +} From 375338a56b867f5bb0313bc6c59783a4dacc389b Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 21:50:29 -0700 Subject: [PATCH 34/39] fix: fixed bottom space for DIsplayOptions --- SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift b/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift index 5c3efe30..41de01f5 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/Sidebar.swift @@ -33,6 +33,7 @@ struct Sidebar: View { List(selection: $sidebarModel.selectedItemID) { WorkspaceFolderView() SidebarFolderView() + VStack{}.frame(height: Constants.Layout.SidebarToolbar.bottomBarHeight) } .listStyle(SidebarListStyle()) .onDrop(of: [UTType.plainText], isTargeted: nil) { providers in @@ -40,8 +41,7 @@ struct Sidebar: View { return false } - EmptyView() - .frame(height: Constants.Layout.SidebarToolbar.bottomBarHeight) + DisplayOptionsBar() } From 24de36c5fd5c3fb736443c8cc6bf3c7fadc29b50 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 22:26:50 -0700 Subject: [PATCH 35/39] refactor: SmallerDetailButton for PromptView --- .../CustomViews/BackgroundButtonStyles.swift | 49 ++++++++++++++++++- .../ContentView/CustomViews/BlueButton.swift | 16 ++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/SwiftDiffusion/Views/MainViews/ContentView/CustomViews/BackgroundButtonStyles.swift b/SwiftDiffusion/Views/MainViews/ContentView/CustomViews/BackgroundButtonStyles.swift index 7ed69e30..07546580 100644 --- a/SwiftDiffusion/Views/MainViews/ContentView/CustomViews/BackgroundButtonStyles.swift +++ b/SwiftDiffusion/Views/MainViews/ContentView/CustomViews/BackgroundButtonStyles.swift @@ -41,7 +41,7 @@ struct BlueBackgroundButtonStyle: ButtonStyle { .overlay( RoundedRectangle(cornerRadius: 6) .stroke(self.borderColor(for: configuration.isPressed), lineWidth: 1) - .opacity(self.borderOpacity(for: configuration.isPressed)) // Adjust border opacity + .opacity(self.borderOpacity(for: configuration.isPressed)) ) } @@ -122,3 +122,50 @@ struct BorderBackgroundButtonStyle: ButtonStyle { #Preview { CommonPreviews.contentView } + + +struct BlueBackgroundSmallButtonStyle: ButtonStyle { + @Environment(\.isEnabled) var isEnabled: Bool + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding(.vertical, 4).padding(.horizontal, 8) + .background(self.backgroundColor(for: configuration.isPressed)) + .foregroundColor(.white) + .opacity(self.textOpacity(for: configuration.isPressed)) + .clipShape(RoundedRectangle(cornerRadius: 6)) + .overlay( + RoundedRectangle(cornerRadius: 6) + .stroke(self.borderColor(for: configuration.isPressed), lineWidth: 1) + .opacity(self.borderOpacity(for: configuration.isPressed)) + ) + } + + private func backgroundColor(for isPressed: Bool) -> Color { + if !isEnabled { + return StateColors.disabledGray + } else { + return isPressed ? StateColors.pressedBlue : StateColors.normalBlue + } + } + + private func borderColor(for isPressed: Bool) -> Color { + return isEnabled ? StateColors.normalBlue : StateColors.disabledGray + } + + private func textOpacity(for isPressed: Bool) -> Double { + if !isEnabled { + return StateColors.disabledTextOpacity + } else { + return isPressed ? StateColors.pressedTextOpacity : StateColors.normalTextOpacity + } + } + + private func borderOpacity(for isPressed: Bool) -> Double { + if !isEnabled { + return StateColors.disabledBorderOpacity + } else { + return isPressed ? StateColors.pressedBorderOpacity : StateColors.normalBorderOpacity + } + } +} diff --git a/SwiftDiffusion/Views/MainViews/ContentView/CustomViews/BlueButton.swift b/SwiftDiffusion/Views/MainViews/ContentView/CustomViews/BlueButton.swift index 3defd562..5a74758f 100644 --- a/SwiftDiffusion/Views/MainViews/ContentView/CustomViews/BlueButton.swift +++ b/SwiftDiffusion/Views/MainViews/ContentView/CustomViews/BlueButton.swift @@ -19,6 +19,22 @@ struct BlueButton: View { } } +struct BlueSymbolButton: View { + let title: String + let symbol: String + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack { + Text(title) + Image(systemName: symbol) + } + } + .buttonStyle(BlueBackgroundSmallButtonStyle()) + } +} + struct OutlineButton: View { let title: String let action: () -> Void From daf8378a00ddbdaf8bc9bc14627c19eeb69dba43 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 22:27:07 -0700 Subject: [PATCH 36/39] refactor: withAnimation paste generation data --- .../PromptView+ParseCivitai.swift | 4 ++- .../MainViews/PromptView/PromptView.swift | 31 ++++++++----------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/SwiftDiffusion/Utilities/ParseGenerationData/PromptView+ParseCivitai.swift b/SwiftDiffusion/Utilities/ParseGenerationData/PromptView+ParseCivitai.swift index fb105e8a..023bb866 100644 --- a/SwiftDiffusion/Utilities/ParseGenerationData/PromptView+ParseCivitai.swift +++ b/SwiftDiffusion/Utilities/ParseGenerationData/PromptView+ParseCivitai.swift @@ -73,7 +73,9 @@ extension PromptView { if let pasteboardContent = getPasteboardString() { let hasData = userHasGenerationDataInPasteboard(from: pasteboardContent) await MainActor.run { - generationDataInPasteboard = hasData + withAnimation { + generationDataInPasteboard = hasData + } } } } diff --git a/SwiftDiffusion/Views/MainViews/PromptView/PromptView.swift b/SwiftDiffusion/Views/MainViews/PromptView/PromptView.swift index 5a7d4839..6dd21894 100644 --- a/SwiftDiffusion/Views/MainViews/PromptView/PromptView.swift +++ b/SwiftDiffusion/Views/MainViews/PromptView/PromptView.swift @@ -28,10 +28,6 @@ struct PromptView: View { @State var generationDataInPasteboard: Bool = false @State var disablePromptView: Bool = false - func updateDisabledPromptViewState() { - disablePromptView = !sidebarModel.workspaceFolderContainsSelectedSidebarItem() - } - private var leftPane: some View { VStack(spacing: 0) { @@ -41,6 +37,19 @@ struct PromptView: View { ScrollView { Form { + if generationDataInPasteboard, let pasteboard = getPasteboardString() { + HStack { + Spacer() + BlueSymbolButton(title: "Paste Generation Data", symbol: "arrow.up.doc.on.clipboard") { + parseAndSetPromptData(from: pasteboard) + withAnimation { + generationDataInPasteboard = false + } + } + } + .padding(.top, 14) + } + HStack { CheckpointMenu() SamplingMethodMenu() @@ -75,17 +84,9 @@ struct PromptView: View { await checkPasteboardAndUpdateFlag() } } - //.disabled(!sidebarModel.workspaceFolderContainsSelectedSidebarItem()) .disabled(sidebarModel.disablePromptView) } - PasteGenerationDataStatusBar( - generationDataInPasteboard: generationDataInPasteboard, - onPaste: { pasteboardContent in - self.parseAndSetPromptData(from: pasteboardContent) - } - ) - DebugPromptActionView(scriptManager: scriptManager) } @@ -107,12 +108,6 @@ struct PromptView: View { .frame(minWidth: 370) } } - .onChange(of: sidebarModel.selectedSidebarItem) { - updateDisabledPromptViewState() - } - .onChange(of: sidebarModel.workspaceFolder?.items) { - updateDisabledPromptViewState() - } } } From 0cb9ad02a9d2dbe9d17bf3786e0ce3551a9c4dd4 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 22:27:17 -0700 Subject: [PATCH 37/39] refactor: clean workspace item --- .../WorkspaceItemView.swift | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift index 87e0f27c..17908962 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift @@ -34,7 +34,7 @@ struct WorkspaceItemView: View { Spacer() } .frame(height: 28) - + .padding(.leading, 4) .contentShape(Rectangle()) .cornerRadius(4) .background( @@ -42,7 +42,7 @@ struct WorkspaceItemView: View { ZStack(alignment: .leading) { if sidebarItem == sidebarModel.currentlyGeneratingSidebarItem { RoundedRectangle(cornerRadius: 4) - .fill(Color(/*0x195AC2*/0x1C5EC8))//.fill(Color.blue.opacity(0.9)) + .fill(Color(0x1C5EC8)) .frame(width: animatedWidth) .listRowInsets(EdgeInsets(top: -8, leading: -20, bottom: -8, trailing: -20)) .onChange(of: scriptManager.genProgress) { @@ -93,23 +93,6 @@ extension SidebarModel { } } -struct WidthAnimatableModifier: AnimatableModifier { - var width: CGFloat - var color: Color = Color.blue.opacity(0.9) - - // AnimatableData must be a single, animatable value. SwiftUI will interpolate this value. - var animatableData: CGFloat { - get { width } - set { width = newValue } - } - - func body(content: Content) -> some View { - content - .frame(width: width) - .background(RoundedRectangle(cornerRadius: 4).fill(color)) - } -} - extension Color { init(_ hex: UInt, alpha: Double = 1) { self.init( From 91baba45f83ac3be4193df8ded9e2c59a78cd2ab Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 22:33:45 -0700 Subject: [PATCH 38/39] refactor: environment color scheme --- .../WorkspaceFolderView/WorkspaceFolderView.swift | 1 - .../WorkspaceFolderView/WorkspaceItemView.swift | 12 +++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift index 5b153a35..54a290d3 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceFolderView.swift @@ -27,7 +27,6 @@ struct WorkspaceFolderView: View { .onTapGesture { sidebarModel.setSelectedSidebarItem(to: sidebarItem) } - //.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) } } .onChange(of: sidebarModel.workspaceFolder?.items) { diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift index 17908962..da89898e 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift @@ -13,6 +13,7 @@ extension Constants.Sidebar { } struct WorkspaceItemView: View { + @Environment(\.colorScheme) var colorScheme @Environment(\.modelContext) private var modelContext @EnvironmentObject var currentPrompt: PromptModel @EnvironmentObject var sidebarModel: SidebarModel @@ -21,6 +22,15 @@ struct WorkspaceItemView: View { let sidebarItem: SidebarItem + var progressBarColor: Color { + switch colorScheme { + case .light: + return Color(0x2984F2) + default: + return Color(0x1C5EC8) + } + } + var body: some View { HStack { @@ -42,7 +52,7 @@ struct WorkspaceItemView: View { ZStack(alignment: .leading) { if sidebarItem == sidebarModel.currentlyGeneratingSidebarItem { RoundedRectangle(cornerRadius: 4) - .fill(Color(0x1C5EC8)) + .fill(progressBarColor) .frame(width: animatedWidth) .listRowInsets(EdgeInsets(top: -8, leading: -20, bottom: -8, trailing: -20)) .onChange(of: scriptManager.genProgress) { From df95fe922d5ae7b66c64e916903deee9423b6427 Mon Sep 17 00:00:00 2001 From: Justin Bush Date: Sun, 3 Mar 2024 22:53:23 -0700 Subject: [PATCH 39/39] fix: sidebar item deletion logic in model context --- .../PromptBars/PromptControlBar.swift | 5 ++++- .../SidebarFolderView/SidebarFolderView.swift | 17 +++++++++++++---- .../Views/MainViews/Sidebar/SidebarModel.swift | 6 ++++-- .../WorkspaceFolderView/WorkspaceItemView.swift | 12 ++++++++---- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/SwiftDiffusion/Views/MainViews/PromptView/PromptBars/PromptControlBar.swift b/SwiftDiffusion/Views/MainViews/PromptView/PromptBars/PromptControlBar.swift index 1007e5d1..8f5750d0 100644 --- a/SwiftDiffusion/Views/MainViews/PromptView/PromptBars/PromptControlBar.swift +++ b/SwiftDiffusion/Views/MainViews/PromptView/PromptBars/PromptControlBar.swift @@ -51,6 +51,7 @@ struct PromptControlBar: View { private var workspaceItemBar: some View { HStack { Button(action: { + if let sidebarItem = sidebarModel.selectedSidebarItem { sidebarModel.deleteFromWorkspace(sidebarItem: sidebarItem, in: modelContext) } @@ -79,7 +80,9 @@ struct PromptControlBar: View { private var storedItemBar: some View { HStack { Button(action: { - sidebarModel.promptUserToConfirmDeletion = true + if let selectedSidebarItem = sidebarModel.selectedSidebarItem { + sidebarModel.queueStoredSidebarItemForDeletion = selectedSidebarItem + } }) { Image(systemName: "trash") Text("Delete") diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/SidebarFolderView/SidebarFolderView.swift b/SwiftDiffusion/Views/MainViews/Sidebar/SidebarFolderView/SidebarFolderView.swift index f90743be..b30bda8c 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/SidebarFolderView/SidebarFolderView.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/SidebarFolderView/SidebarFolderView.swift @@ -89,16 +89,25 @@ struct SidebarFolderView: View { sidebarModel.applyCustomLeadingInsets = true } } - .onChange(of: sidebarModel.promptUserToConfirmDeletion) { - showingDeleteSelectedSidebarItemConfirmationAlert = sidebarModel.promptUserToConfirmDeletion + .onChange(of: sidebarModel.queueStoredSidebarItemForDeletion) { + if sidebarModel.queueStoredSidebarItemForDeletion != nil { + showingDeleteSelectedSidebarItemConfirmationAlert = true + } } .alert(isPresented: $showingDeleteSelectedSidebarItemConfirmationAlert) { Alert( title: Text("Are you sure you want to delete this item?"), primaryButton: .destructive(Text("Delete")) { - sidebarModel.deleteSelectedSidebarItemFromStorage(in: modelContext) + if let sidebarItem = sidebarModel.queueStoredSidebarItemForDeletion { + sidebarModel.selectNextClosestSidebarItemIfApplicable(sortedItems: sidebarModel.sortedCurrentFolderItems, sortingOrder: .mostRecent) + sidebarModel.currentFolder?.remove(item: sidebarItem) + sidebarModel.saveData(in: modelContext) + } + sidebarModel.queueStoredSidebarItemForDeletion = nil }, - secondaryButton: .cancel() + secondaryButton: .cancel() { + sidebarModel.queueStoredSidebarItemForDeletion = nil + } ) } } diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift b/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift index 7ba21267..ad11a57d 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/SidebarModel.swift @@ -29,9 +29,11 @@ class SidebarModel: ObservableObject { @Published var widthOffset: CGFloat = 32 // 50 @Published var applyCustomLeadingInsets = false - + // TODO: Do we need? @Published var updateControlBarView: Bool = false - @Published var promptUserToConfirmDeletion: Bool = false + + @Published var queueWorkspaceItemForDeletion: SidebarItem? = nil + @Published var queueStoredSidebarItemForDeletion: SidebarItem? = nil @Published var queueMovableSidebarItemID: UUID? = nil @Published var queueDestinationFolderID: UUID? = nil diff --git a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift index da89898e..1dff96e5 100644 --- a/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift +++ b/SwiftDiffusion/Views/MainViews/Sidebar/WorkspaceFolderView/WorkspaceItemView.swift @@ -25,9 +25,9 @@ struct WorkspaceItemView: View { var progressBarColor: Color { switch colorScheme { case .light: - return Color(0x2984F2) + return Color(0x2984F2).opacity(0.5) default: - return Color(0x1C5EC8) + return Color(0x1C5EC8).opacity(0.6) } } @@ -65,14 +65,18 @@ struct WorkspaceItemView: View { .listRowInsets(EdgeInsets(top: -8, leading: -20, bottom: -8, trailing: -20)) } ) - - .onChange(of: currentPrompt.positivePrompt) { if sidebarItem.id == sidebarModel.selectedSidebarItem?.id { let trimmedPrompt = currentPrompt.positivePrompt.trimmingCharacters(in: .whitespaces) sidebarModel.setSelectedWorkspaceItemTitle(trimmedPrompt, in: modelContext) } } + .onChange(of: sidebarModel.queueWorkspaceItemForDeletion) { + if let workspaceItem = sidebarModel.queueWorkspaceItemForDeletion { + sidebarModel.workspaceFolder?.remove(item: workspaceItem) + sidebarModel.saveData(in: modelContext) + } + } }