IceCubesApp/Packages/StatusKit/Sources/StatusKit/Detail/StatusDetailViewModel.swift

152 lines
4.6 KiB
Swift
Raw Normal View History

2023-03-13 12:38:28 +00:00
import Env
2022-12-21 17:28:21 +00:00
import Foundation
import Models
import Network
2023-01-17 10:36:01 +00:00
import SwiftUI
2022-12-21 17:28:21 +00:00
@MainActor
@Observable class StatusDetailViewModel {
public var statusId: String?
public var remoteStatusURL: URL?
2023-01-17 10:36:01 +00:00
2022-12-21 17:28:21 +00:00
var client: Client?
2023-03-13 17:47:24 +00:00
var routerPath: RouterPath?
2023-01-17 10:36:01 +00:00
2022-12-21 17:28:21 +00:00
enum State {
case loading, display(statuses: [Status]), error(error: Error)
2022-12-21 17:28:21 +00:00
}
2023-01-17 10:36:01 +00:00
var state: State = .loading
var title: LocalizedStringKey = ""
var scrollToId: String?
2023-09-18 19:03:52 +00:00
@ObservationIgnored
var indentationLevelPreviousCache: [String: UInt] = [:]
2023-01-22 05:38:30 +00:00
2022-12-21 17:28:21 +00:00
init(statusId: String) {
state = .loading
self.statusId = statusId
2023-01-17 10:36:01 +00:00
remoteStatusURL = nil
2022-12-21 17:28:21 +00:00
}
2023-02-12 15:29:41 +00:00
init(status: Status) {
state = .display(statuses: [status])
title = "status.post-from-\(status.account.displayNameWithoutEmojis)"
statusId = status.id
remoteStatusURL = nil
2023-09-20 19:19:02 +00:00
if status.inReplyToId != nil {
indentationLevelPreviousCache[status.id] = 1
2023-09-20 19:19:02 +00:00
}
}
2023-01-17 10:36:01 +00:00
init(remoteStatusURL: URL) {
state = .loading
self.remoteStatusURL = remoteStatusURL
2023-01-17 10:36:01 +00:00
statusId = nil
}
2023-01-17 10:36:01 +00:00
func fetch() async -> Bool {
if statusId != nil {
2023-02-10 19:57:09 +00:00
await fetchStatusDetail(animate: false)
return true
} else if remoteStatusURL != nil {
return await fetchRemoteStatus()
}
return false
}
2023-01-17 10:36:01 +00:00
private func fetchRemoteStatus() async -> Bool {
guard let client, let remoteStatusURL else { return false }
let results: SearchResults? = try? await client.get(endpoint: Search.search(query: remoteStatusURL.absoluteString,
2023-01-17 10:36:01 +00:00
type: "statuses",
offset: nil,
following: nil),
forceVersion: .v2)
if let statusId = results?.statuses.first?.id {
self.statusId = statusId
2023-02-10 19:57:09 +00:00
await fetchStatusDetail(animate: false)
return true
} else {
return false
}
}
2023-01-17 10:36:01 +00:00
2023-01-17 18:46:04 +00:00
struct ContextData {
let status: Status
let context: StatusContext
}
2023-01-22 05:38:30 +00:00
2023-02-10 19:57:09 +00:00
private func fetchStatusDetail(animate: Bool) async {
guard let client, let statusId else { return }
2022-12-21 17:28:21 +00:00
do {
2023-01-17 18:46:04 +00:00
let data = try await fetchContextData(client: client, statusId: statusId)
title = "status.post-from-\(data.status.account.displayNameWithoutEmojis)"
2023-02-16 16:07:52 +00:00
var statuses = data.context.ancestors
statuses.append(data.status)
statuses.append(contentsOf: data.context.descendants)
cacheReplyTopPrevious(statuses: statuses)
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
2023-03-13 12:38:28 +00:00
2023-02-10 19:57:09 +00:00
if animate {
withAnimation {
state = .display(statuses: statuses)
2023-02-10 19:57:09 +00:00
}
} else {
state = .display(statuses: statuses)
2024-01-01 13:16:42 +00:00
scrollToId = data.status.id + (data.status.editedAt?.asDate.description ?? "")
2023-02-10 19:57:09 +00:00
}
2022-12-21 17:28:21 +00:00
} catch {
2023-03-13 17:47:24 +00:00
if let error = error as? ServerError, error.httpCode == 404 {
_ = routerPath?.path.popLast()
} else {
state = .error(error: error)
}
2022-12-21 17:28:21 +00:00
}
}
2023-01-22 05:38:30 +00:00
2023-01-17 18:46:04 +00:00
private func fetchContextData(client: Client, statusId: String) async throws -> ContextData {
async let status: Status = client.get(endpoint: Statuses.status(id: statusId))
async let context: StatusContext = client.get(endpoint: Statuses.context(id: statusId))
return try await .init(status: status, context: context)
}
2023-01-17 10:36:01 +00:00
private func cacheReplyTopPrevious(statuses: [Status]) {
indentationLevelPreviousCache = [:]
for status in statuses {
if let inReplyToId = status.inReplyToId,
2023-12-18 07:22:59 +00:00
let prevIndent = indentationLevelPreviousCache[inReplyToId]
{
indentationLevelPreviousCache[status.id] = prevIndent + 1
} else {
indentationLevelPreviousCache[status.id] = 0
}
}
}
2023-09-18 19:03:52 +00:00
2022-12-25 16:39:23 +00:00
func handleEvent(event: any StreamEvent, currentAccount: Account?) {
2023-03-15 13:55:45 +00:00
Task {
if let event = event as? StreamEventUpdate,
2023-03-19 15:28:06 +00:00
event.status.account.id == currentAccount?.id
{
2023-02-10 19:57:09 +00:00
await fetchStatusDetail(animate: true)
2023-03-15 13:55:45 +00:00
} else if let event = event as? StreamEventStatusUpdate,
2023-03-19 15:28:06 +00:00
event.status.account.id == currentAccount?.id
{
2023-03-15 13:55:45 +00:00
await fetchStatusDetail(animate: true)
} else if event is StreamEventDelete {
2023-02-10 19:57:09 +00:00
await fetchStatusDetail(animate: true)
2022-12-25 16:39:23 +00:00
}
}
}
2023-12-18 07:22:59 +00:00
func getIndentationLevel(id: String, maxIndent: UInt) -> (indentationLevel: UInt, extraInset: Double) {
let level = min(indentationLevelPreviousCache[id] ?? 0, maxIndent)
2023-12-18 07:22:59 +00:00
let barSize = Double(level) * 2
let spaceBetween = (Double(level) - 1) * 3
let size = barSize + spaceBetween + 8
2023-12-18 07:22:59 +00:00
return (level, size)
}
2022-12-21 17:28:21 +00:00
}