From f948a16ad003a6b61b0ea190fd3eaf07dfc3c6d8 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 27 Jan 2021 10:18:26 -0500 Subject: [PATCH 1/3] Use ObservedObject rather than unwrapping model.selectedPost --- Shared/PostList/PostListView.swift | 22 +-- macOS/Navigation/ActivePostToolbarView.swift | 155 +++++++++---------- 2 files changed, 88 insertions(+), 89 deletions(-) diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 5f0bdc6..149f37b 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -112,19 +112,21 @@ struct PostListView: View { ) .toolbar { ToolbarItemGroup(placement: .primaryAction) { - ActivePostToolbarView() - .alert(isPresented: $model.isPresentingNetworkErrorAlert, content: { - Alert( - title: Text("Connection Error"), - message: Text(""" + if model.selectedPost != nil { + ActivePostToolbarView(activePost: model.selectedPost!) + .alert(isPresented: $model.isPresentingNetworkErrorAlert, content: { + Alert( + title: Text("Connection Error"), + message: Text(""" There is no internet connection at the moment. \ Please reconnect or try again later. """), - dismissButton: .default(Text("OK"), action: { - model.isPresentingNetworkErrorAlert = false - }) - ) - }) + dismissButton: .default(Text("OK"), action: { + model.isPresentingNetworkErrorAlert = false + }) + ) + }) + } } } .navigationTitle( diff --git a/macOS/Navigation/ActivePostToolbarView.swift b/macOS/Navigation/ActivePostToolbarView.swift index bd18ff9..7b6d0c9 100644 --- a/macOS/Navigation/ActivePostToolbarView.swift +++ b/macOS/Navigation/ActivePostToolbarView.swift @@ -2,6 +2,7 @@ import SwiftUI struct ActivePostToolbarView: View { @EnvironmentObject var model: WriteFreelyModel + @ObservedObject var activePost: WFAPost @State private var isPresentingSharingServicePicker: Bool = false @State private var selectedCollection: WFACollection? @@ -11,100 +12,96 @@ struct ActivePostToolbarView: View { ) var collections: FetchedResults var body: some View { - if let activePost = model.selectedPost { - HStack { - if model.account.isLoggedIn && - activePost.status != PostStatus.local.rawValue && - !(activePost.wasDeletedFromServer || activePost.hasNewerRemoteCopy) { - Section(header: Text("Move To:")) { - Picker(selection: $selectedCollection, label: Text("Move To…"), content: { - Text("\(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") - .tag(nil as WFACollection?) - Divider() - ForEach(collections) { collection in - Text("\(collection.title)").tag(collection as WFACollection?) - } - }) - } - } - PostEditorStatusToolbarView(post: activePost) - .frame(minWidth: 50, alignment: .center) - .layoutPriority(1) - .padding(.horizontal) - if activePost.status == PostStatus.local.rawValue { - Menu(content: { - Label("Publish To:", systemImage: "paperplane") + HStack { + if model.account.isLoggedIn && + activePost.status != PostStatus.local.rawValue && + !(activePost.wasDeletedFromServer || activePost.hasNewerRemoteCopy) { + Section(header: Text("Move To:")) { + Picker(selection: $selectedCollection, label: Text("Move To…"), content: { + Text("\(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") + .tag(nil as WFACollection?) Divider() + ForEach(collections) { collection in + Text("\(collection.title)").tag(collection as WFACollection?) + } + }) + } + } + PostEditorStatusToolbarView(post: activePost) + .frame(minWidth: 50, alignment: .center) + .layoutPriority(1) + .padding(.horizontal) + if activePost.status == PostStatus.local.rawValue { + Menu(content: { + Label("Publish To:", systemImage: "paperplane") + Divider() + Button(action: { + if model.account.isLoggedIn { + withAnimation { + activePost.collectionAlias = nil + publishPost(activePost) + } + } else { + openSettingsWindow() + } + }, label: { + Text("\(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") + }) + ForEach(collections) { collection in Button(action: { if model.account.isLoggedIn { withAnimation { - activePost.collectionAlias = nil + activePost.collectionAlias = collection.alias publishPost(activePost) } } else { openSettingsWindow() } }, label: { - Text("\(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") + Text("\(collection.title)") }) - ForEach(collections) { collection in - Button(action: { - if model.account.isLoggedIn { - withAnimation { - activePost.collectionAlias = collection.alias - publishPost(activePost) - } - } else { - openSettingsWindow() - } - }, label: { - Text("\(collection.title)") - }) - } - }, label: { - Label("Publish…", systemImage: "paperplane") - }) - .disabled(activePost.body.isEmpty) - .help("Publish the post to the web.\(model.account.isLoggedIn ? "" : " You must be logged in to do this.")") // swiftlint:disable:this line_length - } else { - HStack(spacing: 4) { - Button( - action: { - self.isPresentingSharingServicePicker = true - }, - label: { Image(systemName: "square.and.arrow.up") } - ) - .disabled(activePost.status == PostStatus.local.rawValue) - .help("Copy the post's URL to your Mac's pasteboard.") - .popover(isPresented: $isPresentingSharingServicePicker) { - PostEditorSharingPicker( - isPresented: $isPresentingSharingServicePicker, - sharingItems: createPostUrl() - ) - .frame(width: .zero, height: .zero) - } - Button(action: { publishPost(activePost) }, label: { Image(systemName: "paperplane") }) - .disabled(activePost.body.isEmpty || activePost.status == PostStatus.published.rawValue) - .help("Publish the post to the web.\(model.account.isLoggedIn ? "" : " You must be logged in to do this.")") // swiftlint:disable:this line_length } + }, label: { + Label("Publish…", systemImage: "paperplane") + }) + .disabled(model.selectedPost!.body.isEmpty) + .help("Publish the post to the web.\(model.account.isLoggedIn ? "" : " You must be logged in to do this.")") // swiftlint:disable:this line_length + } else { + HStack(spacing: 4) { + Button( + action: { + self.isPresentingSharingServicePicker = true + }, + label: { Image(systemName: "square.and.arrow.up") } + ) + .disabled(activePost.status == PostStatus.local.rawValue) + .help("Copy the post's URL to your Mac's pasteboard.") + .popover(isPresented: $isPresentingSharingServicePicker) { + PostEditorSharingPicker( + isPresented: $isPresentingSharingServicePicker, + sharingItems: createPostUrl() + ) + .frame(width: .zero, height: .zero) + } + Button(action: { publishPost(activePost) }, label: { Image(systemName: "paperplane") }) + .disabled(activePost.body.isEmpty || activePost.status == PostStatus.published.rawValue) + .help("Publish the post to the web.\(model.account.isLoggedIn ? "" : " You must be logged in to do this.")") // swiftlint:disable:this line_length } } - .onAppear(perform: { - self.selectedCollection = collections.first { $0.alias == activePost.collectionAlias } - }) - .onChange(of: selectedCollection, perform: { [selectedCollection] newCollection in - if activePost.collectionAlias == newCollection?.alias { - return - } else { - withAnimation { - activePost.collectionAlias = newCollection?.alias - model.move(post: activePost, from: selectedCollection, to: newCollection) - } - } - }) - } else { - EmptyView() } + .onAppear(perform: { + self.selectedCollection = collections.first { $0.alias == activePost.collectionAlias } + }) + .onChange(of: selectedCollection, perform: { [selectedCollection] newCollection in + if activePost.collectionAlias == newCollection?.alias { + return + } else { + withAnimation { + activePost.collectionAlias = newCollection?.alias + model.move(post: activePost, from: selectedCollection, to: newCollection) + } + } + }) } private func createPostUrl() -> [Any] { From f589d427943a5b88314f9a2a68b39f736d0baaa5 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 27 Jan 2021 10:37:18 -0500 Subject: [PATCH 2/3] Check that we're working with model's selectedPost property --- macOS/Navigation/ActivePostToolbarView.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/macOS/Navigation/ActivePostToolbarView.swift b/macOS/Navigation/ActivePostToolbarView.swift index 7b6d0c9..f95d123 100644 --- a/macOS/Navigation/ActivePostToolbarView.swift +++ b/macOS/Navigation/ActivePostToolbarView.swift @@ -64,7 +64,7 @@ struct ActivePostToolbarView: View { }, label: { Label("Publish…", systemImage: "paperplane") }) - .disabled(model.selectedPost!.body.isEmpty) + .disabled(model.selectedPost?.body.isEmpty ?? true) .help("Publish the post to the web.\(model.account.isLoggedIn ? "" : " You must be logged in to do this.")") // swiftlint:disable:this line_length } else { HStack(spacing: 4) { @@ -114,6 +114,9 @@ struct ActivePostToolbarView: View { } private func publishPost(_ post: WFAPost) { + if post != model.selectedPost { + return + } DispatchQueue.main.async { LocalStorageManager().saveContext() model.publish(post: post) From 5cdce15808e42e0e3adb87ff87c1b1e6c04feea5 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 27 Jan 2021 16:12:40 -0500 Subject: [PATCH 3/3] Differentiate between updating old post / publishing new post in handler --- Shared/Models/WriteFreelyModel.swift | 81 +++++++++++++++++----------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index 8cb36dd..286f39b 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -37,6 +37,7 @@ class WriteFreelyModel: ObservableObject { private let defaults = UserDefaults.standard private let monitor = NWPathMonitor() private let queue = DispatchQueue(label: "NetworkMonitor") + private var postToUpdate: WFAPost? init() { DispatchQueue.main.async { @@ -145,6 +146,8 @@ extension WriteFreelyModel { } func publish(post: WFAPost) { + postToUpdate = nil + if !hasNetworkConnection { DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true } return @@ -173,10 +176,8 @@ extension WriteFreelyModel { if let existingPostId = post.postId { // This is an existing post. + postToUpdate = post wfPost.postId = post.postId - wfPost.slug = post.slug - wfPost.updatedDate = post.updatedDate - wfPost.collectionAlias = post.collectionAlias loggedInClient.updatePost( postId: existingPostId, @@ -407,37 +408,55 @@ private extension WriteFreelyModel { // See: https://github.com/writeas/writefreely-swift/issues/20 do { let fetchedPost = try result.get() - 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 + // If this is an updated post, check it against postToUpdate. + if let updatingPost = self.postToUpdate { + updatingPost.appearance = fetchedPost.appearance + updatingPost.body = fetchedPost.body + updatingPost.createdDate = fetchedPost.createdDate + updatingPost.language = fetchedPost.language + updatingPost.postId = fetchedPost.postId + updatingPost.rtl = fetchedPost.rtl ?? false + updatingPost.slug = fetchedPost.slug + updatingPost.status = PostStatus.published.rawValue + updatingPost.title = fetchedPost.title ?? "" + updatingPost.updatedDate = fetchedPost.updatedDate DispatchQueue.main.async { LocalStorageManager().saveContext() } - } catch { - print("Error: Failed to fetch cached posts") + } else { + // Otherwise if it's a newly-published post, find it in the local store. + 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)