From d51fa4672adb97f9a259ab86886c08c3ca12fea3 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 6 Jan 2021 13:37:56 -0500 Subject: [PATCH 01/16] Remove unnecessary SidebarView SidebarView literally just calls CollectionListView and provides no other functionality or state management; ContentView can therefore just call CollectionListView directly. --- Shared/Navigation/ContentView.swift | 6 +++--- Shared/Navigation/SidebarView.swift | 18 ------------------ .../project.pbxproj | 6 ------ .../xcschemes/xcschememanagement.plist | 4 ++-- 4 files changed, 5 insertions(+), 29 deletions(-) delete mode 100644 Shared/Navigation/SidebarView.swift diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index e8cd97c..1b599c0 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -6,7 +6,7 @@ struct ContentView: View { var body: some View { NavigationView { #if os(macOS) - SidebarView() + CollectionListView() .toolbar { Button( action: { @@ -49,12 +49,12 @@ struct ContentView: View { .help("Create a new local draft.") } #else - SidebarView() + CollectionListView() #endif #if os(macOS) ZStack { - PostListView(selectedCollection: nil, showAllPosts: model.account.isLoggedIn) + PostListView(selectedCollection: nil, showAllPosts: false) //model.account.isLoggedIn) if model.isProcessingRequest { ZStack { Color(NSColor.controlBackgroundColor).opacity(0.75) diff --git a/Shared/Navigation/SidebarView.swift b/Shared/Navigation/SidebarView.swift deleted file mode 100644 index b4c9719..0000000 --- a/Shared/Navigation/SidebarView.swift +++ /dev/null @@ -1,18 +0,0 @@ -import SwiftUI - -struct SidebarView: View { - var body: some View { - CollectionListView() - } -} - -struct SidebarView_Previews: PreviewProvider { - static var previews: some View { - let context = LocalStorageManager.persistentContainer.viewContext - let model = WriteFreelyModel() - - return SidebarView() - .environment(\.managedObjectContext, context) - .environmentObject(model) - } -} diff --git a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj index d8287f1..40e0dde 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj +++ b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj @@ -48,8 +48,6 @@ 1756DC0224FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; 1756DC0324FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; 1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; - 1765F62A24E18EA200C9EBF0 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1765F62924E18EA200C9EBF0 /* SidebarView.swift */; }; - 1765F62B24E18EA200C9EBF0 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1765F62924E18EA200C9EBF0 /* SidebarView.swift */; }; 17681E412519410E00D394AE /* UINavigationController+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17681E402519410E00D394AE /* UINavigationController+Appearance.swift */; }; 1780F6EF25895EDB00FE45FF /* PostCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1780F6EE25895EDB00FE45FF /* PostCommands.swift */; }; 17A4FEDA25924AF70037E96B /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 17A4FED925924AF70037E96B /* Sparkle */; }; @@ -146,7 +144,6 @@ 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorageManager.swift; sourceTree = ""; }; 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; - 1765F62924E18EA200C9EBF0 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; 17681E402519410E00D394AE /* UINavigationController+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Appearance.swift"; sourceTree = ""; }; 1780F6EE25895EDB00FE45FF /* PostCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCommands.swift; sourceTree = ""; }; 17A4FEDF25924E810037E96B /* MacSoftwareUpdater.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = MacSoftwareUpdater.md; sourceTree = ""; }; @@ -457,7 +454,6 @@ isa = PBXGroup; children = ( 17DF328224C87D3300BCE2E3 /* ContentView.swift */, - 1765F62924E18EA200C9EBF0 /* SidebarView.swift */, ); path = Navigation; sourceTree = ""; @@ -745,7 +741,6 @@ 1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */, 17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, 17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */, - 1765F62A24E18EA200C9EBF0 /* SidebarView.swift in Sources */, 1756AE7A24CB65DF00FD7257 /* PostListView.swift in Sources */, 17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */, 1756DC0124FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */, @@ -769,7 +764,6 @@ buildActionMask = 2147483647; files = ( 17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */, - 1765F62B24E18EA200C9EBF0 /* SidebarView.swift in Sources */, 1756DBBB24FED45500207AB8 /* LocalStorageManager.swift in Sources */, 17A4FEED25927E730037E96B /* AppDelegate.swift in Sources */, 174D313324EC2831006CA9EE /* WriteFreelyModel.swift in Sources */, diff --git a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist index 2723ebe..6cd8075 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_ orderHint - 0 + 1 WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_ orderHint - 1 + 0 From 1acd25ad421be685aa289737132430760fc7706a Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Wed, 13 Jan 2021 17:03:42 -0500 Subject: [PATCH 02/16] Handle selectedCollection in CollectionView --- Shared/Navigation/ContentView.swift | 6 +- .../PostCollection/CollectionListView.swift | 41 +++++++++--- Shared/PostList/PostListFilteredView.swift | 62 +++++++------------ Shared/PostList/PostListView.swift | 27 ++++---- 4 files changed, 73 insertions(+), 63 deletions(-) diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 1b599c0..2f79f4c 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -42,6 +42,8 @@ struct ContentView: View { } withAnimation { DispatchQueue.main.async { + self.model.showAllPosts = false + self.model.selectedCollection = nil self.model.selectedPost = managedPost } } @@ -54,7 +56,7 @@ struct ContentView: View { #if os(macOS) ZStack { - PostListView(selectedCollection: nil, showAllPosts: false) //model.account.isLoggedIn) + PostListView() if model.isProcessingRequest { ZStack { Color(NSColor.controlBackgroundColor).opacity(0.75) @@ -63,7 +65,7 @@ struct ContentView: View { } } #else - PostListView(selectedCollection: nil, showAllPosts: model.account.isLoggedIn) + PostListView() #endif Text("Select a post, or create a new local draft.") diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index 23a4aa5..a084f82 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -9,21 +9,48 @@ struct CollectionListView: View { ) var collections: FetchedResults var body: some View { - List { + List(selection: $model.selectedCollection) { if model.account.isLoggedIn { - NavigationLink(destination: PostListView(selectedCollection: nil, showAllPosts: true)) { + NavigationLink( + destination: PostListView(), + isActive: Binding( + get: { () -> Bool in + model.selectedCollection == nil && model.showAllPosts + }, set: { newValue in + if newValue { + self.model.selectedCollection = nil + self.model.showAllPosts = true + } else { + // No-op + } + } + )) { Text("All Posts") } - NavigationLink(destination: PostListView(selectedCollection: nil, showAllPosts: false)) { + NavigationLink( + destination: PostListView(), + isActive: Binding( + get: { () -> Bool in + model.selectedCollection == nil && !model.showAllPosts + }, set: { newValue in + if newValue { + self.model.selectedCollection = nil + self.model.showAllPosts = false + } else { + // No-op + } + } + )) { Text(model.account.server == "https://write.as" ? "Anonymous" : "Drafts") } Section(header: Text("Your Blogs")) { ForEach(collections, id: \.alias) { collection in NavigationLink( - destination: PostListView(selectedCollection: collection, showAllPosts: false) - ) { - Text(collection.title) - } + collection.title, + destination: PostListView(), + tag: collection, + selection: $model.selectedCollection + ) } } } else { diff --git a/Shared/PostList/PostListFilteredView.swift b/Shared/PostList/PostListFilteredView.swift index 3460033..29601d6 100644 --- a/Shared/PostList/PostListFilteredView.swift +++ b/Shared/PostList/PostListFilteredView.swift @@ -6,21 +6,7 @@ struct PostListFilteredView: View { @FetchRequest(entity: WFACollection.entity(), sortDescriptors: []) var collections: FetchedResults var fetchRequest: FetchRequest - var showAllPosts: Bool { - didSet { - model.showAllPosts = showAllPosts - } - } - - var selectedCollection: WFACollection? { - didSet { - model.selectedCollection = selectedCollection - } - } - init(collection: WFACollection?, showAllPosts: Bool, postCount: Binding) { - self.showAllPosts = showAllPosts - self.selectedCollection = collection if showAllPosts { fetchRequest = FetchRequest( entity: WFAPost.entity(), @@ -51,20 +37,20 @@ struct PostListFilteredView: View { NavigationLink( destination: PostEditorView(post: post), tag: post, - selection: $model.selectedPost - ) { - if showAllPosts { - if let collection = collections.filter { $0.alias == post.collectionAlias }.first { - PostCellView(post: post, collectionName: collection.title) + 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 { - let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts" - PostCellView(post: post, collectionName: collectionName) + PostCellView(post: post) } - } else { - PostCellView(post: post) - } - } - .deleteDisabled(post.status != PostStatus.local.rawValue) + }) + .deleteDisabled(post.status != PostStatus.local.rawValue) } .onDelete(perform: { indexSet in for index in indexSet { @@ -85,20 +71,20 @@ struct PostListFilteredView: View { NavigationLink( destination: PostEditorView(post: post), tag: post, - selection: $model.selectedPost - ) { - if showAllPosts { - if let collection = collections.filter { $0.alias == post.collectionAlias }.first { - PostCellView(post: post, collectionName: collection.title) + 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 { - let collectionName = model.account.server == "https://write.as" ? "Anonymous" : "Drafts" - PostCellView(post: post, collectionName: collectionName) + PostCellView(post: post) } - } else { - PostCellView(post: post) - } - } - .deleteDisabled(post.status != PostStatus.local.rawValue) + }) + .deleteDisabled(post.status != PostStatus.local.rawValue) } .onDelete(perform: { indexSet in for index in indexSet { diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 1d16962..271e4e8 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -5,8 +5,6 @@ struct PostListView: View { @EnvironmentObject var model: WriteFreelyModel @Environment(\.managedObjectContext) var managedObjectContext - @State var selectedCollection: WFACollection? - @State var showAllPosts: Bool = false @State private var postCount: Int = 0 #if os(iOS) @@ -21,9 +19,13 @@ struct PostListView: View { var body: some View { #if os(iOS) ZStack(alignment: .bottom) { - PostListFilteredView(collection: selectedCollection, showAllPosts: showAllPosts, postCount: $postCount) + PostListFilteredView( + collection: model.selectedCollection, + showAllPosts: model.showAllPosts, + postCount: $postCount + ) .navigationTitle( - showAllPosts ? "All Posts" : selectedCollection?.title ?? ( + model.showAllPosts ? "All Posts" : model.selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) @@ -54,8 +56,8 @@ struct PostListView: View { managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft } withAnimation { - self.selectedCollection = nil - self.showAllPosts = false + self.model.showAllPosts = false + self.model.selectedCollection = nil self.model.selectedPost = managedPost } }, label: { @@ -122,8 +124,8 @@ struct PostListView: View { .ignoresSafeArea() #else //if os(macOS) PostListFilteredView( - collection: selectedCollection, - showAllPosts: showAllPosts, + collection: model.selectedCollection, + showAllPosts: model.showAllPosts, postCount: $postCount ) .toolbar { @@ -145,15 +147,8 @@ struct PostListView: View { } } } - .onDisappear { - DispatchQueue.main.async { - self.model.selectedCollection = nil - self.model.showAllPosts = true - self.model.selectedPost = nil - } - } .navigationTitle( - showAllPosts ? "All Posts" : selectedCollection?.title ?? ( + model.showAllPosts ? "All Posts" : model.selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) From 3b6c7748e8a19be6a260f3043b7114033911ce1d Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Thu, 14 Jan 2021 10:28:14 -0500 Subject: [PATCH 03/16] Ensure showAllPosts is false when navigating to a given blog --- .../PostCollection/CollectionListView.swift | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index a084f82..9dbcb22 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -18,15 +18,16 @@ struct CollectionListView: View { model.selectedCollection == nil && model.showAllPosts }, set: { newValue in if newValue { - self.model.selectedCollection = nil self.model.showAllPosts = true + self.model.selectedCollection = nil } else { // No-op } } - )) { + ), + label: { Text("All Posts") - } + }) NavigationLink( destination: PostListView(), isActive: Binding( @@ -34,27 +35,38 @@ struct CollectionListView: View { model.selectedCollection == nil && !model.showAllPosts }, set: { newValue in if newValue { - self.model.selectedCollection = nil 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: \.alias) { collection in NavigationLink( - collection.title, destination: PostListView(), - tag: collection, - selection: $model.selectedCollection + 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(selectedCollection: nil, showAllPosts: false)) { + NavigationLink(destination: PostListView()) { Text("Drafts") } } From 9b06eb34a8ea71cb3da6dd55009080ca1152d252 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Thu, 14 Jan 2021 13:29:51 -0500 Subject: [PATCH 04/16] Fix race condition in creating new post --- Shared/Navigation/ContentView.swift | 12 +++++++++--- Shared/WriteFreely_MultiPlatformApp.swift | 10 +++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 2f79f4c..17f45ad 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -20,8 +20,14 @@ struct ContentView: View { Spacer() Button(action: { 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 = "" @@ -40,10 +46,10 @@ struct ContentView: View { managedPost.language = languageCode managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft } + withAnimation { - DispatchQueue.main.async { - self.model.showAllPosts = false - self.model.selectedCollection = nil + DispatchQueue.main.asyncAfter(deadline: .now()) { + // Load the new post in the editor self.model.selectedPost = managedPost } } diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index 61a634a..6079e02 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -114,8 +114,14 @@ struct WriteFreely_MultiPlatformApp: App { 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 = "" @@ -135,7 +141,9 @@ struct WriteFreely_MultiPlatformApp: App { managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft } withAnimation { - self.model.selectedPost = managedPost + DispatchQueue.main.asyncAfter(deadline: .now()) { + self.model.selectedPost = managedPost + } } } } From deadfb8509e9bb9c797d4148ce00a5a6e4b3cc89 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Thu, 14 Jan 2021 17:04:52 -0500 Subject: [PATCH 05/16] Save/restore navigation state to last selected post and collection --- .../PostCollection/CollectionListView.swift | 5 +++ Shared/PostList/PostListFilteredView.swift | 11 +++++++ Shared/WriteFreely_MultiPlatformApp.swift | 33 ++++++++++++++++--- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index 9dbcb22..933ba96 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -2,6 +2,8 @@ import SwiftUI struct CollectionListView: View { @EnvironmentObject var model: WriteFreelyModel + @AppStorage("showAllPostsFlag") var showAllPostsFlag: Bool = false + @AppStorage("selectedCollectionURL") var selectedCollectionURL: URL? @FetchRequest( entity: WFACollection.entity(), @@ -75,6 +77,9 @@ struct CollectionListView: View { model.account.isLoggedIn ? "\(URL(string: model.account.server)?.host ?? "WriteFreely")" : "WriteFreely" ) .listStyle(SidebarListStyle()) + .onChange(of: model.selectedCollection) { collection in + self.selectedCollectionURL = collection?.objectID.uriRepresentation() + } } } diff --git a/Shared/PostList/PostListFilteredView.swift b/Shared/PostList/PostListFilteredView.swift index 29601d6..1b7fbf4 100644 --- a/Shared/PostList/PostListFilteredView.swift +++ b/Shared/PostList/PostListFilteredView.swift @@ -2,6 +2,7 @@ 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 @@ -65,6 +66,9 @@ struct PostListFilteredView: View { .onChange(of: fetchRequest.wrappedValue.count, perform: { value in self.postCount = value }) + .onChange(of: model.selectedPost) { post in + saveSelectedPostURL(post) + } #else List { ForEach(fetchRequest.wrappedValue, id: \.self) { post in @@ -119,9 +123,16 @@ struct PostListFilteredView: View { model.isPresentingDeleteAlert = true } }) + .onChange(of: model.selectedPost) { post in + saveSelectedPostURL(post) + } #endif } + private func saveSelectedPostURL(_ post: WFAPost?) { + self.selectedPostURL = post?.objectID.uriRepresentation() + } + func delete(_ post: WFAPost) { DispatchQueue.main.async { if post == model.selectedPost { diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index 6079e02..47d610d 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -22,6 +22,9 @@ struct CheckForDebugModifier { 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 @@ -33,10 +36,12 @@ struct WriteFreely_MultiPlatformApp: App { WindowGroup { ContentView() .onAppear(perform: { - if let lastDraft = model.editor.fetchLastDraftFromUserDefaults() { - self.model.selectedPost = lastDraft - } else { - createNewLocalPost() + DispatchQueue.main.async { + self.model.showAllPosts = showAllPostsFlag + self.model.selectedCollection = fetchSelectedCollectionFromAppStorage() + } + DispatchQueue.main.asyncAfter(deadline: .now()) { + self.model.selectedPost = fetchSelectedPostFromAppStorage() } }) .environmentObject(model) @@ -146,4 +151,24 @@ struct WriteFreely_MultiPlatformApp: App { } } } + + 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 + } } From 083d69d1438549811353959465dc225396a0d31a Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Tue, 19 Jan 2021 09:48:00 -0500 Subject: [PATCH 06/16] Track changes to model.showAllPosts in @AppStorage --- Shared/PostCollection/CollectionListView.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index 933ba96..abe1f8b 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -80,6 +80,9 @@ struct CollectionListView: View { .onChange(of: model.selectedCollection) { collection in self.selectedCollectionURL = collection?.objectID.uriRepresentation() } + .onChange(of: model.showAllPosts) { value in + self.showAllPostsFlag = model.showAllPosts + } } } From 6beee8cf3216347836fc35b8bc01aafa9a667880 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Tue, 19 Jan 2021 10:33:25 -0500 Subject: [PATCH 07/16] Prevent AppStorage updates if there's no change --- Shared/PostCollection/CollectionListView.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index abe1f8b..aebe15f 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -78,12 +78,26 @@ struct CollectionListView: View { ) .listStyle(SidebarListStyle()) .onChange(of: model.selectedCollection) { collection in - self.selectedCollectionURL = collection?.objectID.uriRepresentation() + if collection != fetchSelectedCollectionFromAppStorage() { + self.selectedCollectionURL = collection?.objectID.uriRepresentation() + } } .onChange(of: model.showAllPosts) { value in - self.showAllPostsFlag = model.showAllPosts + if value != showAllPostsFlag { + self.showAllPostsFlag = model.showAllPosts + } } } + + 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 + } } struct CollectionListView_LoggedOutPreviews: PreviewProvider { From 0b19c8461f89983956ced20f7527594d5888e0f5 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Tue, 19 Jan 2021 11:29:41 -0500 Subject: [PATCH 08/16] Set selection of post --- Shared/PostList/PostListFilteredView.swift | 22 ++++++++++++++++++---- Shared/WriteFreely_MultiPlatformApp.swift | 6 ++---- 2 files changed, 20 insertions(+), 8 deletions(-) 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 @@ -33,7 +33,7 @@ struct PostListFilteredView: View { var body: some View { #if os(iOS) - List { + List(selection: $model.selectedPost) { ForEach(fetchRequest.wrappedValue, id: \.self) { post in NavigationLink( destination: PostEditorView(post: post), @@ -67,10 +67,12 @@ struct PostListFilteredView: View { 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), @@ -124,7 +126,9 @@ struct PostListFilteredView: View { } }) .onChange(of: model.selectedPost) { post in - saveSelectedPostURL(post) + if post != fetchSelectedPostFromAppStorage() { + saveSelectedPostURL(post) + } } #endif } @@ -133,6 +137,16 @@ struct PostListFilteredView: View { 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 { 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 @@ -36,10 +36,8 @@ struct WriteFreely_MultiPlatformApp: App { 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() } From dcb18c86db1cb375e8a94706fa6e462e343ac575 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Thu, 21 Jan 2021 15:39:28 -0500 Subject: [PATCH 09/16] Set app state in App entrypoint for macOS, in relevant Views for iOS --- Shared/PostCollection/CollectionListView.swift | 10 +++++++++- Shared/PostList/PostListFilteredView.swift | 3 +++ Shared/WriteFreely_MultiPlatformApp.swift | 6 +++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index aebe15f..8096584 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -48,7 +48,7 @@ struct CollectionListView: View { Text(model.account.server == "https://write.as" ? "Anonymous" : "Drafts") }) Section(header: Text("Your Blogs")) { - ForEach(collections, id: \.alias) { collection in + ForEach(collections, id: \.self) { collection in NavigationLink( destination: PostListView(), isActive: Binding( @@ -77,6 +77,14 @@ struct CollectionListView: View { model.account.isLoggedIn ? "\(URL(string: model.account.server)?.host ?? "WriteFreely")" : "WriteFreely" ) .listStyle(SidebarListStyle()) + .onAppear(perform: { + #if os(iOS) + DispatchQueue.main.async { + self.model.showAllPosts = showAllPostsFlag + self.model.selectedCollection = fetchSelectedCollectionFromAppStorage() + } + #endif + }) .onChange(of: model.selectedCollection) { collection in if collection != fetchSelectedCollectionFromAppStorage() { self.selectedCollectionURL = collection?.objectID.uriRepresentation() diff --git a/Shared/PostList/PostListFilteredView.swift b/Shared/PostList/PostListFilteredView.swift index 6848ce6..d6b35ac 100644 --- a/Shared/PostList/PostListFilteredView.swift +++ b/Shared/PostList/PostListFilteredView.swift @@ -62,6 +62,9 @@ struct PostListFilteredView: View { } .onAppear(perform: { self.postCount = fetchRequest.wrappedValue.count + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.model.selectedPost = fetchSelectedPostFromAppStorage() + } }) .onChange(of: fetchRequest.wrappedValue.count, perform: { value in self.postCount = value diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index 9171cf3..b7dc143 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -36,11 +36,11 @@ struct WriteFreely_MultiPlatformApp: App { WindowGroup { ContentView() .onAppear(perform: { + #if os(macOS) self.model.showAllPosts = showAllPostsFlag self.model.selectedCollection = fetchSelectedCollectionFromAppStorage() - DispatchQueue.main.asyncAfter(deadline: .now()) { - self.model.selectedPost = fetchSelectedPostFromAppStorage() - } + self.model.selectedPost = fetchSelectedPostFromAppStorage() + #endif }) .environmentObject(model) .environment(\.managedObjectContext, LocalStorageManager.persistentContainer.viewContext) From 20f12fa7cc4298f05ba8954e7f36c9f04618c18b Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Mon, 25 Jan 2021 11:30:52 -0500 Subject: [PATCH 10/16] Move state-fetching logic to PostEditorModel for Mac app --- Shared/PostEditor/PostEditorModel.swift | 44 +++++++++++++++++ Shared/WriteFreely_MultiPlatformApp.swift | 60 ++++++----------------- 2 files changed, 60 insertions(+), 44 deletions(-) diff --git a/Shared/PostEditor/PostEditorModel.swift b/Shared/PostEditor/PostEditorModel.swift index 7580271..316ed5b 100644 --- a/Shared/PostEditor/PostEditorModel.swift +++ b/Shared/PostEditor/PostEditorModel.swift @@ -8,6 +8,9 @@ enum PostAppearance: String { } struct PostEditorModel { + @AppStorage("showAllPostsFlag") var showAllPostsFlag: Bool = false + @AppStorage("selectedCollectionURL") var selectedCollectionURL: URL? + @AppStorage("selectedPostURL") var selectedPostURL: URL? @AppStorage("lastDraftURL") private var lastDraftURL: URL? func saveLastDraft(_ post: WFAPost) { @@ -29,4 +32,45 @@ struct PostEditorModel { return post } + + func generateNewLocalPost(withAppearance 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 + } + + 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/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index b7dc143..23022a9 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -22,9 +22,6 @@ struct CheckForDebugModifier { 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 @@ -37,9 +34,20 @@ struct WriteFreely_MultiPlatformApp: App { ContentView() .onAppear(perform: { #if os(macOS) - self.model.showAllPosts = showAllPostsFlag - self.model.selectedCollection = fetchSelectedCollectionFromAppStorage() - self.model.selectedPost = fetchSelectedPostFromAppStorage() + 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() + } #endif }) .environmentObject(model) @@ -125,48 +133,12 @@ struct WriteFreely_MultiPlatformApp: App { 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 - } + let managedPost = model.editor.generateNewLocalPost(withAppearance: model.preferences.font) withAnimation { + // Set it as the selectedPost 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 - } } From e1c5823cd6cdc9ec5c407c2768ac2cddbbfde67c Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Mon, 25 Jan 2021 11:53:16 -0500 Subject: [PATCH 11/16] Move new-post generation for Mac app to PostEditorModel --- Shared/Navigation/ContentView.swift | 20 +------------------- Shared/PostEditor/PostEditorModel.swift | 2 +- Shared/WriteFreely_MultiPlatformApp.swift | 2 +- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 17f45ad..09d389f 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -28,25 +28,7 @@ struct ContentView: View { 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 - } - + let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { DispatchQueue.main.asyncAfter(deadline: .now()) { // Load the new post in the editor diff --git a/Shared/PostEditor/PostEditorModel.swift b/Shared/PostEditor/PostEditorModel.swift index 316ed5b..c07c703 100644 --- a/Shared/PostEditor/PostEditorModel.swift +++ b/Shared/PostEditor/PostEditorModel.swift @@ -33,7 +33,7 @@ struct PostEditorModel { return post } - func generateNewLocalPost(withAppearance appearance: Int) -> WFAPost { + func generateNewLocalPost(withFont appearance: Int) -> WFAPost { let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) managedPost.createdDate = Date() managedPost.title = "" diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index 23022a9..25dfe78 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -133,7 +133,7 @@ struct WriteFreely_MultiPlatformApp: App { self.model.selectedCollection = nil } // Create the new-post managed object - let managedPost = model.editor.generateNewLocalPost(withAppearance: model.preferences.font) + let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { // Set it as the selectedPost DispatchQueue.main.asyncAfter(deadline: .now()) { From 4ad076ccabcc301483a6c7a028a060261964e7e5 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Mon, 25 Jan 2021 13:22:15 -0500 Subject: [PATCH 12/16] Move new-post generation for iOS app to PostEditorModel --- Shared/PostList/PostListView.swift | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 271e4e8..972ad38 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -36,25 +36,7 @@ struct PostListView: View { ZStack { Spacer() Button(action: { - let managedPost = WFAPost(context: self.managedObjectContext) - 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 - //swiftlint:disable:next line_length - managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft - } + let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { self.model.showAllPosts = false self.model.selectedCollection = nil From 34b14ba46c17977a45af0b2203c21adb3916ee95 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Mon, 25 Jan 2021 13:46:30 -0500 Subject: [PATCH 13/16] Move state-fetching logic to PostEditorModel for iOS app --- .../PostCollection/CollectionListView.swift | 33 ++++++++----------- Shared/PostList/PostListFilteredView.swift | 17 ++-------- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index 8096584..79d7047 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -2,8 +2,6 @@ import SwiftUI struct CollectionListView: View { @EnvironmentObject var model: WriteFreelyModel - @AppStorage("showAllPostsFlag") var showAllPostsFlag: Bool = false - @AppStorage("selectedCollectionURL") var selectedCollectionURL: URL? @FetchRequest( entity: WFACollection.entity(), @@ -79,33 +77,30 @@ struct CollectionListView: View { .listStyle(SidebarListStyle()) .onAppear(perform: { #if os(iOS) - DispatchQueue.main.async { - self.model.showAllPosts = showAllPostsFlag - self.model.selectedCollection = fetchSelectedCollectionFromAppStorage() + 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 != fetchSelectedCollectionFromAppStorage() { - self.selectedCollectionURL = collection?.objectID.uriRepresentation() + if collection != model.editor.fetchSelectedCollectionFromAppStorage() { + self.model.editor.selectedCollectionURL = collection?.objectID.uriRepresentation() } } .onChange(of: model.showAllPosts) { value in - if value != showAllPostsFlag { - self.showAllPostsFlag = model.showAllPosts + if value != model.editor.showAllPostsFlag { + self.model.editor.showAllPostsFlag = model.showAllPosts } } } - - 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 - } } struct CollectionListView_LoggedOutPreviews: PreviewProvider { diff --git a/Shared/PostList/PostListFilteredView.swift b/Shared/PostList/PostListFilteredView.swift index d6b35ac..dd11691 100644 --- a/Shared/PostList/PostListFilteredView.swift +++ b/Shared/PostList/PostListFilteredView.swift @@ -2,7 +2,6 @@ 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 @@ -63,14 +62,14 @@ struct PostListFilteredView: View { .onAppear(perform: { self.postCount = fetchRequest.wrappedValue.count DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.model.selectedPost = fetchSelectedPostFromAppStorage() + 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 != fetchSelectedPostFromAppStorage() { + if post != model.editor.fetchSelectedPostFromAppStorage() { saveSelectedPostURL(post) } } @@ -137,17 +136,7 @@ struct PostListFilteredView: View { } 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 + self.model.editor.selectedPostURL = post?.objectID.uriRepresentation() } func delete(_ post: WFAPost) { From 900ef269ba67076e96e01c943b2fe46ef1ba29a0 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Tue, 26 Jan 2021 09:23:47 -0500 Subject: [PATCH 14/16] Use same code path to set iOS and Mac app state on launch --- .../PostCollection/CollectionListView.swift | 15 --------- Shared/PostEditor/PostEditorModel.swift | 31 ++++++------------- Shared/PostList/PostListFilteredView.swift | 17 ---------- Shared/WriteFreely_MultiPlatformApp.swift | 8 +++-- 4 files changed, 14 insertions(+), 57 deletions(-) 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 @@ -75,21 +75,6 @@ struct CollectionListView: View { 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() 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 @@ -10,8 +10,7 @@ enum PostAppearance: String { 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 @@ -21,15 +20,9 @@ struct PostEditorModel { 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 } @@ -55,22 +48,16 @@ struct PostEditorModel { 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 @@ -61,18 +61,10 @@ struct PostListFilteredView: View { } .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 @@ -127,18 +119,9 @@ struct PostListFilteredView: View { 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 { 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 @@ -33,7 +33,6 @@ struct WriteFreely_MultiPlatformApp: App { WindowGroup { ContentView() .onAppear(perform: { - #if os(macOS) if model.editor.showAllPostsFlag { DispatchQueue.main.async { self.model.selectedCollection = nil @@ -46,9 +45,12 @@ struct WriteFreely_MultiPlatformApp: App { } } 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) From 9bc453219307931be1968674adb2e057cb316a48 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Tue, 26 Jan 2021 09:27:43 -0500 Subject: [PATCH 15/16] Clear last draft when holding Shift key on Mac app launch --- Shared/WriteFreely_MultiPlatformApp.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index e6aaa58..c05344a 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -9,11 +9,12 @@ struct CheckForDebugModifier { static func main() { #if os(macOS) if NSEvent.modifierFlags.contains(.shift) { - print("Debug launch detected") - // Run debug-mode launch code here + // Clear the launch-to-last-draft values to load a new draft. + UserDefaults.standard.setValue(false, forKey: "showAllPostsFlag") + UserDefaults.standard.setValue(nil, forKey: "selectedCollectionURL") + UserDefaults.standard.setValue(nil, forKey: "lastDraftURL") } else { - print("Normal launch detected") - // Don't do anything + // No-op } #endif WriteFreely_MultiPlatformApp.main() From 12785d733c32cf619b6a83d004f9bbaa00755e85 Mon Sep 17 00:00:00 2001 From: Angelo Stavrow Date: Tue, 26 Jan 2021 10:29:05 -0500 Subject: [PATCH 16/16] Use model.selectedPost for ActivePostToolbarView --- Shared/PostList/PostListView.swift | 22 ++- macOS/Navigation/ActivePostToolbarView.swift | 160 ++++++++++--------- 2 files changed, 91 insertions(+), 91 deletions(-) diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 972ad38..5f0bdc6 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -112,21 +112,19 @@ struct PostListView: View { ) .toolbar { ToolbarItemGroup(placement: .primaryAction) { - if let selectedPost = model.selectedPost { - ActivePostToolbarView(activePost: selectedPost) - .alert(isPresented: $model.isPresentingNetworkErrorAlert, content: { - Alert( - title: Text("Connection Error"), - message: Text(""" + ActivePostToolbarView() + .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 20334f4..bd18ff9 100644 --- a/macOS/Navigation/ActivePostToolbarView.swift +++ b/macOS/Navigation/ActivePostToolbarView.swift @@ -2,9 +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? @FetchRequest( @@ -13,102 +11,106 @@ struct ActivePostToolbarView: View { ) var collections: FetchedResults var body: some View { - 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) + 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?) } - } else { - openSettingsWindow() - } - }, label: { - Text("\(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") - }) - ForEach(collections) { collection in + }) + } + } + 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 = collection.alias + activePost.collectionAlias = nil publishPost(activePost) } } else { openSettingsWindow() } }, label: { - Text("\(collection.title)") + Text("\(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") }) - } - }, 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() + 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") } ) - .frame(width: .zero, height: .zero) + .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 } - 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] { - guard let postId = activePost.postId else { return [] } - guard let urlString = activePost.slug != nil ? - "\(model.account.server)/\((activePost.collectionAlias)!)/\((activePost.slug)!)" : + guard let postId = model.selectedPost?.postId else { return [] } + guard let urlString = model.selectedPost?.slug != nil ? + "\(model.account.server)/\((model.selectedPost?.collectionAlias)!)/\((model.selectedPost?.slug)!)" : "\(model.account.server)/\((postId))" else { return [] } guard let data = URL(string: urlString) else { return [] } return [data as NSURL]