diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index e20651a..0f9e371 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -1,131 +1,146 @@ import SwiftUI import Combine 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 var body: some View { #if os(iOS) GeometryReader { geometry in PostListFilteredView(collection: selectedCollection, showAllPosts: showAllPosts, postCount: $postCount) .navigationTitle( showAllPosts ? "All Posts" : selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) .toolbar { ToolbarItem(placement: .primaryAction) { - 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 - managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft - } - withAnimation { - self.selectedCollection = nil - self.showAllPosts = false - self.model.selectedPost = managedPost - } - }, label: { - Image(systemName: "square.and.pencil") - }) + // 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 = 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 + managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft + } + withAnimation { + self.selectedCollection = nil + self.showAllPosts = false + self.model.selectedPost = managedPost + } + }, label: { + Image(systemName: "square.and.pencil") + .scaleEffect(1.25) // 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). + }) + .accessibilityLabel(Text("Compose")) + .accessibilityHint(Text("Compose a new local draft")) + } } ToolbarItem(placement: .bottomBar) { HStack { Button(action: { model.isPresentingSettingsView = true }, label: { Image(systemName: "gear") }) + .accessibilityLabel(Text("Settings")) + .accessibilityHint(Text("Open the Settings sheet")) Spacer() Text(postCount == 1 ? "\(postCount) post" : "\(postCount) posts") .foregroundColor(.secondary) Spacer() if model.isProcessingRequest { ProgressView() } else { Button(action: { DispatchQueue.main.async { model.fetchUserCollections() model.fetchUserPosts() } }, label: { Image(systemName: "arrow.clockwise") }) + .accessibilityLabel(Text("Refresh Posts")) + .accessibilityHint(Text("Fetch changes from the server")) .disabled(!model.account.isLoggedIn) } } .padding() .frame(width: geometry.size.width) } } } #else //if os(macOS) PostListFilteredView( collection: selectedCollection, showAllPosts: 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(""" There is no internet connection at the moment. \ Please reconnect or try again later. """), dismissButton: .default(Text("OK"), action: { model.isPresentingNetworkErrorAlert = false }) ) }) } } } .onDisappear { DispatchQueue.main.async { self.model.selectedCollection = nil self.model.showAllPosts = true self.model.selectedPost = nil } } .navigationTitle( showAllPosts ? "All Posts" : 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/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/RemoteChangePromptView.swift b/iOS/PostEditor/RemoteChangePromptView.swift index 0807155..184d6b3 100644 --- a/iOS/PostEditor/RemoteChangePromptView.swift +++ b/iOS/PostEditor/RemoteChangePromptView.swift @@ -1,55 +1,63 @@ import SwiftUI enum RemotePostChangeType { case remoteCopyUpdated case remoteCopyDeleted } struct RemoteChangePromptView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass @State private var promptText: String = "This is placeholder prompt text. Replace it?" @State private var promptIcon: Image = Image(systemName: "questionmark.square.dashed") + @State private var accessibilityLabel: String = "Replace" + @State private var accessibilityHint: String = "Replace this text with an accessibility hint" @State var remoteChangeType: RemotePostChangeType @State var buttonHandler: () -> Void var body: some View { HStack { Text("⚠️ \(promptText)") .font(horizontalSizeClass == .compact ? .caption : .body) .foregroundColor(.secondary) Button(action: buttonHandler, label: { promptIcon }) + .accessibilityLabel(Text(accessibilityLabel)) + .accessibilityHint(Text(accessibilityHint)) } .padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20)) .background(Color(UIColor.secondarySystemBackground)) .clipShape(Capsule()) .padding(.bottom) .onAppear(perform: { switch remoteChangeType { case .remoteCopyUpdated: promptText = "Newer copy on server. Replace local copy?" promptIcon = Image(systemName: "square.and.arrow.down") + accessibilityLabel = "Update post" + accessibilityHint = "Replace this post with the server version" case .remoteCopyDeleted: promptText = "Post deleted from server. Delete local copy?" promptIcon = Image(systemName: "trash") + accessibilityLabel = "Delete" + accessibilityHint = "Delete this post from your device" } }) } } struct RemoteChangePromptView_UpdatedPreviews: PreviewProvider { static var previews: some View { RemoteChangePromptView( remoteChangeType: .remoteCopyUpdated, buttonHandler: { print("Hello, updated post!") } ) } } struct RemoteChangePromptView_DeletedPreviews: PreviewProvider { static var previews: some View { RemoteChangePromptView( remoteChangeType: .remoteCopyDeleted, buttonHandler: { print("Goodbye, deleted post!") } ) } } diff --git a/iOS/Settings/SettingsHeaderView.swift b/iOS/Settings/SettingsHeaderView.swift index ca65578..090040b 100644 --- a/iOS/Settings/SettingsHeaderView.swift +++ b/iOS/Settings/SettingsHeaderView.swift @@ -1,32 +1,34 @@ import SwiftUI struct SettingsHeaderView: View { @Environment(\.presentationMode) var presentationMode var body: some View { VStack { HStack { Text("Settings") .font(.largeTitle) .fontWeight(.bold) Spacer() Button(action: { presentationMode.wrappedValue.dismiss() }, label: { Image(systemName: "xmark.circle") }) + .accessibilityLabel(Text("Close")) + .accessibilityHint(Text("Dismiss the Settings sheet")) } Text("WriteFreely v\(Bundle.main.appMarketingVersion) (build \(Bundle.main.appBuildVersion))") .font(.caption) .foregroundColor(.secondary) .padding(.top) } .padding() } } struct SettingsHeaderView_Previews: PreviewProvider { static var previews: some View { SettingsHeaderView() } }