diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index e0b56c46..f7f5d9dd 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -10,35 +10,32 @@ public struct StatusRowView: View { @Environment(\.isInCaptureMode) private var isInCaptureMode: Bool @Environment(\.redactionReasons) private var reasons @Environment(\.isCompact) private var isCompact: Bool - + @EnvironmentObject private var theme: Theme @EnvironmentObject private var client: Client - + @StateObject var viewModel: StatusRowViewModel - + // StateObject accepts an @autoclosure which only allocates the view model once when the view gets on screen. public init(viewModel: @escaping () -> StatusRowViewModel) { _viewModel = StateObject(wrappedValue: viewModel()) } - + var contextMenu: some View { StatusRowContextMenu(viewModel: viewModel) } - + public var body: some View { - if viewModel.isFiltered, let filter = viewModel.filter { - switch filter.filter.filterAction { - case .warn: - makeFilterView(filter: filter.filter) - .listRowBackground(viewModel.highlightRowColor) - case .hide: - EmptyView() - .listRowSeparator(.hidden) - .listRowInsets(.init()) - } - } else { + VStack(alignment: .leading) { let status: AnyStatus = viewModel.status.reblog ?? viewModel.status - VStack(alignment: .leading) { + if viewModel.isFiltered, let filter = viewModel.filter { + switch filter.filter.filterAction { + case .warn: + makeFilterView(filter: filter.filter) + case .hide: + EmptyView() + } + } else { if !isCompact, theme.avatarPosition == .leading { StatusRowReblogView(viewModel: viewModel) StatusRowReplyView(viewModel: viewModel) @@ -85,76 +82,76 @@ public struct StatusRowView: View { } } } - .onAppear { - viewModel.markSeen() - if reasons.isEmpty { - if !isCompact, viewModel.embeddedStatus == nil { - Task { - await viewModel.loadEmbeddedStatus() - } - } - } - } - .contextMenu { - contextMenu - } - .swipeActions(edge: .trailing) { - if !isCompact { - StatusRowSwipeView(viewModel: viewModel, mode: .trailing) - } - } - .swipeActions(edge: .leading) { - if !isCompact { - StatusRowSwipeView(viewModel: viewModel, mode: .leading) - } - } - .listRowBackground(viewModel.highlightRowColor) - .listRowInsets(.init(top: 12, - leading: .layoutPadding, - bottom: 12, - trailing: .layoutPadding)) - .accessibilityElement(children: viewModel.isFocused ? .contain : .combine) - .accessibilityActions { - if UIAccessibility.isVoiceOverRunning { - accesibilityActions - } - } - .background { - Color.clear - .contentShape(Rectangle()) - .onTapGesture { - viewModel.navigateToDetail() - } - } - .overlay { - if viewModel.isLoadingRemoteContent { - remoteContentLoadingView - } - } - .alert(isPresented: $viewModel.showDeleteAlert, content: { - Alert( - title: Text("status.action.delete.confirm.title"), - message: Text("status.action.delete.confirm.message"), - primaryButton: .destructive( - Text("status.action.delete")) - { - Task { - await viewModel.delete() - } - }, - secondaryButton: .cancel() - ) - }) - .alignmentGuide(.listRowSeparatorLeading) { _ in - -100 - } - .environmentObject( - StatusDataControllerProvider.shared.dataController(for: viewModel.status.reblog ?? viewModel.status, - client: client) - ) } + .onAppear { + viewModel.markSeen() + if reasons.isEmpty { + if !isCompact, viewModel.embeddedStatus == nil { + Task { + await viewModel.loadEmbeddedStatus() + } + } + } + } + .contextMenu { + contextMenu + } + .swipeActions(edge: .trailing) { + if !isCompact { + StatusRowSwipeView(viewModel: viewModel, mode: .trailing) + } + } + .swipeActions(edge: .leading) { + if !isCompact { + StatusRowSwipeView(viewModel: viewModel, mode: .leading) + } + } + .listRowBackground(viewModel.highlightRowColor) + .listRowInsets(.init(top: 12, + leading: .layoutPadding, + bottom: 12, + trailing: .layoutPadding)) + .accessibilityElement(children: viewModel.isFocused ? .contain : .combine) + .accessibilityActions { + if UIAccessibility.isVoiceOverRunning { + accesibilityActions + } + } + .background { + Color.clear + .contentShape(Rectangle()) + .onTapGesture { + viewModel.navigateToDetail() + } + } + .overlay { + if viewModel.isLoadingRemoteContent { + remoteContentLoadingView + } + } + .alert(isPresented: $viewModel.showDeleteAlert, content: { + Alert( + title: Text("status.action.delete.confirm.title"), + message: Text("status.action.delete.confirm.message"), + primaryButton: .destructive( + Text("status.action.delete")) + { + Task { + await viewModel.delete() + } + }, + secondaryButton: .cancel() + ) + }) + .alignmentGuide(.listRowSeparatorLeading) { _ in + -100 + } + .environmentObject( + StatusDataControllerProvider.shared.dataController(for: viewModel.status.reblog ?? viewModel.status, + client: client) + ) } - + @ViewBuilder private var accesibilityActions: some View { // Add the individual mentions as accessibility actions @@ -163,20 +160,20 @@ public struct StatusRowView: View { viewModel.routerPath.navigate(to: .accountDetail(id: mention.id)) } } - + Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") { withAnimation { viewModel.displaySpoiler.toggle() } } - + Button("@\(viewModel.status.account.username)") { viewModel.routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id)) } - + contextMenu } - + private func makeFilterView(filter: Filter) -> some View { HStack { Text("status.filter.filtered-by-\(filter.title)") @@ -189,7 +186,7 @@ public struct StatusRowView: View { } } } - + private var remoteContentLoadingView: some View { ZStack(alignment: .center) { VStack {