From bd695b23e614e9fb04954ac8f933eb7792de8429 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Thu, 29 Oct 2020 12:03:31 -0400 Subject: [PATCH 1/8] Remove anything that attempts to set selected post or last draft --- Shared/Models/WriteFreelyModel.swift | 14 +------------ Shared/Navigation/ContentView.swift | 24 ----------------------- Shared/PostEditor/PostEditorModel.swift | 26 ------------------------- iOS/PostEditor/PostEditorView.swift | 11 ----------- macOS/PostEditor/PostEditorView.swift | 11 ----------- 5 files changed, 1 insertion(+), 85 deletions(-) diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index ca3764b..89cffea 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -13,19 +13,7 @@ class WriteFreelyModel: ObservableObject { @Published var isLoggingIn: Bool = false @Published var isProcessingRequest: Bool = false @Published var hasNetworkConnection: Bool = true - @Published var selectedPost: WFAPost? { - didSet { - if let post = selectedPost { - if post.status != PostStatus.published.rawValue { - editor.setLastDraft(post) - } else { - editor.clearLastDraft() - } - } else { - editor.clearLastDraft() - } - } - } + @Published var selectedPost: WFAPost? @Published var isPresentingDeleteAlert: Bool = false @Published var isPresentingLoginErrorAlert: Bool = false @Published var isPresentingNetworkErrorAlert: Bool = false diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index ee33945..bdd6d3e 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -12,30 +12,6 @@ struct ContentView: View { Text("Select a post, or create a new local draft.") .foregroundColor(.secondary) } - .onAppear(perform: { - if let lastDraft = self.model.editor.fetchLastDraft() { - model.selectedPost = lastDraft - } else { - let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) - managedPost.createdDate = Date() - managedPost.title = "" - managedPost.body = "" - managedPost.status = PostStatus.local.rawValue - switch self.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 - } - model.selectedPost = managedPost - } - }) .environmentObject(model) .alert(isPresented: $model.isPresentingDeleteAlert) { Alert( diff --git a/Shared/PostEditor/PostEditorModel.swift b/Shared/PostEditor/PostEditorModel.swift index ca8959f..d8a5683 100644 --- a/Shared/PostEditor/PostEditorModel.swift +++ b/Shared/PostEditor/PostEditorModel.swift @@ -8,31 +8,5 @@ enum PostAppearance: String { } struct PostEditorModel { - let lastDraftObjectURLKey = "lastDraftObjectURLKey" - private(set) var lastDraft: WFAPost? - mutating func setLastDraft(_ post: WFAPost) { - lastDraft = post - UserDefaults.standard.set(post.objectID.uriRepresentation(), forKey: lastDraftObjectURLKey) - } - - mutating func fetchLastDraft() -> WFAPost? { - let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator - - // See if we have a lastDraftObjectURI - guard let lastDraftObjectURI = UserDefaults.standard.url(forKey: lastDraftObjectURLKey) else { return nil } - - // See if we can get an ObjectID from the URI representation - guard let lastDraftObjectID = coordinator.managedObjectID(forURIRepresentation: lastDraftObjectURI) else { - return nil - } - - lastDraft = LocalStorageManager.persistentContainer.viewContext.object(with: lastDraftObjectID) as? WFAPost - return lastDraft - } - - mutating func clearLastDraft() { - lastDraft = nil - UserDefaults.standard.removeObject(forKey: lastDraftObjectURLKey) - } } diff --git a/iOS/PostEditor/PostEditorView.swift b/iOS/PostEditor/PostEditorView.swift index 818d4bd..e2ae217 100644 --- a/iOS/PostEditor/PostEditorView.swift +++ b/iOS/PostEditor/PostEditorView.swift @@ -128,17 +128,6 @@ struct PostEditorView: View { updatingBodyFromServer = true } }) - .onChange(of: post.status, perform: { _ in - if post.status != PostStatus.published.rawValue { - DispatchQueue.main.async { - model.editor.setLastDraft(post) - } - } else { - DispatchQueue.main.async { - model.editor.clearLastDraft() - } - } - }) .onChange(of: selectedCollection, perform: { [selectedCollection] newCollection in if post.collectionAlias == newCollection?.alias { return diff --git a/macOS/PostEditor/PostEditorView.swift b/macOS/PostEditor/PostEditorView.swift index ba7cb53..1b023c2 100644 --- a/macOS/PostEditor/PostEditorView.swift +++ b/macOS/PostEditor/PostEditorView.swift @@ -41,17 +41,6 @@ struct PostEditorView: View { post.status = PostStatus.published.rawValue } }) - .onChange(of: post.status, perform: { _ in - if post.status != PostStatus.published.rawValue { - DispatchQueue.main.async { - model.editor.setLastDraft(post) - } - } else { - DispatchQueue.main.async { - model.editor.clearLastDraft() - } - } - }) .onDisappear(perform: { if post.title.count == 0 && post.body.count == 0 From e7408a47e9c834805b18852e719e2d7e1dccb144 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Thu, 29 Oct 2020 16:09:35 -0400 Subject: [PATCH 2/8] Fix crash-on-load bug by cleaning up/thread-sanitizing CoreData calls --- Shared/Account/AccountLogoutView.swift | 1 - Shared/Models/WriteFreelyModel.swift | 122 +++++++++++------- .../PostCollection/CollectionListView.swift | 1 - Shared/PostList/PostListModel.swift | 22 ---- Shared/PostList/PostListView.swift | 4 +- iOS/PostEditor/PostEditorView.swift | 3 - macOS/PostEditor/PostEditorView.swift | 2 - 7 files changed, 74 insertions(+), 81 deletions(-) diff --git a/Shared/Account/AccountLogoutView.swift b/Shared/Account/AccountLogoutView.swift index 8613d6b..0e2f0cf 100644 --- a/Shared/Account/AccountLogoutView.swift +++ b/Shared/Account/AccountLogoutView.swift @@ -2,7 +2,6 @@ import SwiftUI struct AccountLogoutView: View { @EnvironmentObject var model: WriteFreelyModel - @Environment(\.managedObjectContext) var moc @State private var isPresentingLogoutConfirmation: Bool = false @State private var editedPostsWarningString: String = "" diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index 89cffea..7a6962d 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -335,44 +335,53 @@ private extension WriteFreelyModel { DispatchQueue.main.async { self.isProcessingRequest = false } + let request = WFAPost.createFetchRequest() do { - var postsToDelete = posts.userPosts.filter { $0.status != PostStatus.local.rawValue } - let fetchedPosts = try result.get() - for fetchedPost in fetchedPosts { - if let managedPost = posts.userPosts.first(where: { $0.postId == fetchedPost.postId }) { - managedPost.wasDeletedFromServer = false - if let fetchedPostUpdatedDate = fetchedPost.updatedDate, - let localPostUpdatedDate = managedPost.updatedDate { - managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate + let locallyCachedPosts = try LocalStorageManager.persistentContainer.viewContext.fetch(request) + do { + var postsToDelete = locallyCachedPosts.filter { $0.status != PostStatus.local.rawValue } + let fetchedPosts = try result.get() + for fetchedPost in fetchedPosts { + if let managedPost = locallyCachedPosts.first(where: { $0.postId == fetchedPost.postId }) { + DispatchQueue.main.async { + managedPost.wasDeletedFromServer = false + if let fetchedPostUpdatedDate = fetchedPost.updatedDate, + let localPostUpdatedDate = managedPost.updatedDate { + managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate + } else { + print("Error: could not determine which copy of post is newer") + } + postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId }) + } } else { - print("Error: could not determine which copy of post is newer") + DispatchQueue.main.async { + let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) + managedPost.postId = fetchedPost.postId + managedPost.slug = fetchedPost.slug + managedPost.appearance = fetchedPost.appearance + managedPost.language = fetchedPost.language + managedPost.rtl = fetchedPost.rtl ?? false + managedPost.createdDate = fetchedPost.createdDate + managedPost.updatedDate = fetchedPost.updatedDate + managedPost.title = fetchedPost.title ?? "" + managedPost.body = fetchedPost.body + managedPost.collectionAlias = fetchedPost.collectionAlias + managedPost.status = PostStatus.published.rawValue + managedPost.wasDeletedFromServer = false + } } - postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId }) - } else { - let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) - managedPost.postId = fetchedPost.postId - managedPost.slug = fetchedPost.slug - managedPost.appearance = fetchedPost.appearance - managedPost.language = fetchedPost.language - managedPost.rtl = fetchedPost.rtl ?? false - managedPost.createdDate = fetchedPost.createdDate - managedPost.updatedDate = fetchedPost.updatedDate - managedPost.title = fetchedPost.title ?? "" - managedPost.body = fetchedPost.body - managedPost.collectionAlias = fetchedPost.collectionAlias - managedPost.status = PostStatus.published.rawValue - managedPost.wasDeletedFromServer = false } - } - for post in postsToDelete { - post.wasDeletedFromServer = true - } - DispatchQueue.main.async { - LocalStorageManager().saveContext() - self.posts.loadCachedPosts() + DispatchQueue.main.async { + for post in postsToDelete { + post.wasDeletedFromServer = true + } + LocalStorageManager().saveContext() + } + } catch { + print(error) } } catch { - print(error) + print("Error: Failed to fetch cached posts") } } @@ -387,23 +396,37 @@ private extension WriteFreelyModel { // See: https://github.com/writeas/writefreely-swift/issues/20 do { let fetchedPost = try result.get() - let foundPostIndex = posts.userPosts.firstIndex(where: { - $0.title == fetchedPost.title && $0.body == fetchedPost.body - }) - guard let index = foundPostIndex else { return } - let cachedPost = self.posts.userPosts[index] - cachedPost.appearance = fetchedPost.appearance - cachedPost.body = fetchedPost.body - cachedPost.createdDate = fetchedPost.createdDate - cachedPost.language = fetchedPost.language - cachedPost.postId = fetchedPost.postId - cachedPost.rtl = fetchedPost.rtl ?? false - cachedPost.slug = fetchedPost.slug - cachedPost.status = PostStatus.published.rawValue - cachedPost.title = fetchedPost.title ?? "" - cachedPost.updatedDate = fetchedPost.updatedDate - DispatchQueue.main.async { - LocalStorageManager().saveContext() + let request = WFAPost.createFetchRequest() + let matchBodyPredicate = NSPredicate(format: "body == %@", fetchedPost.body) + if let fetchedPostTitle = fetchedPost.title { + let matchTitlePredicate = NSPredicate(format: "title == %@", fetchedPostTitle) + request.predicate = NSCompoundPredicate( + andPredicateWithSubpredicates: [ + matchTitlePredicate, + matchBodyPredicate + ] + ) + } else { + request.predicate = matchBodyPredicate + } + do { + let cachedPostsResults = try LocalStorageManager.persistentContainer.viewContext.fetch(request) + guard let cachedPost = cachedPostsResults.first else { return } + cachedPost.appearance = fetchedPost.appearance + cachedPost.body = fetchedPost.body + cachedPost.createdDate = fetchedPost.createdDate + cachedPost.language = fetchedPost.language + cachedPost.postId = fetchedPost.postId + cachedPost.rtl = fetchedPost.rtl ?? false + cachedPost.slug = fetchedPost.slug + cachedPost.status = PostStatus.published.rawValue + cachedPost.title = fetchedPost.title ?? "" + cachedPost.updatedDate = fetchedPost.updatedDate + DispatchQueue.main.async { + LocalStorageManager().saveContext() + } + } catch { + print("Error: Failed to fetch cached posts") } } catch { print(error) @@ -435,7 +458,6 @@ private extension WriteFreelyModel { cachedPost.hasNewerRemoteCopy = false DispatchQueue.main.async { LocalStorageManager().saveContext() - self.posts.loadCachedPosts() } } catch { print(error) diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index e8e50e9..09d1170 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -2,7 +2,6 @@ import SwiftUI struct CollectionListView: View { @EnvironmentObject var model: WriteFreelyModel - @Environment(\.managedObjectContext) var moc @FetchRequest( entity: WFACollection.entity(), diff --git a/Shared/PostList/PostListModel.swift b/Shared/PostList/PostListModel.swift index 774a451..e6464e4 100644 --- a/Shared/PostList/PostListModel.swift +++ b/Shared/PostList/PostListModel.swift @@ -2,40 +2,18 @@ import SwiftUI import CoreData class PostListModel: ObservableObject { - @Published var userPosts = [WFAPost]() - - init() { - loadCachedPosts() - } - - func loadCachedPosts() { - let request = WFAPost.createFetchRequest() - let sort = NSSortDescriptor(key: "createdDate", ascending: false) - request.sortDescriptors = [sort] - - userPosts = [] - do { - let cachedPosts = try LocalStorageManager.persistentContainer.viewContext.fetch(request) - userPosts.append(contentsOf: cachedPosts) - } catch { - print("Error: Failed to fetch cached posts.") - } - } - func remove(_ post: WFAPost) { LocalStorageManager.persistentContainer.viewContext.delete(post) LocalStorageManager().saveContext() } func purgePublishedPosts() { - userPosts = [] let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "WFAPost") fetchRequest.predicate = NSPredicate(format: "status != %i", 0) let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) do { try LocalStorageManager.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest) - loadCachedPosts() } catch { print("Error: Failed to purge cached posts.") } diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index bdaad24..928ec7a 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -2,7 +2,7 @@ import SwiftUI struct PostListView: View { @EnvironmentObject var model: WriteFreelyModel - @Environment(\.managedObjectContext) var moc + @Environment(\.managedObjectContext) var managedObjectContext @State var selectedCollection: WFACollection? @State var showAllPosts: Bool = false @@ -103,7 +103,7 @@ struct PostListView: View { } private func createNewLocalDraft() { - let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) + let managedPost = WFAPost(context: self.managedObjectContext) managedPost.createdDate = Date() managedPost.title = "" managedPost.body = "" diff --git a/iOS/PostEditor/PostEditorView.swift b/iOS/PostEditor/PostEditorView.swift index e2ae217..b369ba9 100644 --- a/iOS/PostEditor/PostEditorView.swift +++ b/iOS/PostEditor/PostEditorView.swift @@ -2,7 +2,6 @@ import SwiftUI struct PostEditorView: View { @EnvironmentObject var model: WriteFreelyModel - @Environment(\.managedObjectContext) var moc @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.presentationMode) var presentationMode @@ -147,7 +146,6 @@ struct PostEditorView: View { && post.postId == nil { DispatchQueue.main.async { model.posts.remove(post) - model.posts.loadCachedPosts() } } else if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { @@ -160,7 +158,6 @@ struct PostEditorView: View { private func publishPost() { DispatchQueue.main.async { LocalStorageManager().saveContext() - model.posts.loadCachedPosts() model.publish(post: post) } #if os(iOS) diff --git a/macOS/PostEditor/PostEditorView.swift b/macOS/PostEditor/PostEditorView.swift index 1b023c2..6d2b238 100644 --- a/macOS/PostEditor/PostEditorView.swift +++ b/macOS/PostEditor/PostEditorView.swift @@ -49,7 +49,6 @@ struct PostEditorView: View { && post.postId == nil { DispatchQueue.main.async { model.posts.remove(post) - model.posts.loadCachedPosts() } } else if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { @@ -62,7 +61,6 @@ struct PostEditorView: View { private func publishPost() { DispatchQueue.main.async { LocalStorageManager().saveContext() - model.posts.loadCachedPosts() model.publish(post: post) } } From 518d793d5dadab607f86a049b57470ebccc8a904 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Thu, 29 Oct 2020 17:05:01 -0400 Subject: [PATCH 3/8] Fix SwiftLint error about function body length --- Shared/Models/WriteFreelyModel.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index 7a6962d..c079cf5 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -348,9 +348,7 @@ private extension WriteFreelyModel { if let fetchedPostUpdatedDate = fetchedPost.updatedDate, let localPostUpdatedDate = managedPost.updatedDate { managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate - } else { - print("Error: could not determine which copy of post is newer") - } + } else { print("Error: could not determine which copy of post is newer") } postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId }) } } else { @@ -372,9 +370,7 @@ private extension WriteFreelyModel { } } DispatchQueue.main.async { - for post in postsToDelete { - post.wasDeletedFromServer = true - } + for post in postsToDelete { post.wasDeletedFromServer = true } LocalStorageManager().saveContext() } } catch { From 3c81b5cd6886f6a3abbebd8c7a12e8c2af89d4bd Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Thu, 29 Oct 2020 17:19:06 -0400 Subject: [PATCH 4/8] Propagate login/logout fixes to Mac app target --- Shared/Account/AccountLoginView.swift | 2 ++ Shared/Account/AccountLogoutView.swift | 31 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Shared/Account/AccountLoginView.swift b/Shared/Account/AccountLoginView.swift index 02c32f9..de7bb97 100644 --- a/Shared/Account/AccountLoginView.swift +++ b/Shared/Account/AccountLoginView.swift @@ -55,7 +55,9 @@ struct AccountLoginView: View { .padding() } else { Button(action: { + #if os(iOS) hideKeyboard() + #endif model.login( to: URL(string: server)!, as: username, password: password diff --git a/Shared/Account/AccountLogoutView.swift b/Shared/Account/AccountLogoutView.swift index 0e2f0cf..9f69a50 100644 --- a/Shared/Account/AccountLogoutView.swift +++ b/Shared/Account/AccountLogoutView.swift @@ -7,6 +7,7 @@ struct AccountLogoutView: View { @State private var editedPostsWarningString: String = "" var body: some View { + #if os(iOS) VStack { Spacer() VStack { @@ -30,6 +31,36 @@ struct AccountLogoutView: View { ] ) }) + #else + VStack { + Spacer() + VStack { + Text("Logged in as \(model.account.username)") + Text("on \(model.account.server)") + } + Spacer() + Button(action: logoutHandler, label: { + Text("Log Out") + }) + } + .sheet(isPresented: $isPresentingLogoutConfirmation) { + VStack { + Text("Log Out?") + .font(.title) + Text("\(editedPostsWarningString)You won't lose any local posts. Are you sure?") + HStack { + Button(action: model.logout, label: { + Text("Log Out") + }) + Button(action: { + self.isPresentingLogoutConfirmation = false + }, label: { + Text("Cancel") + }).keyboardShortcut(.cancelAction) + } + } + } + #endif } func logoutHandler() { From 06510f181e9c5a90b3b977c48fdbf3dae1ef01e7 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Thu, 29 Oct 2020 17:20:20 -0400 Subject: [PATCH 5/8] Fix post-count label to fetch info from managed object context --- Shared/PostList/PostListFilteredView.swift | 19 ++++++- Shared/PostList/PostListView.swift | 64 ++++++++-------------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/Shared/PostList/PostListFilteredView.swift b/Shared/PostList/PostListFilteredView.swift index bb42ba3..6b37511 100644 --- a/Shared/PostList/PostListFilteredView.swift +++ b/Shared/PostList/PostListFilteredView.swift @@ -2,12 +2,12 @@ import SwiftUI struct PostListFilteredView: View { @EnvironmentObject var model: WriteFreelyModel - + @Binding var postCount: Int @FetchRequest(entity: WFACollection.entity(), sortDescriptors: []) var collections: FetchedResults var fetchRequest: FetchRequest var showAllPosts: Bool - init(filter: String?, showAllPosts: Bool) { + init(filter: String?, showAllPosts: Bool, postCount: Binding) { self.showAllPosts = showAllPosts if showAllPosts { fetchRequest = FetchRequest( @@ -29,6 +29,7 @@ struct PostListFilteredView: View { ) } } + _postCount = postCount } var body: some View { @@ -60,6 +61,12 @@ struct PostListFilteredView: View { } }) } + .onAppear(perform: { + self.postCount = fetchRequest.wrappedValue.count + }) + .onChange(of: fetchRequest.wrappedValue.count, perform: { value in + self.postCount = value + }) #else List { ForEach(fetchRequest.wrappedValue, id: \.self) { post in @@ -79,6 +86,12 @@ struct PostListFilteredView: View { } }) } + .onAppear(perform: { + self.postCount = fetchRequest.wrappedValue.count + }) + .onChange(of: fetchRequest.wrappedValue.count, perform: { value in + self.postCount = value + }) .onDeleteCommand(perform: { guard let selectedPost = model.selectedPost else { return } if selectedPost.status == PostStatus.local.rawValue { @@ -96,6 +109,6 @@ struct PostListFilteredView: View { struct PostListFilteredView_Previews: PreviewProvider { static var previews: some View { - return PostListFilteredView(filter: nil, showAllPosts: false) + return PostListFilteredView(filter: nil, showAllPosts: false, postCount: .constant(999)) } } diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 928ec7a..dddca79 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -1,4 +1,5 @@ import SwiftUI +import Combine struct PostListView: View { @EnvironmentObject var model: WriteFreelyModel @@ -6,11 +7,12 @@ struct PostListView: View { @State var selectedCollection: WFACollection? @State var showAllPosts: Bool = false + @State private var postCount: Int = 0 var body: some View { #if os(iOS) GeometryReader { geometry in - PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts) + PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts, postCount: $postCount) .navigationTitle( showAllPosts ? "All Posts" : selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" @@ -32,7 +34,7 @@ struct PostListView: View { Image(systemName: "gear") }) Spacer() - Text(pluralizedPostCount(for: showPosts(for: selectedCollection))) + Text(postCount == 1 ? "\(postCount) post" : "\(postCount) posts") .foregroundColor(.secondary) Spacer() if model.isProcessingRequest { @@ -52,47 +54,27 @@ struct PostListView: View { } } #else //if os(macOS) - PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts) - .navigationTitle( - showAllPosts ? "All Posts" : selectedCollection?.title ?? ( - model.account.server == "https://write.as" ? "Anonymous" : "Drafts" + PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts, postCount: $postCount) + .navigationTitle( + showAllPosts ? "All Posts" : selectedCollection?.title ?? ( + model.account.server == "https://write.as" ? "Anonymous" : "Drafts" + ) ) - ) - .navigationSubtitle(pluralizedPostCount(for: showPosts(for: selectedCollection))) - .toolbar { - Button(action: { - createNewLocalDraft() - }, label: { - Image(systemName: "square.and.pencil") - }) - Button(action: { - reloadFromServer() - }, label: { - Image(systemName: "arrow.clockwise") - }) - .disabled(!model.account.isLoggedIn) - } - #endif - } - - private func pluralizedPostCount(for posts: [WFAPost]) -> String { - if posts.count == 1 { - return "1 post" - } else { - return "\(posts.count) posts" - } - } - - private func showPosts(for collection: WFACollection?) -> [WFAPost] { - if showAllPosts { - return model.posts.userPosts - } else { - if let selectedCollection = collection { - return model.posts.userPosts.filter { $0.collectionAlias == selectedCollection.alias } - } else { - return model.posts.userPosts.filter { $0.collectionAlias == nil } + .navigationSubtitle(postCount == 1 ? "\(postCount) post" : "\(postCount) posts") + .toolbar { + Button(action: { + createNewLocalDraft() + }, label: { + Image(systemName: "square.and.pencil") + }) + Button(action: { + reloadFromServer() + }, label: { + Image(systemName: "arrow.clockwise") + }) + .disabled(!model.account.isLoggedIn) } - } + #endif } private func reloadFromServer() { From 4ef40453d5483d09f32bae6b2ebd9bd148ab8ea2 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Fri, 30 Oct 2020 15:26:32 -0400 Subject: [PATCH 6/8] Add methods to save, clear, and retrieve last draft from UserDefaults --- Shared/PostEditor/PostEditorModel.swift | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Shared/PostEditor/PostEditorModel.swift b/Shared/PostEditor/PostEditorModel.swift index d8a5683..ae08d75 100644 --- a/Shared/PostEditor/PostEditorModel.swift +++ b/Shared/PostEditor/PostEditorModel.swift @@ -1,4 +1,4 @@ -import Foundation +import SwiftUI import CoreData enum PostAppearance: String { @@ -8,5 +8,25 @@ enum PostAppearance: String { } struct PostEditorModel { + @AppStorage("lastDraftURL") private 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? { + 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 } + + return post + } } From d129c0d0f93c6a7476edc5ec541b14a395e90b16 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Fri, 30 Oct 2020 16:28:34 -0400 Subject: [PATCH 7/8] Save/clear last draft based on status when loading post in editor --- iOS/PostEditor/PostEditorView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iOS/PostEditor/PostEditorView.swift b/iOS/PostEditor/PostEditorView.swift index b369ba9..954798d 100644 --- a/iOS/PostEditor/PostEditorView.swift +++ b/iOS/PostEditor/PostEditorView.swift @@ -137,6 +137,11 @@ struct PostEditorView: View { }) .onAppear(perform: { self.selectedCollection = collections.first { $0.alias == post.collectionAlias } + if post.status != PostStatus.published.rawValue { + self.model.editor.saveLastDraft(post) + } else { + self.model.editor.clearLastDraft() + } }) .onDisappear(perform: { if post.title.count == 0 From 01bfe7e74a43aa876c9a63ff89f01f4b2d03b1ff Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Mon, 2 Nov 2020 13:09:38 -0500 Subject: [PATCH 8/8] Call PostListModel.remove() async on main queue --- Shared/Navigation/ContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index bdd6d3e..e77fa11 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -20,7 +20,7 @@ struct ContentView: View { primaryButton: .destructive(Text("Delete"), action: { if let postToDelete = model.postToDelete { model.selectedPost = nil - withAnimation { + DispatchQueue.main.async { model.posts.remove(postToDelete) } model.postToDelete = nil