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

147 lines
4.4 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
2023-09-17 05:53:32 +00:00
@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 {
2023-09-17 18:13:03 +00:00
case loading, display(statuses: [Status]), error(error: Error)
2022-12-21 17:28:21 +00:00
}
2023-01-17 10:36:01 +00:00
2023-09-17 05:53:32 +00:00
var state: State = .loading
var title: LocalizedStringKey = ""
var scrollToId: String?
2023-09-17 18:32:57 +00:00
@ObservationIgnored
2023-09-17 12:43:01 +00:00
var isReplyToPreviousCache: [String: Bool] = [:]
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) {
2023-09-17 18:13:03 +00:00
state = .display(statuses: [status])
title = "status.post-from-\(status.account.displayNameWithoutEmojis)"
statusId = status.id
remoteStatusURL = nil
}
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)
2023-09-17 12:43:01 +00:00
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 {
2023-09-17 18:13:03 +00:00
state = .display(statuses: statuses)
2023-02-10 19:57:09 +00:00
}
} else {
2023-09-17 18:13:03 +00:00
state = .display(statuses: statuses)
2023-02-10 19:57:09 +00:00
scrollToId = statusId
}
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
2023-09-17 12:43:01 +00:00
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
}
}
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
}
}
}
2022-12-21 17:28:21 +00:00
}