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 @@ -1,150 +1,148 @@ import SwiftUI import Combine struct PostListView: View { @EnvironmentObject var model: WriteFreelyModel @Environment(\.managedObjectContext) var managedObjectContext @State private var postCount: Int = 0 #if os(iOS) private var frameHeight: CGFloat { var height: CGFloat = 50 let bottom = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0 height += bottom return height } #endif var body: some View { #if os(iOS) ZStack(alignment: .bottom) { PostListFilteredView( collection: model.selectedCollection, showAllPosts: model.showAllPosts, postCount: $postCount ) .navigationTitle( model.showAllPosts ? "All Posts" : model.selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) .toolbar { ToolbarItem(placement: .primaryAction) { // We have to add a Spacer as a sibling view to the Button in some kind of Stack, so that any // a11y modifiers are applied as expected: bug report filed as FB8956392. ZStack { Spacer() Button(action: { let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { self.model.showAllPosts = false self.model.selectedCollection = nil self.model.selectedPost = managedPost } }, label: { ZStack { Image("does.not.exist") .accessibilityHidden(true) Image(systemName: "square.and.pencil") .accessibilityHidden(true) .imageScale(.large) // These modifiers compensate for the resizing .padding(.vertical, 12) // done to the Image (and the button tap target) .padding(.leading, 12) // by the SwiftUI layout system from adding a .padding(.trailing, 8) // Spacer in this ZStack (FB8956392). } .frame(maxWidth: .infinity, maxHeight: .infinity) }) .accessibilityLabel(Text("Compose")) .accessibilityHint(Text("Compose a new local draft")) } } } VStack { HStack(spacing: 0) { Button(action: { model.isPresentingSettingsView = true }, label: { Image(systemName: "gear") .padding(.vertical, 4) .padding(.horizontal, 8) }) .accessibilityLabel(Text("Settings")) .accessibilityHint(Text("Open the Settings sheet")) Spacer() Text(postCount == 1 ? "\(postCount) post" : "\(postCount) posts") .foregroundColor(.secondary) Spacer() if model.isProcessingRequest { ProgressView() .padding(.vertical, 4) .padding(.horizontal, 8) } else { Button(action: { DispatchQueue.main.async { model.fetchUserCollections() model.fetchUserPosts() } }, label: { Image(systemName: "arrow.clockwise") .padding(.vertical, 4) .padding(.horizontal, 8) }) .accessibilityLabel(Text("Refresh Posts")) .accessibilityHint(Text("Fetch changes from the server")) .disabled(!model.account.isLoggedIn) } } .padding(.top, 8) .padding(.horizontal, 8) Spacer() } .frame(height: frameHeight) .background(Color(UIColor.systemGray5)) .overlay(Divider(), alignment: .top) } .ignoresSafeArea() #else //if os(macOS) PostListFilteredView( collection: model.selectedCollection, showAllPosts: model.showAllPosts, postCount: $postCount ) .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( model.showAllPosts ? "All Posts" : model.selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) #endif } } 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/macOS/Navigation/ActivePostToolbarView.swift b/macOS/Navigation/ActivePostToolbarView.swift index 20334f4..bd18ff9 100644 --- a/macOS/Navigation/ActivePostToolbarView.swift +++ b/macOS/Navigation/ActivePostToolbarView.swift @@ -1,128 +1,130 @@ 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( entity: WFACollection.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)] ) 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) + .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() + } } 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] } private func publishPost(_ post: WFAPost) { DispatchQueue.main.async { LocalStorageManager().saveContext() model.publish(post: post) } } private func openSettingsWindow() { guard let menuItem = NSApplication.shared.mainMenu?.item(at: 0)?.submenu?.item(at: 2) else { return } NSApplication.shared.sendAction(menuItem.action!, to: menuItem.target, from: nil) } }