Source code for the WriteFreely SwiftUI app for iOS, iPadOS, and macOS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

176 lines
7.5 KiB

  1. import SwiftUI
  2. import Combine
  3. struct PostListView: View {
  4. @EnvironmentObject var model: WriteFreelyModel
  5. @Environment(\.managedObjectContext) var managedObjectContext
  6. @State private var postCount: Int = 0
  7. var selectedCollection: WFACollection?
  8. var showAllPosts: Bool
  9. #if os(iOS)
  10. private var frameHeight: CGFloat {
  11. var height: CGFloat = 50
  12. let bottom = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0
  13. height += bottom
  14. return height
  15. }
  16. #endif
  17. var body: some View {
  18. #if os(iOS)
  19. ZStack(alignment: .bottom) {
  20. PostListFilteredView(
  21. collection: selectedCollection,
  22. showAllPosts: showAllPosts,
  23. postCount: $postCount
  24. )
  25. .navigationTitle(
  26. showAllPosts ? "All Posts" : selectedCollection?.title ?? (
  27. model.account.server == "https://write.as" ? "Anonymous" : "Drafts"
  28. )
  29. )
  30. .toolbar {
  31. ToolbarItem(placement: .primaryAction) {
  32. // We have to add a Spacer as a sibling view to the Button in some kind of Stack, so that any
  33. // a11y modifiers are applied as expected: bug report filed as FB8956392.
  34. ZStack {
  35. Spacer()
  36. Button(action: {
  37. let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font)
  38. withAnimation {
  39. self.model.showAllPosts = false
  40. self.model.selectedPost = managedPost
  41. }
  42. }, label: {
  43. ZStack {
  44. Image("does.not.exist")
  45. .accessibilityHidden(true)
  46. Image(systemName: "square.and.pencil")
  47. .accessibilityHidden(true)
  48. .imageScale(.large) // These modifiers compensate for the resizing
  49. .padding(.vertical, 12) // done to the Image (and the button tap target)
  50. .padding(.leading, 12) // by the SwiftUI layout system from adding a
  51. .padding(.trailing, 8) // Spacer in this ZStack (FB8956392).
  52. }
  53. .frame(maxWidth: .infinity, maxHeight: .infinity)
  54. })
  55. .accessibilityLabel(Text("Compose"))
  56. .accessibilityHint(Text("Compose a new local draft"))
  57. }
  58. }
  59. }
  60. VStack {
  61. HStack(spacing: 0) {
  62. Button(action: {
  63. model.isPresentingSettingsView = true
  64. }, label: {
  65. Image(systemName: "gear")
  66. .padding(.vertical, 4)
  67. .padding(.horizontal, 8)
  68. })
  69. .accessibilityLabel(Text("Settings"))
  70. .accessibilityHint(Text("Open the Settings sheet"))
  71. .sheet(
  72. isPresented: $model.isPresentingSettingsView,
  73. onDismiss: { model.isPresentingSettingsView = false },
  74. content: {
  75. SettingsView()
  76. .environmentObject(model)
  77. }
  78. )
  79. Spacer()
  80. Text(postCount == 1 ? "\(postCount) post" : "\(postCount) posts")
  81. .foregroundColor(.secondary)
  82. .alert(isPresented: $model.isPresentingNetworkErrorAlert, content: {
  83. Alert(
  84. title: Text("Connection Error"),
  85. message: Text("""
  86. There is no internet connection at the moment. Please reconnect or try again later.
  87. """),
  88. dismissButton: .default(Text("OK"), action: {
  89. model.isPresentingNetworkErrorAlert = false
  90. })
  91. )
  92. })
  93. Spacer()
  94. if model.isProcessingRequest {
  95. ProgressView()
  96. .padding(.vertical, 4)
  97. .padding(.horizontal, 8)
  98. } else {
  99. Button(action: {
  100. DispatchQueue.main.async {
  101. model.fetchUserCollections()
  102. model.fetchUserPosts()
  103. }
  104. }, label: {
  105. Image(systemName: "arrow.clockwise")
  106. .padding(.vertical, 4)
  107. .padding(.horizontal, 8)
  108. })
  109. .accessibilityLabel(Text("Refresh Posts"))
  110. .accessibilityHint(Text("Fetch changes from the server"))
  111. .disabled(!model.account.isLoggedIn)
  112. }
  113. }
  114. .padding(.top, 8)
  115. .padding(.horizontal, 8)
  116. Spacer()
  117. }
  118. .frame(height: frameHeight)
  119. .background(Color(UIColor.systemGray5))
  120. .overlay(Divider(), alignment: .top)
  121. }
  122. .ignoresSafeArea()
  123. .onAppear {
  124. model.selectedCollection = selectedCollection
  125. model.showAllPosts = showAllPosts
  126. }
  127. #else
  128. PostListFilteredView(
  129. collection: selectedCollection,
  130. showAllPosts: showAllPosts,
  131. postCount: $postCount
  132. )
  133. .toolbar {
  134. ToolbarItemGroup(placement: .primaryAction) {
  135. if model.selectedPost != nil {
  136. ActivePostToolbarView(activePost: model.selectedPost!)
  137. .alert(isPresented: $model.isPresentingNetworkErrorAlert, content: {
  138. Alert(
  139. title: Text("Connection Error"),
  140. message: Text("""
  141. There is no internet connection at the moment. \
  142. Please reconnect or try again later.
  143. """),
  144. dismissButton: .default(Text("OK"), action: {
  145. model.isPresentingNetworkErrorAlert = false
  146. })
  147. )
  148. })
  149. }
  150. }
  151. }
  152. .navigationTitle(
  153. showAllPosts ? "All Posts" : selectedCollection?.title ?? (
  154. model.account.server == "https://write.as" ? "Anonymous" : "Drafts"
  155. )
  156. )
  157. #endif
  158. }
  159. }
  160. struct PostListView_Previews: PreviewProvider {
  161. static var previews: some View {
  162. let context = LocalStorageManager.standard.container.viewContext
  163. let model = WriteFreelyModel()
  164. return PostListView(showAllPosts: true)
  165. .environment(\.managedObjectContext, context)
  166. .environmentObject(model)
  167. }
  168. }