2022-12-19 11:28:55 +00:00
|
|
|
import DesignSystem
|
2023-01-17 10:36:01 +00:00
|
|
|
import EmojiText
|
|
|
|
import Env
|
|
|
|
import Models
|
2022-12-19 14:51:25 +00:00
|
|
|
import Network
|
2022-12-30 18:31:17 +00:00
|
|
|
import Shimmer
|
2023-01-17 10:36:01 +00:00
|
|
|
import SwiftUI
|
2022-11-21 08:31:32 +00:00
|
|
|
|
2022-12-18 19:30:19 +00:00
|
|
|
public struct StatusRowView: View {
|
2023-02-22 17:49:17 +00:00
|
|
|
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
2022-12-17 12:37:46 +00:00
|
|
|
@Environment(\.redactionReasons) private var reasons
|
2023-02-17 17:17:51 +00:00
|
|
|
@Environment(\.isCompact) private var isCompact: Bool
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2022-12-24 14:09:17 +00:00
|
|
|
@EnvironmentObject private var theme: Theme
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2022-12-20 19:33:45 +00:00
|
|
|
@StateObject var viewModel: StatusRowViewModel
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2023-02-24 12:25:21 +00:00
|
|
|
// 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())
|
2022-12-18 19:30:19 +00:00
|
|
|
}
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2023-01-27 15:51:45 +00:00
|
|
|
var contextMenu: some View {
|
|
|
|
StatusRowContextMenu(viewModel: viewModel)
|
|
|
|
}
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2022-12-18 19:30:19 +00:00
|
|
|
public var body: some View {
|
2023-03-01 16:34:03 +00:00
|
|
|
VStack(alignment: .leading) {
|
|
|
|
if viewModel.isFiltered, let filter = viewModel.filter {
|
|
|
|
switch filter.filter.filterAction {
|
|
|
|
case .warn:
|
|
|
|
makeFilterView(filter: filter.filter)
|
|
|
|
case .hide:
|
|
|
|
EmptyView()
|
|
|
|
}
|
|
|
|
} else {
|
2023-02-17 17:17:51 +00:00
|
|
|
if !isCompact, theme.avatarPosition == .leading {
|
2023-03-07 06:10:57 +00:00
|
|
|
Group {
|
|
|
|
StatusRowReblogView(viewModel: viewModel)
|
|
|
|
StatusRowReplyView(viewModel: viewModel)
|
|
|
|
}
|
|
|
|
.padding(.leading, AvatarView.Size.status.size.width + .statusColumnsSpacing)
|
2022-12-31 11:29:19 +00:00
|
|
|
}
|
2023-01-24 18:07:55 +00:00
|
|
|
HStack(alignment: .top, spacing: .statusColumnsSpacing) {
|
2023-02-17 17:17:51 +00:00
|
|
|
if !isCompact,
|
2023-02-18 06:26:48 +00:00
|
|
|
theme.avatarPosition == .leading
|
|
|
|
{
|
2023-01-24 18:07:55 +00:00
|
|
|
Button {
|
2023-03-12 07:04:20 +00:00
|
|
|
viewModel.navigateToAccountDetail(account: viewModel.finalStatus.account)
|
2023-01-24 18:07:55 +00:00
|
|
|
} label: {
|
2023-03-02 05:56:25 +00:00
|
|
|
AvatarView(url: viewModel.finalStatus.account.avatar, size: .status)
|
2023-01-24 18:07:55 +00:00
|
|
|
}
|
2023-01-03 11:24:15 +00:00
|
|
|
}
|
2023-01-24 18:07:55 +00:00
|
|
|
VStack(alignment: .leading) {
|
2023-02-17 17:17:51 +00:00
|
|
|
if !isCompact, theme.avatarPosition == .top {
|
2023-02-17 12:30:56 +00:00
|
|
|
StatusRowReblogView(viewModel: viewModel)
|
|
|
|
StatusRowReplyView(viewModel: viewModel)
|
|
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 8) {
|
2023-02-17 17:17:51 +00:00
|
|
|
if !isCompact {
|
2023-03-02 05:56:25 +00:00
|
|
|
StatusRowHeaderView(viewModel: viewModel)
|
2023-02-17 12:30:56 +00:00
|
|
|
}
|
2023-03-02 05:56:25 +00:00
|
|
|
StatusRowContentView(viewModel: viewModel)
|
2023-02-17 12:30:56 +00:00
|
|
|
.contentShape(Rectangle())
|
|
|
|
.onTapGesture {
|
|
|
|
viewModel.navigateToDetail()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
|
|
|
|
.accessibilityAction {
|
|
|
|
viewModel.navigateToDetail()
|
2023-01-24 18:07:55 +00:00
|
|
|
}
|
2023-02-22 18:09:39 +00:00
|
|
|
if viewModel.showActions, viewModel.isFocused || theme.statusActionsDisplay != .none, !isInCaptureMode {
|
2023-02-17 12:30:56 +00:00
|
|
|
StatusRowActionsView(viewModel: viewModel)
|
2023-01-24 18:07:55 +00:00
|
|
|
.padding(.top, 8)
|
|
|
|
.tint(viewModel.isFocused ? theme.tintColor : .gray)
|
|
|
|
.contentShape(Rectangle())
|
|
|
|
.onTapGesture {
|
2023-02-15 07:46:14 +00:00
|
|
|
viewModel.navigateToDetail()
|
2023-01-24 18:07:55 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-03 11:24:15 +00:00
|
|
|
}
|
2022-12-31 11:29:19 +00:00
|
|
|
}
|
2022-12-23 16:50:51 +00:00
|
|
|
}
|
2023-03-01 16:34:03 +00:00
|
|
|
}
|
|
|
|
.onAppear {
|
|
|
|
viewModel.markSeen()
|
|
|
|
if reasons.isEmpty {
|
|
|
|
if !isCompact, viewModel.embeddedStatus == nil {
|
|
|
|
Task {
|
|
|
|
await viewModel.loadEmbeddedStatus()
|
2023-01-22 08:51:43 +00:00
|
|
|
}
|
2023-01-09 19:51:12 +00:00
|
|
|
}
|
2022-12-27 06:51:44 +00:00
|
|
|
}
|
2023-03-01 16:34:03 +00:00
|
|
|
}
|
|
|
|
.contextMenu {
|
|
|
|
contextMenu
|
|
|
|
}
|
|
|
|
.swipeActions(edge: .trailing) {
|
|
|
|
if !isCompact {
|
|
|
|
StatusRowSwipeView(viewModel: viewModel, mode: .trailing)
|
2023-01-22 05:38:30 +00:00
|
|
|
}
|
2023-03-01 16:34:03 +00:00
|
|
|
}
|
|
|
|
.swipeActions(edge: .leading) {
|
|
|
|
if !isCompact {
|
|
|
|
StatusRowSwipeView(viewModel: viewModel, mode: .leading)
|
2023-02-01 20:51:03 +00:00
|
|
|
}
|
2023-03-01 16:34:03 +00:00
|
|
|
}
|
|
|
|
.listRowBackground(viewModel.highlightRowColor)
|
|
|
|
.listRowInsets(.init(top: 12,
|
|
|
|
leading: .layoutPadding,
|
|
|
|
bottom: 12,
|
|
|
|
trailing: .layoutPadding))
|
|
|
|
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
|
|
|
|
.accessibilityActions {
|
|
|
|
if UIAccessibility.isVoiceOverRunning {
|
|
|
|
accesibilityActions
|
2023-02-01 20:51:03 +00:00
|
|
|
}
|
2023-03-01 16:34:03 +00:00
|
|
|
}
|
|
|
|
.background {
|
|
|
|
Color.clear
|
|
|
|
.contentShape(Rectangle())
|
|
|
|
.onTapGesture {
|
|
|
|
viewModel.navigateToDetail()
|
2023-02-15 18:27:26 +00:00
|
|
|
}
|
2023-03-01 16:34:03 +00:00
|
|
|
}
|
|
|
|
.overlay {
|
|
|
|
if viewModel.isLoadingRemoteContent {
|
|
|
|
remoteContentLoadingView
|
2023-01-22 05:38:30 +00:00
|
|
|
}
|
2023-03-01 16:34:03 +00:00
|
|
|
}
|
|
|
|
.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()
|
2023-01-12 18:10:40 +00:00
|
|
|
}
|
2023-03-01 16:34:03 +00:00
|
|
|
},
|
|
|
|
secondaryButton: .cancel()
|
2023-02-28 05:58:52 +00:00
|
|
|
)
|
2023-03-01 16:34:03 +00:00
|
|
|
})
|
|
|
|
.alignmentGuide(.listRowSeparatorLeading) { _ in
|
|
|
|
-100
|
2022-12-20 19:33:45 +00:00
|
|
|
}
|
2023-03-01 16:34:03 +00:00
|
|
|
.environmentObject(
|
2023-03-02 19:15:07 +00:00
|
|
|
StatusDataControllerProvider.shared.dataController(for: viewModel.finalStatus,
|
2023-03-02 20:16:03 +00:00
|
|
|
client: viewModel.client)
|
2023-03-01 16:34:03 +00:00
|
|
|
)
|
2023-01-03 11:24:15 +00:00
|
|
|
}
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2023-02-09 08:12:44 +00:00
|
|
|
@ViewBuilder
|
|
|
|
private var accesibilityActions: some View {
|
|
|
|
// Add the individual mentions as accessibility actions
|
|
|
|
ForEach(viewModel.status.mentions, id: \.id) { mention in
|
|
|
|
Button("@\(mention.username)") {
|
2023-02-15 07:46:14 +00:00
|
|
|
viewModel.routerPath.navigate(to: .accountDetail(id: mention.id))
|
2023-02-09 08:12:44 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2023-02-09 08:12:44 +00:00
|
|
|
Button(viewModel.displaySpoiler ? "status.show-more" : "status.show-less") {
|
|
|
|
withAnimation {
|
|
|
|
viewModel.displaySpoiler.toggle()
|
|
|
|
}
|
|
|
|
}
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2023-02-09 08:12:44 +00:00
|
|
|
Button("@\(viewModel.status.account.username)") {
|
2023-02-15 07:46:14 +00:00
|
|
|
viewModel.routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id))
|
2023-02-09 08:12:44 +00:00
|
|
|
}
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2023-02-09 08:12:44 +00:00
|
|
|
contextMenu
|
|
|
|
}
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2023-01-03 11:24:15 +00:00
|
|
|
private func makeFilterView(filter: Filter) -> some View {
|
|
|
|
HStack {
|
2023-01-19 17:14:08 +00:00
|
|
|
Text("status.filter.filtered-by-\(filter.title)")
|
2023-01-03 11:24:15 +00:00
|
|
|
Button {
|
|
|
|
withAnimation {
|
|
|
|
viewModel.isFiltered = false
|
|
|
|
}
|
|
|
|
} label: {
|
2023-01-19 17:14:08 +00:00
|
|
|
Text("status.filter.show-anyway")
|
2023-01-03 11:24:15 +00:00
|
|
|
}
|
2022-12-27 08:11:12 +00:00
|
|
|
}
|
2022-12-16 12:16:48 +00:00
|
|
|
}
|
2023-03-13 12:38:28 +00:00
|
|
|
|
2023-01-29 10:17:43 +00:00
|
|
|
private var remoteContentLoadingView: some View {
|
|
|
|
ZStack(alignment: .center) {
|
|
|
|
VStack {
|
|
|
|
Spacer()
|
|
|
|
HStack {
|
|
|
|
Spacer()
|
|
|
|
ProgressView()
|
|
|
|
Spacer()
|
|
|
|
}
|
|
|
|
Spacer()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.background(Color.black.opacity(0.40))
|
|
|
|
.transition(.opacity)
|
|
|
|
}
|
2022-11-21 08:31:32 +00:00
|
|
|
}
|