Set collection for postpull/42/head
@@ -0,0 +1,17 @@ | |||
import CoreData | |||
extension NSManagedObjectContext { | |||
/// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given | |||
/// managed object context up to date. | |||
/// | |||
/// Credit: https://www.avanderlee.com/swift/nsbatchdeleterequest-core-data/ | |||
/// | |||
/// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute. | |||
/// - Throws: An error if anything went wrong executing the batch deletion. | |||
public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws { | |||
batchDeleteRequest.resultType = .resultTypeObjectIDs | |||
let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult | |||
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []] | |||
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self]) | |||
} | |||
} |
@@ -50,9 +50,7 @@ class LocalStorageManager { | |||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) | |||
do { | |||
try LocalStorageManager.persistentContainer.persistentStoreCoordinator.execute( | |||
deleteRequest, with: LocalStorageManager.persistentContainer.viewContext | |||
) | |||
try LocalStorageManager.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest) | |||
} catch { | |||
print("Error: Failed to purge cached collections.") | |||
} | |||
@@ -11,6 +11,10 @@ class WriteFreelyModel: ObservableObject { | |||
@Published var isLoggingIn: Bool = false | |||
@Published var selectedPost: WFAPost? | |||
#if os(iOS) | |||
@Published var isPresentingSettingsView: Bool = false | |||
#endif | |||
private var client: WFClient? | |||
private let defaults = UserDefaults.standard | |||
@@ -112,7 +116,12 @@ extension WriteFreelyModel { | |||
DispatchQueue.main.async { | |||
self.selectedPost = post | |||
} | |||
loggedInClient.getPost(byId: postId, completion: updateFromServerHandler) | |||
if let postCollectionAlias = post.collectionAlias, | |||
let postSlug = post.slug { | |||
loggedInClient.getPost(bySlug: postSlug, from: postCollectionAlias, completion: updateFromServerHandler) | |||
} else { | |||
loggedInClient.getPost(byId: postId, completion: updateFromServerHandler) | |||
} | |||
} | |||
} | |||
@@ -169,8 +178,8 @@ private extension WriteFreelyModel { | |||
try purgeTokenFromKeychain(username: account.user?.username, server: account.server) | |||
client = nil | |||
DispatchQueue.main.async { | |||
LocalStorageManager().purgeUserCollections() | |||
self.account.logout() | |||
LocalStorageManager().purgeUserCollections() | |||
self.posts.purgeAllPosts() | |||
} | |||
} catch { | |||
@@ -280,12 +289,15 @@ private extension WriteFreelyModel { | |||
} | |||
func updateFromServerHandler(result: Result<WFPost, Error>) { | |||
// ⚠️ NOTE: | |||
// The API does not return a collection alias, so we take care not to overwrite the | |||
// cached post's collection alias with the 'nil' value from the fetched post. | |||
// See: https://github.com/writeas/writefreely-swift/issues/20 | |||
do { | |||
let fetchedPost = try result.get() | |||
guard let cachedPost = self.selectedPost else { return } | |||
cachedPost.appearance = fetchedPost.appearance | |||
cachedPost.body = fetchedPost.body | |||
cachedPost.collectionAlias = fetchedPost.collectionAlias | |||
cachedPost.createdDate = fetchedPost.createdDate | |||
cachedPost.language = fetchedPost.language | |||
cachedPost.postId = fetchedPost.postId | |||
@@ -13,6 +13,18 @@ struct ContentView: View { | |||
.foregroundColor(.secondary) | |||
} | |||
.environmentObject(model) | |||
#if os(iOS) | |||
EmptyView() | |||
.sheet( | |||
isPresented: $model.isPresentingSettingsView, | |||
onDismiss: { model.isPresentingSettingsView = false }, | |||
content: { | |||
SettingsView() | |||
.environmentObject(model) | |||
} | |||
) | |||
#endif | |||
} | |||
} | |||
@@ -30,8 +30,7 @@ struct PostEditorView: View { | |||
} | |||
ToolbarItem(placement: .primaryAction) { | |||
Button(action: { | |||
model.publish(post: post) | |||
post.status = PostStatus.published.rawValue | |||
publishPost() | |||
}, label: { | |||
Image(systemName: "paperplane") | |||
}) | |||
@@ -50,6 +49,14 @@ struct PostEditorView: View { | |||
} | |||
}) | |||
} | |||
private func publishPost() { | |||
DispatchQueue.main.async { | |||
LocalStorageManager().saveContext() | |||
model.posts.loadCachedPosts() | |||
model.publish(post: post) | |||
} | |||
} | |||
} | |||
struct PostEditorView_Previews: PreviewProvider { | |||
@@ -28,9 +28,7 @@ class PostListModel: ObservableObject { | |||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) | |||
do { | |||
try LocalStorageManager.persistentContainer.persistentStoreCoordinator.execute( | |||
deleteRequest, with: LocalStorageManager.persistentContainer.viewContext | |||
) | |||
try LocalStorageManager.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest) | |||
} catch { | |||
print("Error: Failed to purge cached posts.") | |||
} | |||
@@ -7,10 +7,6 @@ struct PostListView: View { | |||
@State var selectedCollection: WFACollection? | |||
@State var showAllPosts: Bool = false | |||
#if os(iOS) | |||
@State private var isPresentingSettings = false | |||
#endif | |||
var body: some View { | |||
#if os(iOS) | |||
GeometryReader { geometry in | |||
@@ -31,18 +27,10 @@ struct PostListView: View { | |||
ToolbarItem(placement: .bottomBar) { | |||
HStack { | |||
Button(action: { | |||
isPresentingSettings = true | |||
model.isPresentingSettingsView = true | |||
}, label: { | |||
Image(systemName: "gear") | |||
}).sheet( | |||
isPresented: $isPresentingSettings, | |||
onDismiss: { | |||
isPresentingSettings = false | |||
}, | |||
content: { | |||
SettingsView(isPresented: $isPresentingSettings) | |||
} | |||
) | |||
}) | |||
.padding(.leading) | |||
Spacer() | |||
Text(pluralizedPostCount(for: showPosts(for: selectedCollection))) | |||
@@ -117,6 +105,9 @@ struct PostListView: View { | |||
managedPost.title = "" | |||
managedPost.body = "" | |||
managedPost.status = PostStatus.local.rawValue | |||
if let selectedCollectionAlias = selectedCollection?.alias { | |||
managedPost.collectionAlias = selectedCollectionAlias | |||
} | |||
DispatchQueue.main.async { | |||
LocalStorageManager().saveContext() | |||
} | |||
@@ -54,6 +54,8 @@ | |||
17C42E632507D8E600072984 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; | |||
17C42E652509237800072984 /* PostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E642509237800072984 /* PostListFilteredView.swift */; }; | |||
17C42E662509237800072984 /* PostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E642509237800072984 /* PostListFilteredView.swift */; }; | |||
17C42E70250AA12300072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; | |||
17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; | |||
17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; | |||
17D435E924E3128F0036B539 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; | |||
17DF329D24C87D3500BCE2E3 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */; }; | |||
@@ -114,6 +116,7 @@ | |||
17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFAPost+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; | |||
17C42E612507D8E600072984 /* PostStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStatus.swift; sourceTree = "<group>"; }; | |||
17C42E642509237800072984 /* PostListFilteredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListFilteredView.swift; sourceTree = "<group>"; }; | |||
17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+ExecuteAndMergeChanges.swift"; sourceTree = "<group>"; }; | |||
17D435E724E3128F0036B539 /* PreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesModel.swift; sourceTree = "<group>"; }; | |||
17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteFreely_MultiPlatformApp.swift; sourceTree = "<group>"; }; | |||
17DF328224C87D3300BCE2E3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; | |||
@@ -193,6 +196,7 @@ | |||
isa = PBXGroup; | |||
children = ( | |||
1756AE8024CB844500FD7257 /* View+Keyboard.swift */, | |||
17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */, | |||
); | |||
path = Extensions; | |||
sourceTree = "<group>"; | |||
@@ -591,6 +595,7 @@ | |||
17120DA324E19A42002B9F6C /* PreferencesView.swift in Sources */, | |||
1756AE6E24CB255B00FD7257 /* PostListModel.swift in Sources */, | |||
174D313224EC2831006CA9EE /* WriteFreelyModel.swift in Sources */, | |||
17C42E70250AA12300072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, | |||
17120DA124E19839002B9F6C /* AccountView.swift in Sources */, | |||
1756AE7424CB26FA00FD7257 /* PostCellView.swift in Sources */, | |||
); | |||
@@ -610,6 +615,7 @@ | |||
17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, | |||
17C42E662509237800072984 /* PostListFilteredView.swift in Sources */, | |||
17120DAD24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, | |||
17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, | |||
1756AE7B24CB65DF00FD7257 /* PostListView.swift in Sources */, | |||
1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */, | |||
1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */, | |||
@@ -1,7 +1,7 @@ | |||
import SwiftUI | |||
struct SettingsHeaderView: View { | |||
@Binding var isPresented: Bool | |||
@Environment(\.presentationMode) var presentationMode | |||
var body: some View { | |||
HStack { | |||
@@ -10,7 +10,7 @@ struct SettingsHeaderView: View { | |||
.fontWeight(.bold) | |||
Spacer() | |||
Button(action: { | |||
isPresented = false | |||
presentationMode.wrappedValue.dismiss() | |||
}, label: { | |||
Image(systemName: "xmark.circle") | |||
}) | |||
@@ -21,6 +21,6 @@ struct SettingsHeaderView: View { | |||
struct SettingsHeaderView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
SettingsHeaderView(isPresented: .constant(true)) | |||
SettingsHeaderView() | |||
} | |||
} |
@@ -3,11 +3,9 @@ import SwiftUI | |||
struct SettingsView: View { | |||
@EnvironmentObject var model: WriteFreelyModel | |||
@Binding var isPresented: Bool | |||
var body: some View { | |||
VStack { | |||
SettingsHeaderView(isPresented: $isPresented) | |||
SettingsHeaderView() | |||
Form { | |||
Section(header: Text("Login Details")) { | |||
AccountView() | |||
@@ -23,7 +21,7 @@ struct SettingsView: View { | |||
struct SettingsView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
SettingsView(isPresented: .constant(true)) | |||
SettingsView() | |||
.environmentObject(WriteFreelyModel()) | |||
} | |||
} |