diff --git a/Shared/PostList/PostListFilteredView.swift b/Shared/PostList/PostListFilteredView.swift index 1b7fbf4..6848ce6 100644 --- a/Shared/PostList/PostListFilteredView.swift +++ b/Shared/PostList/PostListFilteredView.swift @@ -1,151 +1,165 @@ import SwiftUI struct PostListFilteredView: View { @EnvironmentObject var model: WriteFreelyModel @AppStorage("selectedPostURL") var selectedPostURL: URL? @Binding var postCount: Int @FetchRequest(entity: WFACollection.entity(), sortDescriptors: []) var collections: FetchedResults var fetchRequest: FetchRequest init(collection: WFACollection?, showAllPosts: Bool, postCount: Binding) { if showAllPosts { fetchRequest = FetchRequest( entity: WFAPost.entity(), sortDescriptors: [NSSortDescriptor(key: "createdDate", ascending: false)] ) } else { if let collectionAlias = collection?.alias { fetchRequest = FetchRequest( entity: WFAPost.entity(), sortDescriptors: [NSSortDescriptor(key: "createdDate", ascending: false)], predicate: NSPredicate(format: "collectionAlias == %@", collectionAlias) ) } else { fetchRequest = FetchRequest( entity: WFAPost.entity(), sortDescriptors: [NSSortDescriptor(key: "createdDate", ascending: false)], predicate: NSPredicate(format: "collectionAlias == nil") ) } } _postCount = postCount } var body: some View { #if os(iOS) - List { + List(selection: $model.selectedPost) { ForEach(fetchRequest.wrappedValue, id: \.self) { post in NavigationLink( destination: PostEditorView(post: post), tag: post, selection: $model.selectedPost, label: { if model.showAllPosts { if let collection = collections.filter { $0.alias == post.collectionAlias }.first { PostCellView(post: post, collectionName: collection.title) } else { let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts" PostCellView(post: post, collectionName: collectionName) } } else { PostCellView(post: post) } }) .deleteDisabled(post.status != PostStatus.local.rawValue) } .onDelete(perform: { indexSet in for index in indexSet { let post = fetchRequest.wrappedValue[index] delete(post) } }) } .onAppear(perform: { self.postCount = fetchRequest.wrappedValue.count }) .onChange(of: fetchRequest.wrappedValue.count, perform: { value in self.postCount = value }) .onChange(of: model.selectedPost) { post in - saveSelectedPostURL(post) + if post != fetchSelectedPostFromAppStorage() { + saveSelectedPostURL(post) + } } #else - List { + List(selection: $model.selectedPost) { ForEach(fetchRequest.wrappedValue, id: \.self) { post in NavigationLink( destination: PostEditorView(post: post), tag: post, selection: $model.selectedPost, label: { if model.showAllPosts { if let collection = collections.filter { $0.alias == post.collectionAlias }.first { PostCellView(post: post, collectionName: collection.title) } else { let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts" PostCellView(post: post, collectionName: collectionName) } } else { PostCellView(post: post) } }) .deleteDisabled(post.status != PostStatus.local.rawValue) } .onDelete(perform: { indexSet in for index in indexSet { let post = fetchRequest.wrappedValue[index] delete(post) } }) } .alert(isPresented: $model.isPresentingDeleteAlert) { Alert( title: Text("Delete Post?"), message: Text("This action cannot be undone."), primaryButton: .cancel() { model.postToDelete = nil }, secondaryButton: .destructive(Text("Delete"), action: { if let postToDelete = model.postToDelete { model.selectedPost = nil DispatchQueue.main.async { model.editor.clearLastDraft() model.posts.remove(postToDelete) } model.postToDelete = nil } }) ) } .onDeleteCommand(perform: { guard let selectedPost = model.selectedPost else { return } if selectedPost.status == PostStatus.local.rawValue { model.postToDelete = selectedPost model.isPresentingDeleteAlert = true } }) .onChange(of: model.selectedPost) { post in - saveSelectedPostURL(post) + if post != fetchSelectedPostFromAppStorage() { + saveSelectedPostURL(post) + } } #endif } private func saveSelectedPostURL(_ post: WFAPost?) { self.selectedPostURL = post?.objectID.uriRepresentation() } + private func fetchSelectedPostFromAppStorage() -> WFAPost? { + guard let objectURL = selectedPostURL else { return nil } + let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator + guard let managedObjectID = coordinator.managedObjectID(forURIRepresentation: objectURL) else { return nil } + guard let object = LocalStorageManager.persistentContainer.viewContext.object( + with: managedObjectID + ) as? WFAPost else { return nil } + return object + } + func delete(_ post: WFAPost) { DispatchQueue.main.async { if post == model.selectedPost { model.selectedPost = nil model.editor.clearLastDraft() } model.posts.remove(post) } } } struct PostListFilteredView_Previews: PreviewProvider { static var previews: some View { return PostListFilteredView(collection: nil, showAllPosts: false, postCount: .constant(999)) } } diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index 47d610d..9171cf3 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -1,174 +1,172 @@ import SwiftUI #if os(macOS) import Sparkle #endif @main struct CheckForDebugModifier { static func main() { #if os(macOS) if NSEvent.modifierFlags.contains(.shift) { print("Debug launch detected") // Run debug-mode launch code here } else { print("Normal launch detected") // Don't do anything } #endif WriteFreely_MultiPlatformApp.main() } } struct WriteFreely_MultiPlatformApp: App { @StateObject private var model = WriteFreelyModel() @AppStorage("showAllPostsFlag") var showAllPostsFlag: Bool = false @AppStorage("selectedCollectionURL") var selectedCollectionURL: URL? @AppStorage("selectedPostURL") var selectedPostURL: URL? #if os(macOS) // swiftlint:disable:next weak_delegate @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @State private var selectedTab = 0 #endif var body: some Scene { WindowGroup { ContentView() .onAppear(perform: { - DispatchQueue.main.async { - self.model.showAllPosts = showAllPostsFlag - self.model.selectedCollection = fetchSelectedCollectionFromAppStorage() - } + self.model.showAllPosts = showAllPostsFlag + self.model.selectedCollection = fetchSelectedCollectionFromAppStorage() DispatchQueue.main.asyncAfter(deadline: .now()) { self.model.selectedPost = fetchSelectedPostFromAppStorage() } }) .environmentObject(model) .environment(\.managedObjectContext, LocalStorageManager.persistentContainer.viewContext) // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. } .commands { #if os(macOS) CommandGroup(after: .appInfo, addition: { Button("Check For Updates") { SUUpdater.shared()?.checkForUpdates(self) } }) #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 } } } #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() .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 createNewLocalPost() { withAnimation { // Un-set the currently selected post self.model.selectedPost = nil // Navigate to the Drafts list self.model.showAllPosts = false self.model.selectedCollection = nil } // Create the new-post managed object let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) managedPost.createdDate = Date() managedPost.title = "" managedPost.body = "" managedPost.status = PostStatus.local.rawValue managedPost.collectionAlias = nil switch model.preferences.font { case 1: managedPost.appearance = "sans" case 2: managedPost.appearance = "wrap" default: managedPost.appearance = "serif" } if let languageCode = Locale.current.languageCode { managedPost.language = languageCode managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft } withAnimation { DispatchQueue.main.asyncAfter(deadline: .now()) { self.model.selectedPost = managedPost } } } private func fetchSelectedPostFromAppStorage() -> WFAPost? { guard let objectURL = selectedPostURL else { return nil } let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator guard let managedObjectID = coordinator.managedObjectID(forURIRepresentation: objectURL) else { return nil } guard let object = LocalStorageManager.persistentContainer.viewContext.object( with: managedObjectID ) as? WFAPost else { return nil } return object } private func fetchSelectedCollectionFromAppStorage() -> WFACollection? { guard let objectURL = selectedCollectionURL else { return nil } let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator guard let managedObjectID = coordinator.managedObjectID(forURIRepresentation: objectURL) else { return nil } guard let object = LocalStorageManager.persistentContainer.viewContext.object( with: managedObjectID ) as? WFACollection else { return nil } return object } }