From 386743e8b1aab8fe034bb650c45aca3a16f047b7 Mon Sep 17 00:00:00 2001 From: Dustin Mierau Date: Fri, 17 May 2024 13:59:46 -0700 Subject: Further work on bookmark syncing. Reworked code that prepopulated bookmarks when empty to wait for first sync. --- Hotline/Application-macOS.swift | 60 +++++++++++++++++++++++++++++++++-- Hotline/Models/ApplicationState.swift | 2 ++ Hotline/Models/Bookmark.swift | 23 +++----------- Hotline/macOS/TrackerView.swift | 28 +++++++++------- 4 files changed, 80 insertions(+), 33 deletions(-) (limited to 'Hotline') diff --git a/Hotline/Application-macOS.swift b/Hotline/Application-macOS.swift index 6b02024..9aec187 100644 --- a/Hotline/Application-macOS.swift +++ b/Hotline/Application-macOS.swift @@ -1,5 +1,6 @@ import SwiftUI import SwiftData +import CloudKit import UniformTypeIdentifiers @Observable @@ -16,8 +17,50 @@ final class AppLaunchState { } class AppDelegate: NSObject, NSApplicationDelegate { + private var cloudKitObserverToken: Any? = nil + func applicationDidFinishLaunching(_ notification: Notification) { AppLaunchState.shared.launchState = .launched + + CKContainer.default().accountStatus { status, error in + switch status { + case .noAccount: + print("iCloud Unavailable") + + // We mark CloudKit has available now since we're not waiting on + // a server sync or anything. + ApplicationState.shared.cloudKitReady = true + default: + print("iCloud Available") + + self.cloudKitObserverToken = NotificationCenter.default.addObserver(forName: NSPersistentCloudKitContainer.eventChangedNotification, object: nil, queue: OperationQueue.main) { [weak self] note in + print("iCloud Changed!") + ApplicationState.shared.cloudKitReady = true + + guard let token = self?.cloudKitObserverToken else { return } + NotificationCenter.default.removeObserver(token) + } + } + } + +// if FileManager.default.ubiquityIdentityToken == nil { +// print("iCloud Unavailable") +// +// // We mark CloudKit has available now since we're not waiting on +// // a server sync or anything. +// ApplicationState.shared.cloudKitReady = true +// } +// else { +// print("iCloud Available") +// +// self.cloudKitObserverToken = NotificationCenter.default.addObserver(forName: NSPersistentCloudKitContainer.eventChangedNotification, object: nil, queue: OperationQueue.main) { [weak self] note in +// print("iCloud Changed!") +// ApplicationState.shared.cloudKitReady = true +// +// guard let token = self?.cloudKitObserverToken else { return } +// NotificationCenter.default.removeObserver(token) +// } +// } } func applicationWillTerminate(_ notification: Notification) { @@ -37,6 +80,19 @@ struct Application: App { @FocusedValue(\.activeHotlineModel) private var activeHotline: Hotline? @FocusedValue(\.activeServerState) private var activeServerState: ServerState? + + private var modelContainer: ModelContainer = { + let schema = Schema([ + Bookmark.self + ]) + let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false, cloudKitDatabase: .private("iCloud.co.goodmake.hotline")) + let modelContainer = try! ModelContainer(for: schema, configurations: [config]) + + // Print local SwiftData sqlite file. +// print(modelContainer.configurations.first?.url.path(percentEncoded: false)) + + return modelContainer + }() var body: some Scene { // MARK: Tracker Window @@ -44,7 +100,7 @@ struct Application: App { TrackerView() .frame(minWidth: 250, minHeight: 250) } - .modelContainer(for: [Bookmark.self]) + .modelContainer(self.modelContainer) .defaultSize(width: 700, height: 550) .defaultPosition(.center) .keyboardShortcut(.init("R"), modifiers: .command) @@ -81,7 +137,7 @@ struct Application: App { } defaultValue: { Server(name: nil, description: nil, address: "") } - .modelContainer(for: [Bookmark.self]) + .modelContainer(self.modelContainer) .defaultSize(width: 750, height: 700) .defaultPosition(.center) .onChange(of: activeServerState) { diff --git a/Hotline/Models/ApplicationState.swift b/Hotline/Models/ApplicationState.swift index 5814fba..6e077c5 100644 --- a/Hotline/Models/ApplicationState.swift +++ b/Hotline/Models/ApplicationState.swift @@ -11,4 +11,6 @@ final class ApplicationState { var activeServerID: UUID? = nil var activeServerBanner: NSImage? = nil var activeServerName: String? = nil + + var cloudKitReady: Bool = false } diff --git a/Hotline/Models/Bookmark.swift b/Hotline/Models/Bookmark.swift index 3530b19..c713bb0 100644 --- a/Hotline/Models/Bookmark.swift +++ b/Hotline/Models/Bookmark.swift @@ -34,6 +34,10 @@ final class Bookmark { @Transient var servers: [Bookmark] = [] + func hash(into hasher: inout Hasher) { + + } + @Transient var displayAddress: String { switch self.type { @@ -53,25 +57,6 @@ final class Bookmark { return "\(self.address):\(String(self.port))" } } - -// if let s = server { -// if s.port == HotlinePorts.DefaultServerPort { -// return s.address -// } -// else { -// return "\(s.address):\(s.port)" -// } -// } -// else if let b = bookmark { -// if b.port == HotlinePorts.DefaultServerPort { -// return b.address -// } -// else { -// return "\(b.address):\(b.port)" -// } -// } - -// return nil } @Transient diff --git a/Hotline/macOS/TrackerView.swift b/Hotline/macOS/TrackerView.swift index ab70389..104e15d 100644 --- a/Hotline/macOS/TrackerView.swift +++ b/Hotline/macOS/TrackerView.swift @@ -11,6 +11,7 @@ struct TrackerView: View { @State private var refreshing = false @State private var trackerSheetPresented: Bool = false @State private var trackerSheetBookmark: Bookmark? = nil + @State private var attemptedPrepopulate: Bool = false @Query(sort: \Bookmark.order) private var bookmarks: [Bookmark] @State private var selection: Bookmark? = nil @@ -46,12 +47,22 @@ struct TrackerView: View { .environment(\.defaultMinListRowHeight, 34) .listStyle(.inset) .alternatingRowBackgrounds(.enabled) - .onAppear { -// Bookmark.deleteAll(context: modelContext) + .onChange(of: ApplicationState.shared.cloudKitReady) { + if attemptedPrepopulate { + print("Tracker: Already attempted to prepopulate bookmarks") + return + } + + print("Tracker: Prepopulating bookmarks") + + attemptedPrepopulate = true // Make sure default bookmarks are there when empty. Bookmark.populateDefaults(context: modelContext) } + .onAppear { +// Bookmark.deleteAll(context: modelContext) + } .contextMenu(forSelectionType: Bookmark.self) { items in if let item = items.first { if item.type == .temporary { @@ -174,15 +185,6 @@ struct TrackerView: View { .help("Connect to Server") } } -// .onReceive(NotificationCenter.default.publisher(for: NSNotification.BookmarkAdded)) { notification in -// guard let bookmarks = bookmarks.bookmarks, let userInfo = notification.userInfo else { -// return -// } -// -// if let i = userInfo["index"] as? Int, bookmarks.count > i { -// self.servers.insert(TrackerItem(bookmark: bookmarks[i]), at: i) -// } -// } .onOpenURL(perform: { url in if let s = Server(url: url) { openWindow(id: "server", value: s) @@ -309,7 +311,7 @@ struct TrackerItemView: View { let bookmark: Bookmark var body: some View { - HStack(alignment: .center, spacing: 8) { + HStack(alignment: .center, spacing: 6) { if bookmark.type == .tracker { Button { bookmark.expanded.toggle() @@ -323,6 +325,7 @@ struct TrackerItemView: View { .buttonStyle(.plain) .frame(width: 10) .padding(.leading, 4) + .padding(.trailing, 2) } switch bookmark.type { @@ -345,6 +348,7 @@ struct TrackerItemView: View { .frame(width: 11, height: 11, alignment: .center) .opacity(0.5) .padding(.leading, 3) + .padding(.trailing, 2) Image("Server") .resizable() .scaledToFit() -- cgit