Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10882524
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
15 KB
Subscribers
None
View Options
diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift
index ea741c8..4378ada 100644
--- a/Shared/WriteFreely_MultiPlatformApp.swift
+++ b/Shared/WriteFreely_MultiPlatformApp.swift
@@ -1,154 +1,146 @@
import SwiftUI
#if os(macOS)
import Sparkle
#endif
@main
struct CheckForDebugModifier {
static func main() {
#if os(macOS)
if NSEvent.modifierFlags.contains(.shift) {
// Clear the launch-to-last-draft values to load a new draft.
UserDefaults.shared.setValue(false, forKey: WFDefaults.showAllPostsFlag)
UserDefaults.shared.setValue(nil, forKey: WFDefaults.selectedCollectionURL)
UserDefaults.shared.setValue(nil, forKey: WFDefaults.lastDraftURL)
} else {
// No-op
}
#endif
WriteFreely_MultiPlatformApp.main()
}
}
struct WriteFreely_MultiPlatformApp: App {
@StateObject private var model = WriteFreelyModel.shared
#if os(macOS)
- // swiftlint:disable:next weak_delegate
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@StateObject var updaterViewModel = MacUpdatesViewModel()
@State private var selectedTab = 0
#endif
var body: some Scene {
WindowGroup {
ContentView()
.onAppear(perform: {
if model.editor.showAllPostsFlag {
DispatchQueue.main.async {
self.model.selectedCollection = nil
self.model.showAllPosts = true
showLastDraftOrCreateNewLocalPost()
}
} else {
DispatchQueue.main.async {
self.model.selectedCollection = model.editor.fetchSelectedCollectionFromAppStorage()
self.model.showAllPosts = false
showLastDraftOrCreateNewLocalPost()
}
}
-// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
-// if model.editor.lastDraftURL != nil {
-// self.model.selectedPost = model.editor.fetchLastDraftFromAppStorage()
-// } else {
-// createNewLocalPost()
-// }
-// }
})
.environmentObject(model)
.environment(\.managedObjectContext, LocalStorageManager.standard.container.viewContext)
// .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info.
}
.commands {
#if os(macOS)
CommandGroup(after: .appInfo) {
CheckForUpdatesView(updaterViewModel: updaterViewModel)
}
#endif
CommandGroup(replacing: .newItem, addition: {
Button("New Post") {
createNewLocalPost()
}
.keyboardShortcut("n", modifiers: [.command])
})
CommandGroup(after: .newItem) {
Button("Refresh Posts") {
DispatchQueue.main.async {
model.fetchUserCollections()
model.fetchUserPosts()
}
}
.disabled(!model.account.isLoggedIn)
.keyboardShortcut("r", modifiers: [.command])
}
SidebarCommands()
#if os(macOS)
PostCommands(model: model)
#endif
CommandGroup(after: .help) {
Button("Visit Support Forum") {
#if os(macOS)
NSWorkspace().open(model.helpURL)
#else
UIApplication.shared.open(model.helpURL)
#endif
}
}
ToolbarCommands()
TextEditingCommands()
}
#if os(macOS)
Settings {
TabView(selection: $selectedTab) {
MacAccountView()
.environmentObject(model)
.tabItem {
Image(systemName: "person.crop.circle")
Text("Account")
}
.tag(0)
MacPreferencesView(preferences: model.preferences)
.tabItem {
Image(systemName: "gear")
Text("Preferences")
}
.tag(1)
- MacUpdatesView()
+ MacUpdatesView(updaterViewModel: updaterViewModel)
.tabItem {
Image(systemName: "arrow.down.circle")
Text("Updates")
}
.tag(2)
}
.frame(minWidth: 500, maxWidth: 500, minHeight: 200)
.padding()
// .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info.
}
#endif
}
private func showLastDraftOrCreateNewLocalPost() {
if model.editor.lastDraftURL != nil {
self.model.selectedPost = model.editor.fetchLastDraftFromAppStorage()
} else {
createNewLocalPost()
}
}
private func createNewLocalPost() {
withAnimation {
// Un-set the currently selected post
self.model.selectedPost = nil
}
// Create the new-post managed object
let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font)
withAnimation {
// Set it as the selectedPost
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.model.selectedPost = managedPost
}
}
}
}
diff --git a/macOS/AppDelegate.swift b/macOS/AppDelegate.swift
index e002e64..410ffe9 100644
--- a/macOS/AppDelegate.swift
+++ b/macOS/AppDelegate.swift
@@ -1,61 +1,38 @@
import Cocoa
import Sparkle
class AppDelegate: NSObject, NSApplicationDelegate {
- func applicationWillFinishLaunching(_ notification: Notification) {
- // Check UserDefaults for values; if the key doesn't exist (e.g., if MacUpdatesView hasn't ever been shown),
- // bool(forKey:) returns false, so set SUUpdater.shared() appropriately.
- let automaticallyChecksForUpdates = UserDefaults.shared.bool(forKey: WFDefaults.automaticallyChecksForUpdates)
- let subscribeToBetaUpdates = UserDefaults.shared.bool(forKey: WFDefaults.subscribeToBetaUpdates)
-
- // Set Sparkle properties.
- SUUpdater.shared()?.automaticallyChecksForUpdates = automaticallyChecksForUpdates
- if subscribeToBetaUpdates {
- SUUpdater.shared()?.feedURL = URL(string: AppcastFeedUrl.beta.rawValue)
- } else {
- SUUpdater.shared()?.feedURL = URL(string: AppcastFeedUrl.release.rawValue)
- }
-
- // If enabled, check for updates.
- if automaticallyChecksForUpdates {
- SUUpdater.shared()?.checkForUpdatesInBackground()
- }
- }
-
// MARK: - Window handling when miniaturized into app icon on the Dock
// Credit to Henry Cooper (pillboxer) on GitHub:
// https://github.com/tact/beta-bugs/issues/31#issuecomment-855914705
// If the window is currently minimized into the Dock, de-miniaturize it (note that if it's minimized
// and the user uses OPT+TAB to switch to it, it will be de-miniaturized and brought to the foreground).
func applicationDidBecomeActive(_ notification: Notification) {
- print("💬 Fired:", #function)
if let window = NSApp.windows.first {
window.deminiaturize(nil)
}
}
// If we're miniaturizing the window, deactivate it as well by activating Finder.app (note that
// this will bring any Finder windows that are behind other apps to the foreground).
func applicationDidChangeOcclusionState(_ notification: Notification) {
- print("💬 Fired:", #function)
if let window = NSApp.windows.first, window.isMiniaturized {
NSWorkspace.shared.runningApplications.first(where: {
$0.activationPolicy == .regular
})?.activate(options: .activateAllWindows)
}
}
lazy var windows = NSWindow()
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
- print("💬 Fired:", #function)
if !flag {
for window in sender.windows {
window.makeKeyAndOrderFront(self)
}
}
return true
}
}
diff --git a/macOS/Settings/MacUpdatesView.swift b/macOS/Settings/MacUpdatesView.swift
index 53b4f0e..afb6c48 100644
--- a/macOS/Settings/MacUpdatesView.swift
+++ b/macOS/Settings/MacUpdatesView.swift
@@ -1,95 +1,91 @@
import SwiftUI
import Sparkle
-enum AppcastFeedUrl: String {
- case release = "https://writefreely-files.s3.amazonaws.com/apps/mac/appcast.xml"
- case beta = "https://writefreely-files.s3.amazonaws.com/apps/mac/appcast-beta.xml"
-}
-
struct MacUpdatesView: View {
+ @ObservedObject var updaterViewModel: MacUpdatesViewModel
+
@AppStorage(WFDefaults.automaticallyChecksForUpdates, store: UserDefaults.shared)
var automaticallyChecksForUpdates: Bool = false
@AppStorage(WFDefaults.subscribeToBetaUpdates, store: UserDefaults.shared)
var subscribeToBetaUpdates: Bool = false
+
@State private var lastUpdateCheck: Date?
private let betaWarningString = """
To get brand new features before each official release, choose "Test versions." Note that test versions may have bugs \
that can cause crashes and data loss.
"""
static let lastUpdateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
formatter.doesRelativeDateFormatting = true
return formatter
}()
var body: some View {
VStack(spacing: 24) {
Toggle(isOn: $automaticallyChecksForUpdates, label: {
Text("Check for updates automatically")
})
VStack {
Button(action: {
- SUUpdater.shared()?.checkForUpdates(self)
- DispatchQueue.main.async {
- lastUpdateCheck = SUUpdater.shared()?.lastUpdateCheckDate
+ updaterViewModel.checkForUpdates()
+ // There's a delay between requesting an update, and the timestamp for that update request being
+ // written to user defaults; we therefore delay updating the "Last checked" UI for one second.
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+ lastUpdateCheck = updaterViewModel.getLastUpdateCheckDate()
}
}, label: {
Text("Check For Updates")
})
HStack {
Text("Last checked:")
.font(.caption)
if let lastUpdateCheck = lastUpdateCheck {
Text(lastUpdateCheck, formatter: Self.lastUpdateFormatter)
.font(.caption)
} else {
Text("Never")
.font(.caption)
}
}
}
VStack(spacing: 16) {
HStack(alignment: .top) {
Text("Download:")
Picker(selection: $subscribeToBetaUpdates, label: Text("Download:"), content: {
Text("Release versions").tag(false)
Text("Test versions").tag(true)
})
.pickerStyle(RadioGroupPickerStyle())
.labelsHidden()
}
Text(betaWarningString)
.frame(width: 350)
.foregroundColor(.secondary)
}
}
.padding()
.onAppear {
- lastUpdateCheck = SUUpdater.shared()?.lastUpdateCheckDate
+ lastUpdateCheck = updaterViewModel.getLastUpdateCheckDate()
}
.onChange(of: automaticallyChecksForUpdates) { value in
- SUUpdater.shared()?.automaticallyChecksForUpdates = value
+ updaterViewModel.automaticallyCheckForUpdates = value
}
- .onChange(of: subscribeToBetaUpdates) { value in
- if value {
- SUUpdater.shared()?.feedURL = URL(string: AppcastFeedUrl.beta.rawValue)
- } else {
- SUUpdater.shared()?.feedURL = URL(string: AppcastFeedUrl.release.rawValue)
- }
+ .onChange(of: subscribeToBetaUpdates) { _ in
+ updaterViewModel.toggleAllowedChannels()
}
}
}
struct MacUpdatesView_Previews: PreviewProvider {
static var previews: some View {
- MacUpdatesView()
+ MacUpdatesView(updaterViewModel: MacUpdatesViewModel())
}
}
diff --git a/macOS/Settings/MacUpdatesViewModel.swift b/macOS/Settings/MacUpdatesViewModel.swift
index 6b0c6fa..b960524 100644
--- a/macOS/Settings/MacUpdatesViewModel.swift
+++ b/macOS/Settings/MacUpdatesViewModel.swift
@@ -1,38 +1,73 @@
/// See https://sparkle-project.org/documentation/programmatic-setup#create-an-updater-in-swiftui
import SwiftUI
import Sparkle
/// This view model class manages Sparkle's updater and publishes when new updates are allowed to be checked.
final class MacUpdatesViewModel: ObservableObject {
@Published var canCheckForUpdates = false
private let updaterController: SPUStandardUpdaterController
+ private let updaterDelegate = MacUpdatesViewModelDelegate()
+
+ var automaticallyCheckForUpdates: Bool {
+ get {
+ return updaterController.updater.automaticallyChecksForUpdates
+ }
+ set(newValue) {
+ updaterController.updater.automaticallyChecksForUpdates = newValue
+ }
+ }
init() {
updaterController = SPUStandardUpdaterController(startingUpdater: true,
- updaterDelegate: nil,
+ updaterDelegate: updaterDelegate,
userDriverDelegate: nil)
updaterController.updater.publisher(for: \.canCheckForUpdates)
.assign(to: &$canCheckForUpdates)
+
+ if automaticallyCheckForUpdates {
+ updaterController.updater.checkForUpdatesInBackground()
+ }
}
func checkForUpdates() {
updaterController.checkForUpdates(nil)
}
+ func getLastUpdateCheckDate() -> Date? {
+ return updaterController.updater.lastUpdateCheckDate
+ }
+
+ @discardableResult
+ func toggleAllowedChannels() -> Set<String> {
+ return updaterDelegate.allowedChannels(for: updaterController.updater)
+ }
+
+}
+
+final class MacUpdatesViewModelDelegate: NSObject, SPUUpdaterDelegate {
+
+ @AppStorage(WFDefaults.subscribeToBetaUpdates, store: UserDefaults.shared)
+ var subscribeToBetaUpdates: Bool = false
+
+ func allowedChannels(for updater: SPUUpdater) -> Set<String> {
+ let allowedChannels = Set(subscribeToBetaUpdates ? ["beta"] : [])
+ return allowedChannels
+ }
+
}
// This additional view is needed for the disabled state on the menu item to work properly before Monterey.
// See https://stackoverflow.com/questions/68553092/menu-not-updating-swiftui-bug for more information
struct CheckForUpdatesView: View {
@ObservedObject var updaterViewModel: MacUpdatesViewModel
var body: some View {
Button("Check for Updates…", action: updaterViewModel.checkForUpdates)
.disabled(!updaterViewModel.canCheckForUpdates)
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jul 17, 7:23 PM (7 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3324284
Attached To
rWFSUI WriteFreely SwiftUI
Event Timeline
Log In to Comment