IceCubesApp/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift

229 lines
6.9 KiB
Swift
Raw Normal View History

2023-01-22 15:55:03 +00:00
import DesignSystem
import Env
2023-01-22 15:55:03 +00:00
import Models
import Network
2023-01-22 15:55:03 +00:00
import NukeUI
import SwiftUI
2023-01-22 15:55:03 +00:00
2023-09-18 19:03:52 +00:00
@MainActor
2023-01-22 15:55:03 +00:00
struct ConversationMessageView: View {
2023-10-24 16:34:45 +00:00
@Environment(\.openWindow) private var openWindow
@Environment(QuickLook.self) private var quickLook
@Environment(RouterPath.self) private var routerPath
@Environment(CurrentAccount.self) private var currentAccount
@Environment(Client.self) private var client
2023-09-18 19:03:52 +00:00
@Environment(Theme.self) private var theme
2023-01-22 15:55:03 +00:00
let message: Status
let conversation: Conversation
2023-01-22 15:55:03 +00:00
@State private var isLiked: Bool = false
@State private var isBookmarked: Bool = false
2023-01-22 15:55:03 +00:00
var body: some View {
let isOwnMessage = message.account.id == currentAccount.account?.id
VStack {
HStack(alignment: .bottom) {
if isOwnMessage {
Spacer()
} else {
AvatarView(message.account.avatar)
2023-01-22 15:55:03 +00:00
.onTapGesture {
routerPath.navigate(to: .accountDetailWithAccount(account: message.account))
}
}
VStack(alignment: .leading) {
EmojiTextApp(message.content, emojis: message.emojis)
.font(.scaledBody)
.foregroundColor(theme.labelColor)
2024-02-05 07:55:24 +00:00
.emojiText.size(Font.scaledBodyFont.emojiSize)
.emojiText.baselineOffset(Font.scaledBodyFont.emojiBaselineOffset)
2023-01-22 15:55:03 +00:00
.padding(6)
2023-01-23 17:43:57 +00:00
.environment(\.openURL, OpenURLAction { url in
routerPath.handleStatus(status: message, url: url)
})
2023-01-22 15:55:03 +00:00
}
#if os(visionOS)
.background(isOwnMessage ? Material.ultraThick : Material.regular)
#else
2023-01-22 15:55:03 +00:00
.background(isOwnMessage ? theme.tintColor.opacity(0.2) : theme.secondaryBackgroundColor)
#endif
2023-01-22 15:55:03 +00:00
.cornerRadius(8)
.padding(.leading, isOwnMessage ? 24 : 0)
.padding(.trailing, isOwnMessage ? 0 : 24)
.overlay {
if isLiked, message.account.id != currentAccount.account?.id {
likeView
}
}
.contextMenu {
contextMenu
}
2023-01-22 15:55:03 +00:00
if !isOwnMessage {
Spacer()
}
}
2023-01-22 15:55:03 +00:00
ForEach(message.mediaAttachments) { media in
makeMediaView(media)
.padding(.leading, isOwnMessage ? 24 : 0)
.padding(.trailing, isOwnMessage ? 0 : 24)
}
2023-02-12 15:29:41 +00:00
if message.id == String(conversation.lastStatus?.id ?? "") {
2023-01-22 15:55:03 +00:00
HStack {
if isOwnMessage {
Spacer()
}
2023-01-26 17:27:53 +00:00
Group {
Text(message.createdAt.shortDateFormatted) +
2023-01-27 19:36:40 +00:00
Text(" ")
2023-01-26 17:27:53 +00:00
Text(message.createdAt.asDate, style: .time)
}
.font(.scaledFootnote)
2023-12-04 14:49:44 +00:00
.foregroundStyle(.secondary)
2023-01-22 15:55:03 +00:00
if !isOwnMessage {
Spacer()
}
}
}
}
.onAppear {
2023-01-24 05:56:28 +00:00
isLiked = message.favourited == true
isBookmarked = message.bookmarked == true
2023-01-22 15:55:03 +00:00
}
}
2023-01-22 15:55:03 +00:00
@ViewBuilder
private var contextMenu: some View {
Button {
routerPath.navigate(to: .statusDetail(id: message.id))
} label: {
2023-02-13 17:12:34 +00:00
Label("conversations.action.view-detail", systemImage: "arrow.forward")
2023-01-22 15:55:03 +00:00
}
Button {
UIPasteboard.general.string = message.content.asRawText
} label: {
Label("status.action.copy-text", systemImage: "doc.on.doc")
}
Button {
Task {
do {
2023-11-01 18:55:48 +00:00
let status: Status
if isLiked {
status = try await client.post(endpoint: Statuses.unfavorite(id: message.id))
2023-01-22 15:55:03 +00:00
} else {
2023-11-01 18:55:48 +00:00
status = try await client.post(endpoint: Statuses.favorite(id: message.id))
2023-01-22 15:55:03 +00:00
}
withAnimation {
2023-01-24 05:56:28 +00:00
isLiked = status.favourited == true
2023-01-22 15:55:03 +00:00
}
} catch {}
2023-01-22 15:55:03 +00:00
}
} label: {
Label(isLiked ? "status.action.unfavorite" : "status.action.favorite",
systemImage: isLiked ? "star.fill" : "star")
}
2023-02-26 05:45:57 +00:00
Button { Task {
do {
2023-11-01 18:55:48 +00:00
let status: Status
if isBookmarked {
status = try await client.post(endpoint: Statuses.unbookmark(id: message.id))
2023-02-26 05:45:57 +00:00
} else {
2023-11-01 18:55:48 +00:00
status = try await client.post(endpoint: Statuses.bookmark(id: message.id))
2023-02-26 05:45:57 +00:00
}
withAnimation {
isBookmarked = status.bookmarked == true
}
2023-02-26 05:45:57 +00:00
} catch {}
} } label: {
Label(isBookmarked ? "status.action.unbookmark" : "status.action.bookmark",
systemImage: isBookmarked ? "bookmark.fill" : "bookmark")
}
2023-01-22 15:55:03 +00:00
Divider()
if message.account.id == currentAccount.account?.id {
Button("status.action.delete", role: .destructive) {
Task {
_ = try await client.delete(endpoint: Statuses.status(id: message.id))
}
}
} else {
2023-02-26 05:45:57 +00:00
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")
}
2023-02-26 05:45:57 +00:00
}
Section {
Button(role: .destructive) {
routerPath.presentedSheet = .report(status: message.reblogAsAsStatus ?? message)
} label: {
Label("status.action.report", systemImage: "exclamationmark.bubble")
}
}
2023-01-22 15:55:03 +00:00
}
}
private func makeImageRequest(for url: URL, size: CGSize) -> ImageRequest {
ImageRequest(url: url, processors: [.resize(size: size)])
}
2023-02-22 18:09:39 +00:00
private func mediaWidth(proxy: GeometryProxy) -> CGFloat {
var width = proxy.frame(in: .local).width
if UIDevice.current.userInterfaceIdiom == .pad {
width = width * 0.60
}
return width
}
2023-02-22 18:09:39 +00:00
2023-01-22 15:55:03 +00:00
private func makeMediaView(_ attachement: MediaAttachment) -> some View {
GeometryReader { proxy in
let width = mediaWidth(proxy: proxy)
if let url = attachement.url {
LazyImage(request: makeImageRequest(for: url,
2023-03-13 12:38:28 +00:00
size: .init(width: width, height: 200)))
{ state in
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 200)
.frame(maxWidth: width)
.clipped()
.cornerRadius(8)
.padding(8)
} else if state.isLoading {
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray)
.frame(height: 200)
}
}
2023-01-22 15:55:03 +00:00
}
}
.frame(height: 200)
.contentShape(Rectangle())
.onTapGesture {
2024-02-14 11:48:14 +00:00
#if targetEnvironment(macCatalyst) || os(visionOS)
openWindow(value: WindowDestinationMedia.mediaViewer(attachments: [attachement],
selectedAttachment: attachement))
#else
quickLook.prepareFor(selectedMediaAttachment: attachement, mediaAttachments: [attachement])
#endif
2023-01-22 15:55:03 +00:00
}
}
2023-01-22 15:55:03 +00:00
private var likeView: some View {
HStack {
Spacer()
VStack {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
.offset(x: -16, y: -7)
Spacer()
}
}
}
}