IceCubesApp/Packages/Conversations/Sources/Conversations/List/ConversationsListRow.swift
Thomas Ricouard 1f858414d8 format .
2024-02-14 12:48:14 +01:00

241 lines
8 KiB
Swift

import DesignSystem
import Env
import Models
import Network
import SwiftUI
@MainActor
struct ConversationsListRow: View {
@Environment(\.openWindow) private var openWindow
@Environment(Client.self) private var client
@Environment(RouterPath.self) private var routerPath
@Environment(Theme.self) private var theme
@Environment(CurrentAccount.self) private var currentAccount
@Binding var conversation: Conversation
var viewModel: ConversationsListViewModel
var body: some View {
Button {
Task {
await viewModel.markAsRead(conversation: conversation)
}
routerPath.navigate(to: .conversationDetail(conversation: conversation))
} label: {
VStack(alignment: .leading) {
HStack(alignment: .top, spacing: 8) {
AvatarView(conversation.accounts.first!.avatar)
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 4) {
HStack {
EmojiTextApp(.init(stringValue: conversation.accounts.map(\.safeDisplayName).joined(separator: ", ")),
emojis: conversation.accounts.flatMap(\.emojis))
.font(.scaledSubheadline)
.foregroundColor(theme.labelColor)
.emojiText.size(Font.scaledSubheadlineFont.emojiSize)
.emojiText.baselineOffset(Font.scaledSubheadlineFont.emojiBaselineOffset)
.fontWeight(.semibold)
.foregroundColor(theme.labelColor)
.multilineTextAlignment(.leading)
Spacer()
if conversation.unread {
Circle()
.foregroundColor(theme.tintColor)
.frame(width: 10, height: 10)
.accessibilityRepresentation {
Text("accessibility.tabs.messages.unread.label")
}
.accessibilitySortPriority(1)
}
if let message = conversation.lastStatus {
Text(message.createdAt.relativeFormatted)
.font(.scaledFootnote)
}
}
EmojiTextApp(conversation.lastStatus?.content ?? HTMLString(stringValue: ""), emojis: conversation.lastStatus?.emojis ?? [])
.multilineTextAlignment(.leading)
.font(.scaledBody)
.foregroundColor(theme.labelColor)
.emojiText.size(Font.scaledBodyFont.emojiSize)
.emojiText.baselineOffset(Font.scaledBodyFont.emojiBaselineOffset)
.accessibilityLabel(conversation.lastStatus?.content.asRawText ?? "")
}
Spacer()
}
.padding(.top, 4)
if conversation.lastStatus != nil {
actionsView
.padding(.bottom, 4)
.accessibilityHidden(true)
}
}
.contextMenu {
contextMenu
.accessibilityHidden(true)
}
.accessibilityElement(children: .combine)
.accessibilityActions {
replyAction
contextMenu
accessibilityActions
}
.accessibilityAction(.magicTap) {
if let lastStatus = conversation.lastStatus {
HapticManager.shared.fireHaptic(.notification(.success))
#if targetEnvironment(macCatalyst) || os(visionOS)
openWindow(value: WindowDestinationEditor.replyToStatusEditor(status: lastStatus))
#else
routerPath.presentedSheet = .replyToStatusEditor(status: lastStatus)
#endif
}
}
}
.buttonStyle(.plain)
.hoverEffectDisabled()
}
private var actionsView: some View {
HStack(spacing: 12) {
Button {
if let lastStatus = conversation.lastStatus {
HapticManager.shared.fireHaptic(.notification(.success))
#if targetEnvironment(macCatalyst) || os(visionOS)
openWindow(value: WindowDestinationEditor.replyToStatusEditor(status: lastStatus))
#else
routerPath.presentedSheet = .replyToStatusEditor(status: lastStatus)
#endif
}
} label: {
Image(systemName: "arrowshape.turn.up.left.fill")
}
Menu {
contextMenu
} label: {
Image(systemName: "ellipsis")
.frame(width: 30, height: 30)
.contentShape(Rectangle())
}
}
.padding(.leading, 48)
.foregroundStyle(.secondary)
}
@ViewBuilder
private var contextMenu: some View {
if conversation.unread {
Button {
Task {
await viewModel.markAsRead(conversation: conversation)
}
} label: {
Label("conversations.action.mark-read", systemImage: "eye")
}
}
if let message = conversation.lastStatus {
Section("conversations.latest.message") {
Button {
UIPasteboard.general.string = message.content.asRawText
} label: {
Label("status.action.copy-text", systemImage: "doc.on.doc")
}
likeAndBookmark
}
Divider()
if message.account.id != currentAccount.account?.id {
Section(message.reblog?.account.acct ?? message.account.acct) {
Button {
routerPath.presentedSheet = .mentionStatusEditor(account: message.reblog?.account ?? message.account, visibility: .pub)
} label: {
Label("status.action.mention", systemImage: "at")
}
}
Section {
Button(role: .destructive) {
routerPath.presentedSheet = .report(status: message.reblogAsAsStatus ?? message)
} label: {
Label("status.action.report", systemImage: "exclamationmark.bubble")
}
}
}
}
Button(role: .destructive) {
Task {
await viewModel.delete(conversation: conversation)
}
} label: {
Label("conversations.action.delete", systemImage: "trash")
}
}
@ViewBuilder
private var likeAndBookmark: some View {
Button {
Task {
await viewModel.favorite(conversation: conversation)
}
} label: {
Label(conversation.lastStatus?.favourited ?? false ? "status.action.unfavorite" : "status.action.favorite",
systemImage: conversation.lastStatus?.favourited ?? false ? "star.fill" : "star")
}
Button {
Task {
await viewModel.bookmark(conversation: conversation)
}
} label: {
Label(conversation.lastStatus?.bookmarked ?? false ? "status.action.unbookmark" : "status.action.bookmark",
systemImage: conversation.lastStatus?.bookmarked ?? false ? "bookmark.fill" : "bookmark")
}
}
// MARK: - Accessibility actions
@ViewBuilder
var replyAction: some View {
if let lastStatus = conversation.lastStatus {
Button("status.action.reply") {
HapticManager.shared.fireHaptic(.notification(.success))
routerPath.presentedSheet = .replyToStatusEditor(status: lastStatus)
}
} else {
EmptyView()
}
}
@ViewBuilder
private var accessibilityActions: some View {
if let lastStatus = conversation.lastStatus {
if lastStatus.account.id != currentAccount.account?.id {
Button("@\(lastStatus.account.username)") {
HapticManager.shared.fireHaptic(.notification(.success))
routerPath.navigate(to: .accountDetail(id: lastStatus.account.id))
}
}
// Add in each detected link in the content
ForEach(lastStatus.content.links) { link in
switch link.type {
case .url:
if UIApplication.shared.canOpenURL(link.url) {
Button("accessibility.tabs.timeline.content-link-\(link.title)") {
HapticManager.shared.fireHaptic(.notification(.success))
_ = routerPath.handle(url: link.url)
}
}
case .hashtag:
Button("accessibility.tabs.timeline.content-hashtag-\(link.title)") {
HapticManager.shared.fireHaptic(.notification(.success))
_ = routerPath.handle(url: link.url)
}
case .mention:
Button("\(link.title)") {
HapticManager.shared.fireHaptic(.notification(.success))
_ = routerPath.handle(url: link.url)
}
}
}
}
}
}