Source code for the WriteFreely SwiftUI app for iOS, iPadOS, and macOS
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 

198 рядки
8.3 KiB

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