mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-05-19 00:38:10 +00:00
147 lines
4.4 KiB
Swift
147 lines
4.4 KiB
Swift
import Env
|
|
import Foundation
|
|
import Models
|
|
import Network
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
@Observable class StatusDetailViewModel {
|
|
public var statusId: String?
|
|
public var remoteStatusURL: URL?
|
|
|
|
var client: Client?
|
|
var routerPath: RouterPath?
|
|
|
|
enum State {
|
|
case loading, display(statuses: [Status]), error(error: Error)
|
|
}
|
|
|
|
var state: State = .loading
|
|
var title: LocalizedStringKey = ""
|
|
var scrollToId: String?
|
|
|
|
@ObservationIgnored
|
|
var isReplyToPreviousCache: [String: Bool] = [:]
|
|
|
|
init(statusId: String) {
|
|
state = .loading
|
|
self.statusId = statusId
|
|
remoteStatusURL = nil
|
|
}
|
|
|
|
init(status: Status) {
|
|
state = .display(statuses: [status])
|
|
title = "status.post-from-\(status.account.displayNameWithoutEmojis)"
|
|
statusId = status.id
|
|
remoteStatusURL = nil
|
|
}
|
|
|
|
init(remoteStatusURL: URL) {
|
|
state = .loading
|
|
self.remoteStatusURL = remoteStatusURL
|
|
statusId = nil
|
|
}
|
|
|
|
func fetch() async -> Bool {
|
|
if statusId != nil {
|
|
await fetchStatusDetail(animate: false)
|
|
return true
|
|
} else if remoteStatusURL != nil {
|
|
return await fetchRemoteStatus()
|
|
}
|
|
return false
|
|
}
|
|
|
|
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,
|
|
type: "statuses",
|
|
offset: nil,
|
|
following: nil),
|
|
forceVersion: .v2)
|
|
if let statusId = results?.statuses.first?.id {
|
|
self.statusId = statusId
|
|
await fetchStatusDetail(animate: false)
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
struct ContextData {
|
|
let status: Status
|
|
let context: StatusContext
|
|
}
|
|
|
|
private func fetchStatusDetail(animate: Bool) async {
|
|
guard let client, let statusId else { return }
|
|
do {
|
|
let data = try await fetchContextData(client: client, statusId: statusId)
|
|
title = "status.post-from-\(data.status.account.displayNameWithoutEmojis)"
|
|
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)
|
|
|
|
if animate {
|
|
withAnimation {
|
|
state = .display(statuses: statuses)
|
|
}
|
|
} else {
|
|
state = .display(statuses: statuses)
|
|
scrollToId = statusId
|
|
}
|
|
} catch {
|
|
if let error = error as? ServerError, error.httpCode == 404 {
|
|
_ = routerPath?.path.popLast()
|
|
} else {
|
|
state = .error(error: error)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
private func cacheReplyTopPrevious(statuses: [Status]) {
|
|
isReplyToPreviousCache = [:]
|
|
for status in statuses {
|
|
var isReplyToPrevious: Bool = false
|
|
if let index = statuses.firstIndex(where: { $0.id == status.id }),
|
|
index > 0,
|
|
statuses[index - 1].id == status.inReplyToId
|
|
{
|
|
if index == 1, statuses.count > 2 {
|
|
let nextStatus = statuses[2]
|
|
isReplyToPrevious = nextStatus.inReplyToId == status.id
|
|
} else if statuses.count == 2 {
|
|
isReplyToPrevious = false
|
|
} else {
|
|
isReplyToPrevious = true
|
|
}
|
|
}
|
|
isReplyToPreviousCache[status.id] = isReplyToPrevious
|
|
}
|
|
}
|
|
|
|
func handleEvent(event: any StreamEvent, currentAccount: Account?) {
|
|
Task {
|
|
if let event = event as? StreamEventUpdate,
|
|
event.status.account.id == currentAccount?.id
|
|
{
|
|
await fetchStatusDetail(animate: true)
|
|
} else if let event = event as? StreamEventStatusUpdate,
|
|
event.status.account.id == currentAccount?.id
|
|
{
|
|
await fetchStatusDetail(animate: true)
|
|
} else if event is StreamEventDelete {
|
|
await fetchStatusDetail(animate: true)
|
|
}
|
|
}
|
|
}
|
|
}
|