diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index 79d7047..c835613 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -1,115 +1,100 @@ import SwiftUI struct CollectionListView: View { @EnvironmentObject var model: WriteFreelyModel @FetchRequest( entity: WFACollection.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)] ) var collections: FetchedResults var body: some View { List(selection: $model.selectedCollection) { if model.account.isLoggedIn { NavigationLink( destination: PostListView(), isActive: Binding( get: { () -> Bool in model.selectedCollection == nil && model.showAllPosts }, set: { newValue in if newValue { self.model.showAllPosts = true self.model.selectedCollection = nil } else { // No-op } } ), label: { Text("All Posts") }) NavigationLink( destination: PostListView(), isActive: Binding( get: { () -> Bool in model.selectedCollection == nil && !model.showAllPosts }, set: { newValue in if newValue { self.model.showAllPosts = false self.model.selectedCollection = nil } else { // No-op } } ), label: { Text(model.account.server == "https://write.as" ? "Anonymous" : "Drafts") }) Section(header: Text("Your Blogs")) { ForEach(collections, id: \.self) { collection in NavigationLink( destination: PostListView(), isActive: Binding( get: { () -> Bool in model.selectedCollection == collection && !model.showAllPosts }, set: { newValue in if newValue { self.model.showAllPosts = false self.model.selectedCollection = collection } else { // No-op } } ), label: { Text(collection.title) } ) } } } else { NavigationLink(destination: PostListView()) { Text("Drafts") } } } .navigationTitle( model.account.isLoggedIn ? "\(URL(string: model.account.server)?.host ?? "WriteFreely")" : "WriteFreely" ) .listStyle(SidebarListStyle()) - .onAppear(perform: { - #if os(iOS) - if model.editor.showAllPostsFlag { - DispatchQueue.main.async { - self.model.selectedCollection = nil - self.model.showAllPosts = true - } - } else { - DispatchQueue.main.async { - self.model.selectedCollection = model.editor.fetchSelectedCollectionFromAppStorage() - self.model.showAllPosts = false - } - } - #endif - }) .onChange(of: model.selectedCollection) { collection in if collection != model.editor.fetchSelectedCollectionFromAppStorage() { self.model.editor.selectedCollectionURL = collection?.objectID.uriRepresentation() } } .onChange(of: model.showAllPosts) { value in if value != model.editor.showAllPostsFlag { self.model.editor.showAllPostsFlag = model.showAllPosts } } } } struct CollectionListView_LoggedOutPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.persistentContainer.viewContext let model = WriteFreelyModel() return CollectionListView() .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/Shared/PostEditor/PostEditorModel.swift b/Shared/PostEditor/PostEditorModel.swift index c07c703..014681f 100644 --- a/Shared/PostEditor/PostEditorModel.swift +++ b/Shared/PostEditor/PostEditorModel.swift @@ -1,76 +1,63 @@ import SwiftUI import CoreData enum PostAppearance: String { case sans = "OpenSans-Regular" case mono = "Hack-Regular" case serif = "Lora-Regular" } struct PostEditorModel { @AppStorage("showAllPostsFlag") var showAllPostsFlag: Bool = false @AppStorage("selectedCollectionURL") var selectedCollectionURL: URL? - @AppStorage("selectedPostURL") var selectedPostURL: URL? - @AppStorage("lastDraftURL") private var lastDraftURL: URL? + @AppStorage("lastDraftURL") var lastDraftURL: URL? func saveLastDraft(_ post: WFAPost) { self.lastDraftURL = post.status != PostStatus.published.rawValue ? post.objectID.uriRepresentation() : nil } func clearLastDraft() { self.lastDraftURL = nil } - func fetchLastDraftFromUserDefaults() -> WFAPost? { + func fetchLastDraftFromAppStorage() -> WFAPost? { guard let postURL = lastDraftURL else { return nil } - - let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator - guard let postManagedObjectID = coordinator.managedObjectID(forURIRepresentation: postURL) else { return nil } - guard let post = LocalStorageManager.persistentContainer.viewContext.object( - with: postManagedObjectID - ) as? WFAPost else { return nil } - + guard let post = fetchManagedObject(from: postURL) as? WFAPost else { return nil } return post } func generateNewLocalPost(withFont appearance: Int) -> WFAPost { let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) managedPost.createdDate = Date() managedPost.title = "" managedPost.body = "" managedPost.status = PostStatus.local.rawValue managedPost.collectionAlias = nil switch appearance { 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 } return managedPost } + func fetchSelectedCollectionFromAppStorage() -> WFACollection? { + guard let collectionURL = selectedCollectionURL else { return nil } + guard let collection = fetchManagedObject(from: collectionURL) as? WFACollection else { return nil } + return collection + } + private func fetchManagedObject(from objectURL: URL) -> NSManagedObject? { let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator guard let managedObjectID = coordinator.managedObjectID(forURIRepresentation: objectURL) else { return nil } let object = LocalStorageManager.persistentContainer.viewContext.object(with: managedObjectID) return object } - - func fetchSelectedPostFromAppStorage() -> WFAPost? { - guard let postURL = selectedPostURL else { return nil } - guard let post = fetchManagedObject(from: postURL) as? WFAPost else { return nil } - return post - } - - func fetchSelectedCollectionFromAppStorage() -> WFACollection? { - guard let collectionURL = selectedCollectionURL else { return nil } - guard let collection = fetchManagedObject(from: collectionURL) as? WFACollection else { return nil } - return collection - } } diff --git a/Shared/PostList/PostListFilteredView.swift b/Shared/PostList/PostListFilteredView.swift index dd11691..7826be9 100644 --- a/Shared/PostList/PostListFilteredView.swift +++ b/Shared/PostList/PostListFilteredView.swift @@ -1,157 +1,140 @@ import SwiftUI struct PostListFilteredView: View { @EnvironmentObject var model: WriteFreelyModel @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(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 - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.model.selectedPost = model.editor.fetchSelectedPostFromAppStorage() - } }) .onChange(of: fetchRequest.wrappedValue.count, perform: { value in self.postCount = value }) - .onChange(of: model.selectedPost) { post in - if post != model.editor.fetchSelectedPostFromAppStorage() { - saveSelectedPostURL(post) - } - } #else 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 - if post != fetchSelectedPostFromAppStorage() { - saveSelectedPostURL(post) - } - } #endif } - private func saveSelectedPostURL(_ post: WFAPost?) { - self.model.editor.selectedPostURL = post?.objectID.uriRepresentation() - } - 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 25dfe78..e6aaa58 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -1,144 +1,146 @@ 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() #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: { - #if os(macOS) if model.editor.showAllPostsFlag { DispatchQueue.main.async { self.model.selectedCollection = nil self.model.showAllPosts = true } } else { DispatchQueue.main.async { self.model.selectedCollection = model.editor.fetchSelectedCollectionFromAppStorage() self.model.showAllPosts = false } } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.model.selectedPost = model.editor.fetchSelectedPostFromAppStorage() + if model.editor.lastDraftURL != nil { + self.model.selectedPost = model.editor.fetchLastDraftFromAppStorage() + } else { + createNewLocalPost() + } } - #endif }) .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 = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { // Set it as the selectedPost DispatchQueue.main.asyncAfter(deadline: .now()) { self.model.selectedPost = managedPost } } } }