Bubble/Threaded/Components/Status/CompactPostView.swift

299 lines
12 KiB
Swift
Raw Normal View History

2023-12-29 11:17:37 +01:00
//Made by Lumaa
import SwiftUI
struct CompactPostView: View {
@Environment(AccountManager.self) private var accountManager: AccountManager
2024-01-06 02:52:20 +01:00
@State var status: Status
@ObservedObject var navigator: Navigator
2023-12-31 03:03:18 +01:00
var pinned: Bool = false
2024-01-06 02:52:20 +01:00
var detailed: Bool = false
var quoted: Bool = false
2023-12-29 11:17:37 +01:00
@State private var preferences: UserPreferences = .defaultPreferences
2023-12-31 03:03:18 +01:00
@State private var initialLike: Bool = false
2024-01-21 15:36:59 +01:00
2023-12-29 11:17:37 +01:00
@State private var isLiked: Bool = false
@State private var isReposted: Bool = false
2024-01-21 15:36:59 +01:00
@State private var isBookmarked: Bool = false
2024-01-03 09:55:18 +01:00
@State private var hasQuote: Bool = false
@State private var quoteStatus: Status? = nil
2023-12-29 11:17:37 +01:00
var body: some View {
VStack {
2024-01-21 15:36:59 +01:00
statusPost(status.reblog ?? status)
2024-01-10 17:45:41 +01:00
.contentShape(Rectangle())
.onTapGesture {
navigator.navigate(to: .post(status: status))
2024-01-06 02:52:20 +01:00
}
2023-12-29 11:17:37 +01:00
2024-01-06 02:52:20 +01:00
if !quoted {
Rectangle()
.fill(Color.gray.opacity(0.2))
.frame(width: .infinity, height: 1)
.padding(.bottom, 3)
}
2023-12-29 11:17:37 +01:00
}
.onAppear {
do {
preferences = try UserPreferences.loadAsCurrent() ?? UserPreferences.defaultPreferences
} catch {
print(error)
}
2024-01-06 02:52:20 +01:00
2023-12-31 03:03:18 +01:00
initialLike = isLiked
2024-01-03 09:55:18 +01:00
}
.task {
2024-01-06 02:52:20 +01:00
await loadEmbeddedStatus(status: status)
if let client = accountManager.getClient() {
if let newStatus: Status = try? await client.get(endpoint: Statuses.status(id: status.id)) {
status = newStatus
}
}
2023-12-29 11:17:37 +01:00
}
}
@ViewBuilder
func statusPost(_ status: AnyStatus) -> some View {
2023-12-29 11:17:37 +01:00
HStack(alignment: .top, spacing: 0) {
// MARK: Profile picture
if status.repliesCount > 0 && preferences.experimental.replySymbol {
VStack {
profilePicture
2023-12-29 11:17:37 +01:00
.onTapGesture {
navigator.navigate(to: .account(acc: status.account))
}
Spacer()
2024-01-02 19:10:53 +01:00
Rectangle()
.fill(Color.gray.opacity(0.3))
.frame(width: 2.5)
.clipShape(.capsule)
.padding([.vertical], 5)
Spacer()
2024-01-02 19:10:53 +01:00
Image(systemName: "person.crop.circle")
.resizable()
.frame(width: 15, height: 15)
.symbolRenderingMode(.monochrome)
.foregroundStyle(Color.gray.opacity(0.3))
.padding(.bottom, 2.5)
2023-12-29 11:17:37 +01:00
}
} else {
2023-12-29 11:17:37 +01:00
profilePicture
.onTapGesture {
navigator.navigate(to: .account(acc: status.account))
2023-12-29 11:17:37 +01:00
}
}
2023-12-29 11:17:37 +01:00
VStack(alignment: .leading) {
// MARK: Status main content
VStack(alignment: .leading, spacing: 10) {
2024-01-10 17:45:41 +01:00
VStack(alignment: .leading, spacing: 2) {
Text(status.account.username)
.font(quoted ? .callout : .body)
.multilineTextAlignment(.leading)
.bold()
.onTapGesture {
navigator.navigate(to: .account(acc: status.account))
}
if status.inReplyToAccountId != nil {
if let user = status.mentions.first(where: { $0.id == status.inReplyToAccountId }) {
Text("status.replied-to.\(user.username)")
.multilineTextAlignment(.leading)
.lineLimit(1)
.font(.caption)
.foregroundStyle(Color(uiColor: UIColor.label).opacity(0.3))
}
2023-12-29 11:17:37 +01:00
}
2024-01-10 17:45:41 +01:00
}
2023-12-29 11:17:37 +01:00
2024-01-02 19:10:53 +01:00
if !status.content.asRawText.isEmpty {
TextEmoji(status.content, emojis: status.emojis, language: status.language)
.multilineTextAlignment(.leading)
2024-01-06 02:52:20 +01:00
.frame(width: quoted ? 250 : 300, alignment: .topLeading)
.lineLimit(quoted ? 3 : nil)
2024-01-02 19:10:53 +01:00
.fixedSize(horizontal: false, vertical: true)
2024-01-06 02:52:20 +01:00
.font(quoted ? .caption : .callout)
2024-01-02 19:10:53 +01:00
}
2024-01-04 23:55:00 +01:00
if status.card != nil && status.mediaAttachments.isEmpty && !hasQuote {
2024-01-02 19:10:53 +01:00
PostCardView(card: status.card!)
}
2024-01-03 09:55:18 +01:00
if !status.mediaAttachments.isEmpty {
if status.mediaAttachments.count > 1 {
2024-01-04 23:37:06 +01:00
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .firstTextBaseline, spacing: 5) {
ForEach(status.mediaAttachments) { attachment in
2024-01-04 23:55:00 +01:00
PostAttachment(attachment: attachment, isFeatured: false)
}
}
}
.scrollClipDisabled()
} else {
PostAttachment(attachment: status.mediaAttachments.first!)
}
}
2024-01-04 23:55:00 +01:00
if hasQuote {
if quoteStatus != nil {
QuotePostView(status: quoteStatus!)
} else {
ProgressView()
.progressViewStyle(.circular)
}
}
2023-12-29 11:17:37 +01:00
}
2024-01-06 02:52:20 +01:00
//MARK: Action buttons
if !quoted {
2024-01-21 15:36:59 +01:00
2024-01-06 02:52:20 +01:00
}
// MARK: Status stats
stats.padding(.top, 5)
}
}
}
2024-01-10 17:45:41 +01:00
var notices: some View {
ZStack {
if pinned {
HStack (alignment:.center, spacing: 5) {
Image(systemName: "pin.fill")
Text("status.pinned")
}
.padding(.leading, 20)
.multilineTextAlignment(.leading)
.lineLimit(1)
.font(.caption)
.foregroundStyle(Color(uiColor: UIColor.label).opacity(0.3))
.padding(.leading, 35)
2024-01-06 02:52:20 +01:00
}
2024-01-10 17:45:41 +01:00
if status.reblog != nil {
HStack (alignment:.center, spacing: 5) {
Image(systemName: "bolt.horizontal")
2024-01-06 02:52:20 +01:00
2024-01-10 17:45:41 +01:00
Text("status.reposted-by.\(status.account.username)")
2024-01-06 02:52:20 +01:00
}
2024-01-10 17:45:41 +01:00
.padding(.leading, 20)
.multilineTextAlignment(.leading)
.lineLimit(1)
.font(.caption)
.foregroundStyle(Color(uiColor: UIColor.label).opacity(0.3))
.padding(.leading, 30)
2023-12-29 11:17:37 +01:00
}
}
}
var profilePicture: some View {
if status.reblog != nil {
OnlineImage(url: status.reblog!.account.avatar, size: 50, useNuke: true)
2023-12-29 11:17:37 +01:00
.frame(width: 40, height: 40)
.padding(.horizontal)
.clipShape(.circle)
} else {
OnlineImage(url: status.account.avatar, size: 50, useNuke: true)
2023-12-29 11:17:37 +01:00
.frame(width: 40, height: 40)
.padding(.horizontal)
.clipShape(.circle)
}
}
var stats: some View {
//MARK: I acknowledge the existance of a count bug here
2023-12-29 11:17:37 +01:00
if status.reblog == nil {
HStack {
if status.repliesCount > 0 {
Text("status.replies-\(status.repliesCount)")
.monospacedDigit()
.foregroundStyle(.gray)
2024-01-06 02:52:20 +01:00
.font(quoted ? .caption : .callout)
2023-12-29 11:17:37 +01:00
}
if status.repliesCount > 0 && (status.favouritesCount > 0 || isLiked) {
Text("")
.foregroundStyle(.gray)
}
if status.favouritesCount > 0 || isLiked {
2024-01-03 09:55:18 +01:00
let likeCount: Int = status.favouritesCount - (initialLike ? 1 : 0)
let incrLike: Int = isLiked ? 1 : 0
Text("status.favourites-\(likeCount + incrLike)")
2023-12-29 11:17:37 +01:00
.monospacedDigit()
.foregroundStyle(.gray)
2024-01-03 09:55:18 +01:00
.contentTransition(.numericText(value: Double(likeCount + incrLike)))
2024-01-06 02:52:20 +01:00
.font(quoted ? .caption : .callout)
2023-12-29 11:17:37 +01:00
.transaction { t in
t.animation = .default
}
}
}
} else {
HStack {
if status.reblog!.repliesCount > 0 {
Text("status.replies-\(status.reblog!.repliesCount)")
.monospacedDigit()
.foregroundStyle(.gray)
2024-01-06 02:52:20 +01:00
.font(quoted ? .caption : .callout)
2023-12-29 11:17:37 +01:00
}
if status.reblog!.repliesCount > 0 && (status.reblog!.favouritesCount > 0 || isLiked) {
Text("")
.foregroundStyle(.gray)
2024-01-06 02:52:20 +01:00
.font(quoted ? .caption : .callout)
2023-12-29 11:17:37 +01:00
}
if status.reblog!.favouritesCount > 0 || isLiked {
2024-01-03 09:55:18 +01:00
let likeCount: Int = status.reblog!.favouritesCount - (initialLike ? 1 : 0)
let incrLike: Int = isLiked ? 1 : 0
Text("status.favourites-\(likeCount + incrLike)")
2023-12-29 11:17:37 +01:00
.monospacedDigit()
.foregroundStyle(.gray)
2024-01-03 09:55:18 +01:00
.contentTransition(.numericText(value: Double(likeCount + incrLike)))
2024-01-06 02:52:20 +01:00
.font(quoted ? .caption : .callout)
2023-12-29 11:17:37 +01:00
.transaction { t in
t.animation = .default
}
}
}
}
}
2024-01-06 02:52:20 +01:00
private func embededStatusURL(_ status: Status) -> URL? {
2024-01-03 09:55:18 +01:00
let content = status.content
if let client = accountManager.getClient() {
if !content.statusesURLs.isEmpty, let url = content.statusesURLs.first, client.hasConnection(with: url) {
return url
}
}
return nil
}
2024-01-06 02:52:20 +01:00
func loadEmbeddedStatus(status: Status) async {
guard let url = embededStatusURL(status), let client = accountManager.getClient() else { hasQuote = false; return }
2024-01-03 09:55:18 +01:00
do {
hasQuote = true
if url.absoluteString.contains(client.server), let id = Int(url.lastPathComponent) {
quoteStatus = try await client.get(endpoint: Statuses.status(id: String(id)))
} else {
let results: SearchResults = try await client.get(endpoint: Search.search(query: url.absoluteString, type: "statuses", offset: 0, following: nil), forceVersion: .v2)
quoteStatus = results.statuses.first
}
} catch {
hasQuote = false
quoteStatus = nil
}
}
2023-12-29 11:17:37 +01:00
}