diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 2103891..332c23f 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -1,138 +1,137 @@ import SwiftUI struct PostListView: View { @EnvironmentObject var model: WriteFreelyModel @Environment(\.managedObjectContext) var moc @State var selectedCollection: WFACollection? @State var showAllPosts: Bool = false var body: some View { #if os(iOS) GeometryReader { geometry in PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts) .navigationTitle( showAllPosts ? "All Posts" : selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) .toolbar { ToolbarItem(placement: .primaryAction) { Button(action: { createNewLocalDraft() }, label: { Image(systemName: "square.and.pencil") }) } ToolbarItem(placement: .bottomBar) { HStack { Button(action: { model.isPresentingSettingsView = true }, label: { Image(systemName: "gear") }) Spacer() Text(pluralizedPostCount(for: showPosts(for: selectedCollection))) .foregroundColor(.secondary) Spacer() Button(action: { reloadFromServer() }, label: { Image(systemName: "arrow.clockwise") }) .disabled(!model.account.isLoggedIn || !model.hasNetworkConnection) } .padding() .frame(width: geometry.size.width) } } } #else //if os(macOS) PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts) .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 || !model.hasNetworkConnection) } #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 } } } } private func reloadFromServer() { DispatchQueue.main.async { model.fetchUserCollections() model.fetchUserPosts() } } private func createNewLocalDraft() { let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) managedPost.createdDate = Date() managedPost.title = "" managedPost.body = "" managedPost.status = PostStatus.local.rawValue 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 } if let selectedCollectionAlias = selectedCollection?.alias { managedPost.collectionAlias = selectedCollectionAlias } DispatchQueue.main.async { - LocalStorageManager().saveContext() model.selectedPost = managedPost } } } struct PostListView_Previews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.persistentContainer.viewContext let model = WriteFreelyModel() return PostListView() .environment(\.managedObjectContext, context) .environmentObject(model) } } 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 @@ -1,19 +1,19 @@ SchemeUserState WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_ orderHint - 0 + 1 WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_ orderHint - 1 + 0 diff --git a/iOS/PostEditor/PostEditorView.swift b/iOS/PostEditor/PostEditorView.swift index 4cb30dd..2994cd2 100644 --- a/iOS/PostEditor/PostEditorView.swift +++ b/iOS/PostEditor/PostEditorView.swift @@ -1,171 +1,180 @@ import SwiftUI struct PostEditorView: View { @EnvironmentObject var model: WriteFreelyModel @ObservedObject var post: WFAPost var body: some View { VStack { switch post.appearance { case "sans": TextField("Title (optional)", text: $post.title) .font(.custom("OpenSans-Regular", size: 26, relativeTo: Font.TextStyle.largeTitle)) .onChange(of: post.title) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } ZStack(alignment: .topLeading) { if post.body.count == 0 { Text("Write...") .foregroundColor(Color(UIColor.placeholderText)) .padding(.horizontal, 4) .padding(.vertical, 8) .font(.custom("OpenSans-Regular", size: 17, relativeTo: Font.TextStyle.body)) } TextEditor(text: $post.body) .font(.custom("OpenSans-Regular", size: 17, relativeTo: Font.TextStyle.body)) .onChange(of: post.body) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } } case "wrap", "mono", "code": TextField("Title (optional)", text: $post.title) .font(.custom("Hack", size: 26, relativeTo: Font.TextStyle.largeTitle)) .onChange(of: post.title) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } ZStack(alignment: .topLeading) { if post.body.count == 0 { Text("Write...") .foregroundColor(Color(UIColor.placeholderText)) .padding(.horizontal, 4) .padding(.vertical, 8) .font(.custom("Hack", size: 17, relativeTo: Font.TextStyle.body)) } TextEditor(text: $post.body) .font(.custom("Hack", size: 17, relativeTo: Font.TextStyle.body)) .onChange(of: post.body) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } } default: TextField("Title (optional)", text: $post.title) .font(.custom("Lora", size: 26, relativeTo: Font.TextStyle.largeTitle)) .onChange(of: post.title) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } ZStack(alignment: .topLeading) { if post.body.count == 0 { Text("Write...") .foregroundColor(Color(UIColor.placeholderText)) .padding(.horizontal, 4) .padding(.vertical, 8) .font(.custom("Lora", size: 17, relativeTo: Font.TextStyle.body)) } TextEditor(text: $post.body) .font(.custom("Lora", size: 17, relativeTo: Font.TextStyle.body)) .onChange(of: post.body) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } } } } .navigationBarTitleDisplayMode(.inline) .padding() .toolbar { ToolbarItem(placement: .principal) { PostEditorStatusToolbarView(post: post) } ToolbarItem(placement: .primaryAction) { Button(action: { publishPost() }, label: { Image(systemName: "paperplane") }) .disabled( post.status == PostStatus.published.rawValue || !model.account.isLoggedIn || !model.hasNetworkConnection ) } } .onChange(of: post.hasNewerRemoteCopy, perform: { _ in if post.status == PostStatus.edited.rawValue && !post.hasNewerRemoteCopy { 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.status != PostStatus.published.rawValue { + if post.title.count == 0 + && post.body.count == 0 + && post.status == PostStatus.local.rawValue + && post.updatedDate == nil + && post.postId == nil { + withAnimation { + model.posts.remove(post) + model.posts.loadCachedPosts() + } + } else if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { LocalStorageManager().saveContext() } } }) } private func publishPost() { DispatchQueue.main.async { LocalStorageManager().saveContext() model.posts.loadCachedPosts() model.publish(post: post) } #if os(iOS) self.hideKeyboard() #endif } } struct PostEditorView_EmptyPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.persistentContainer.viewContext let testPost = WFAPost(context: context) testPost.createdDate = Date() testPost.appearance = "norm" let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } } struct PostEditorView_ExistingPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.persistentContainer.viewContext let testPost = WFAPost(context: context) testPost.title = "Test Post Title" testPost.body = "Here's some cool sample body text." testPost.createdDate = Date() testPost.appearance = "code" let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/macOS/PostEditor/PostEditorView.swift b/macOS/PostEditor/PostEditorView.swift index 5ed58af..9fb1e34 100644 --- a/macOS/PostEditor/PostEditorView.swift +++ b/macOS/PostEditor/PostEditorView.swift @@ -1,190 +1,199 @@ import SwiftUI struct PostEditorView: View { @EnvironmentObject var model: WriteFreelyModel @ObservedObject var post: WFAPost @State private var isHovering: Bool = false var body: some View { VStack { switch post.appearance { case "sans": TextField("Title (optional)", text: $post.title) .textFieldStyle(PlainTextFieldStyle()) .padding(.bottom) .font(.custom("OpenSans-Regular", size: 26, relativeTo: Font.TextStyle.largeTitle)) .onChange(of: post.title) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } ZStack(alignment: .topLeading) { if post.body.count == 0 { Text("Write...") .foregroundColor(Color(NSColor.placeholderTextColor)) .padding(.horizontal, 4) .padding(.vertical, 2) .font(.custom("OpenSans-Regular", size: 17, relativeTo: Font.TextStyle.body)) } TextEditor(text: $post.body) .font(.custom("OpenSans-Regular", size: 17, relativeTo: Font.TextStyle.body)) .opacity(post.body.count == 0 && !isHovering ? 0.0 : 1.0) .onChange(of: post.body) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } .onHover(perform: { hovering in self.isHovering = hovering }) } .background(Color(NSColor.controlBackgroundColor)) case "wrap", "mono", "code": TextField("Title (optional)", text: $post.title) .textFieldStyle(PlainTextFieldStyle()) .padding(.bottom) .font(.custom("Hack", size: 26, relativeTo: Font.TextStyle.largeTitle)) .onChange(of: post.title) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } ZStack(alignment: .topLeading) { if post.body.count == 0 { Text("Write...") .foregroundColor(Color(NSColor.placeholderTextColor)) .padding(.horizontal, 4) .padding(.vertical, 2) .font(.custom("Hack", size: 17, relativeTo: Font.TextStyle.body)) } TextEditor(text: $post.body) .font(.custom("Hack", size: 17, relativeTo: Font.TextStyle.body)) .opacity(post.body.count == 0 && !isHovering ? 0.0 : 1.0) .onChange(of: post.body) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } .onHover(perform: { hovering in self.isHovering = hovering }) } .background(Color(NSColor.controlBackgroundColor)) default: TextField("Title (optional)", text: $post.title) .textFieldStyle(PlainTextFieldStyle()) .padding(.bottom) .font(.custom("Lora", size: 26, relativeTo: Font.TextStyle.largeTitle)) .onChange(of: post.title) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } ZStack(alignment: .topLeading) { if post.body.count == 0 { Text("Write...") .foregroundColor(Color(NSColor.placeholderTextColor)) .padding(.horizontal, 4) .padding(.vertical, 2) .font(.custom("Lora", size: 17, relativeTo: Font.TextStyle.body)) } TextEditor(text: $post.body) .font(.custom("Lora", size: 17, relativeTo: Font.TextStyle.body)) .opacity(post.body.count == 0 && !isHovering ? 0.0 : 1.0) .onChange(of: post.body) { _ in if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } } .onHover(perform: { hovering in self.isHovering = hovering }) } .background(Color(NSColor.controlBackgroundColor)) } } .padding() .background(Color.white) .toolbar { ToolbarItem(placement: .status) { PostEditorStatusToolbarView(post: post) } ToolbarItem(placement: .primaryAction) { Button(action: { publishPost() }, label: { Image(systemName: "paperplane") }) .disabled( post.status == PostStatus.published.rawValue || !model.account.isLoggedIn || !model.hasNetworkConnection ) } } .onChange(of: post.hasNewerRemoteCopy, perform: { _ in if post.status == PostStatus.edited.rawValue && !post.hasNewerRemoteCopy { 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.status != PostStatus.published.rawValue { - DispatchQueue.main.async { - LocalStorageManager().saveContext() + if post.title.count == 0 + && post.body.count == 0 + && post.status == PostStatus.local.rawValue + && post.updatedDate == nil + && post.postId == nil { + withAnimation { + model.posts.remove(post) + model.posts.loadCachedPosts() } - } else { + } else if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { - model.editor.clearLastDraft() + LocalStorageManager().saveContext() } } }) } private func publishPost() { DispatchQueue.main.async { LocalStorageManager().saveContext() model.posts.loadCachedPosts() model.publish(post: post) } } } struct PostEditorView_EmptyPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.persistentContainer.viewContext let testPost = WFAPost(context: context) testPost.createdDate = Date() testPost.appearance = "norm" let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } } struct PostEditorView_ExistingPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.persistentContainer.viewContext let testPost = WFAPost(context: context) testPost.title = "Test Post Title" testPost.body = "Here's some cool sample body text." testPost.createdDate = Date() testPost.appearance = "code" let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } }