2023-12-29 11:17:37 +01:00
|
|
|
//Made by Lumaa
|
|
|
|
|
|
|
|
import SwiftUI
|
|
|
|
|
|
|
|
struct CompactPostView: View {
|
2024-01-02 14:23:36 +01:00
|
|
|
@Environment(AccountManager.self) private var accountManager: AccountManager
|
2023-12-29 11:17:37 +01:00
|
|
|
var status: Status
|
2024-01-02 14:23:36 +01:00
|
|
|
@ObservedObject var navigator: Navigator
|
2023-12-31 03:03:18 +01:00
|
|
|
var pinned: Bool = false
|
2023-12-29 11:17:37 +01:00
|
|
|
|
2023-12-31 03:03:18 +01:00
|
|
|
@State private var initialLike: Bool = false
|
2023-12-29 11:17:37 +01:00
|
|
|
@State private var isLiked: Bool = false
|
|
|
|
@State private var isReposted: Bool = false
|
|
|
|
|
|
|
|
var body: some View {
|
|
|
|
VStack {
|
2024-01-02 14:23:36 +01:00
|
|
|
VStack(alignment: .leading) {
|
|
|
|
if pinned {
|
|
|
|
pinnedNotice
|
|
|
|
.padding(.leading, 35)
|
|
|
|
}
|
|
|
|
|
|
|
|
if status.reblog != nil {
|
2023-12-29 11:17:37 +01:00
|
|
|
repostNotice
|
2023-12-31 03:03:18 +01:00
|
|
|
.padding(.leading, 30)
|
|
|
|
}
|
2024-01-02 14:23:36 +01:00
|
|
|
|
|
|
|
statusPost(status.reblog ?? status)
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Rectangle()
|
|
|
|
.fill(Color.gray.opacity(0.2))
|
|
|
|
.frame(width: .infinity, height: 1)
|
|
|
|
.padding(.bottom, 3)
|
|
|
|
}
|
|
|
|
.onAppear {
|
|
|
|
isLiked = status.reblog != nil ? status.reblog!.favourited ?? false : status.favourited ?? false
|
2023-12-31 03:03:18 +01:00
|
|
|
initialLike = isLiked
|
2023-12-29 11:17:37 +01:00
|
|
|
isReposted = status.reblog != nil ? status.reblog!.reblogged ?? false : status.reblogged ?? false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func likePost() async throws {
|
2024-01-02 14:23:36 +01:00
|
|
|
if let client = accountManager.getClient() {
|
|
|
|
guard client.isAuth else { fatalError("Client is not authenticated") }
|
|
|
|
let statusId: String = status.reblog != nil ? status.reblog!.id : status.id
|
|
|
|
let endpoint = !isLiked ? Statuses.favorite(id: statusId) : Statuses.unfavorite(id: statusId)
|
|
|
|
|
|
|
|
isLiked = !isLiked
|
|
|
|
let newStatus: Status = try await client.post(endpoint: endpoint)
|
|
|
|
if isLiked != newStatus.favourited {
|
|
|
|
isLiked = newStatus.favourited ?? !isLiked
|
|
|
|
}
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func repostPost() async throws {
|
2024-01-02 14:23:36 +01:00
|
|
|
if let client = accountManager.getClient() {
|
|
|
|
guard client.isAuth else { fatalError("Client is not authenticated") }
|
|
|
|
let statusId: String = status.reblog != nil ? status.reblog!.id : status.id
|
|
|
|
let endpoint = !isReposted ? Statuses.reblog(id: statusId) : Statuses.unreblog(id: statusId)
|
|
|
|
|
|
|
|
isReposted = !isReposted
|
|
|
|
let newStatus: Status = try await client.post(endpoint: endpoint)
|
|
|
|
if isReposted != newStatus.reblogged {
|
|
|
|
isReposted = newStatus.reblogged ?? !isReposted
|
|
|
|
}
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-02 14:23:36 +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
|
2024-01-02 14:23:36 +01:00
|
|
|
if status.repliesCount > 0 {
|
|
|
|
VStack {
|
|
|
|
profilePicture
|
2023-12-29 11:17:37 +01:00
|
|
|
.onTapGesture {
|
|
|
|
navigator.navigate(to: .account(acc: status.account))
|
|
|
|
}
|
|
|
|
|
2024-01-02 14:23:36 +01:00
|
|
|
Spacer()
|
|
|
|
|
|
|
|
Rectangle()
|
|
|
|
.fill(Color.gray.opacity(0.3))
|
|
|
|
.frame(width: 2.5)
|
|
|
|
.clipShape(.capsule)
|
|
|
|
.padding([.vertical], 5)
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
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
|
|
|
}
|
2024-01-02 14:23:36 +01:00
|
|
|
} else {
|
2023-12-29 11:17:37 +01:00
|
|
|
profilePicture
|
|
|
|
.onTapGesture {
|
2024-01-02 14:23:36 +01:00
|
|
|
navigator.navigate(to: .account(acc: status.account))
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
2024-01-02 14:23:36 +01:00
|
|
|
}
|
2023-12-29 11:17:37 +01:00
|
|
|
|
|
|
|
VStack(alignment: .leading) {
|
|
|
|
// MARK: Status main content
|
|
|
|
VStack(alignment: .leading, spacing: 10) {
|
2024-01-02 14:23:36 +01:00
|
|
|
Text(status.account.username)
|
2023-12-29 11:17:37 +01:00
|
|
|
.multilineTextAlignment(.leading)
|
|
|
|
.bold()
|
|
|
|
.onTapGesture {
|
2024-01-02 14:23:36 +01:00
|
|
|
navigator.navigate(to: .account(acc: status.account))
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
|
2024-01-02 14:23:36 +01:00
|
|
|
TextEmoji(status.content, emojis: status.emojis, language: status.language)
|
|
|
|
.multilineTextAlignment(.leading)
|
|
|
|
.frame(width: 300, alignment: .topLeading)
|
|
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
.font(.callout)
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//MARK: Action buttons
|
|
|
|
HStack(spacing: 13) {
|
|
|
|
asyncActionButton(isLiked ? "heart.fill" : "heart") {
|
|
|
|
do {
|
|
|
|
try await likePost()
|
2023-12-31 03:03:18 +01:00
|
|
|
HapticManager.playHaptics(haptics: Haptic.tap)
|
2023-12-29 11:17:37 +01:00
|
|
|
} catch {
|
|
|
|
HapticManager.playHaptics(haptics: Haptic.error)
|
|
|
|
print("Error: \(error.localizedDescription)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
actionButton("bubble.right") {
|
|
|
|
print("reply")
|
2024-01-02 14:23:36 +01:00
|
|
|
navigator.presentedSheet = .post()
|
2023-12-29 11:17:37 +01:00
|
|
|
}
|
|
|
|
asyncActionButton(isReposted ? "bolt.horizontal.fill" : "bolt.horizontal") {
|
|
|
|
do {
|
|
|
|
try await repostPost()
|
2023-12-31 03:03:18 +01:00
|
|
|
HapticManager.playHaptics(haptics: Haptic.tap)
|
2023-12-29 11:17:37 +01:00
|
|
|
} catch {
|
|
|
|
HapticManager.playHaptics(haptics: Haptic.error)
|
|
|
|
print("Error: \(error.localizedDescription)")
|
|
|
|
}
|
|
|
|
}
|
2024-01-02 14:23:36 +01:00
|
|
|
ShareLink(item: URL(string: status.url ?? "https://joinmastodon.org/")!) {
|
2023-12-29 11:17:37 +01:00
|
|
|
Image(systemName: "square.and.arrow.up")
|
|
|
|
.font(.title2)
|
|
|
|
}
|
|
|
|
.tint(Color(uiColor: UIColor.label))
|
|
|
|
}
|
|
|
|
.padding(.top)
|
|
|
|
|
|
|
|
// MARK: Status stats
|
|
|
|
stats.padding(.top, 5)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-31 03:03:18 +01:00
|
|
|
var pinnedNotice: some View {
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2023-12-29 11:17:37 +01:00
|
|
|
var repostNotice: some View {
|
|
|
|
HStack (alignment:.center, spacing: 5) {
|
|
|
|
Image(systemName: "bolt.horizontal")
|
|
|
|
|
|
|
|
Text("status.reposted-by.\(status.account.username)")
|
|
|
|
}
|
2023-12-31 03:03:18 +01:00
|
|
|
.padding(.leading, 20)
|
2023-12-29 11:17:37 +01:00
|
|
|
.multilineTextAlignment(.leading)
|
|
|
|
.lineLimit(1)
|
|
|
|
.font(.caption)
|
|
|
|
.foregroundStyle(Color(uiColor: UIColor.label).opacity(0.3))
|
|
|
|
}
|
|
|
|
|
|
|
|
var profilePicture: some View {
|
|
|
|
if status.reblog != nil {
|
2024-01-02 14:23:36 +01:00
|
|
|
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 {
|
2024-01-02 14:23:36 +01:00
|
|
|
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 {
|
2023-12-30 21:12:20 +01:00
|
|
|
//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)
|
|
|
|
}
|
|
|
|
|
|
|
|
if status.repliesCount > 0 && (status.favouritesCount > 0 || isLiked) {
|
|
|
|
Text("•")
|
|
|
|
.foregroundStyle(.gray)
|
|
|
|
}
|
|
|
|
|
|
|
|
if status.favouritesCount > 0 || isLiked {
|
2024-01-02 14:23:36 +01:00
|
|
|
let i: Int = status.favouritesCount
|
|
|
|
let favsCount: Int = i - (initialLike ? 1 : 0) + (isLiked ? 1 : 0)
|
|
|
|
Text("status.favourites-\(favsCount)")
|
2023-12-29 11:17:37 +01:00
|
|
|
.monospacedDigit()
|
|
|
|
.foregroundStyle(.gray)
|
2024-01-02 14:23:36 +01:00
|
|
|
.contentTransition(.numericText(value: Double(favsCount)))
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
if status.reblog!.repliesCount > 0 && (status.reblog!.favouritesCount > 0 || isLiked) {
|
|
|
|
Text("•")
|
|
|
|
.foregroundStyle(.gray)
|
|
|
|
}
|
|
|
|
|
|
|
|
if status.reblog!.favouritesCount > 0 || isLiked {
|
2024-01-02 14:23:36 +01:00
|
|
|
let favsCount: Int = (status.favouritesCount - (initialLike ? 1 : 0)) + (isLiked ? 1 : 0)
|
|
|
|
Text("status.favourites-\(favsCount)")
|
2023-12-29 11:17:37 +01:00
|
|
|
.monospacedDigit()
|
|
|
|
.foregroundStyle(.gray)
|
2024-01-02 14:23:36 +01:00
|
|
|
.contentTransition(.numericText(value: Double(favsCount)))
|
2023-12-29 11:17:37 +01:00
|
|
|
.transaction { t in
|
|
|
|
t.animation = .default
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
func actionButton(_ image: String, action: @escaping () -> Void) -> some View {
|
|
|
|
Button {
|
|
|
|
action()
|
|
|
|
} label: {
|
|
|
|
Image(systemName: image)
|
|
|
|
.font(.title2)
|
|
|
|
}
|
|
|
|
.tint(Color(uiColor: UIColor.label))
|
|
|
|
}
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
func asyncActionButton(_ image: String, action: @escaping () async -> Void) -> some View {
|
|
|
|
Button {
|
|
|
|
Task {
|
|
|
|
await action()
|
|
|
|
}
|
|
|
|
} label: {
|
|
|
|
Image(systemName: image)
|
|
|
|
.font(.title2)
|
|
|
|
}
|
|
|
|
.tint(Color(uiColor: UIColor.label))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#Preview {
|
|
|
|
ScrollView {
|
|
|
|
VStack {
|
|
|
|
ForEach(Status.placeholders()) { status in
|
|
|
|
CompactPostView(status: status, navigator: Navigator())
|
|
|
|
.environment(Client.init(server: AppInfo.defaultServer))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|