diff --git a/Packages/Models/Sources/Models/StatusContext.swift b/Packages/Models/Sources/Models/StatusContext.swift new file mode 100644 index 00000000..bad1e00b --- /dev/null +++ b/Packages/Models/Sources/Models/StatusContext.swift @@ -0,0 +1,6 @@ +import Foundation + +public struct StatusContext: Decodable { + public let ancestors: [Status] + public let descendants: [Status] +} diff --git a/Packages/Network/Sources/Network/Endpoint/Statuses.swift b/Packages/Network/Sources/Network/Endpoint/Statuses.swift index 54e7218c..e2eff81e 100644 --- a/Packages/Network/Sources/Network/Endpoint/Statuses.swift +++ b/Packages/Network/Sources/Network/Endpoint/Statuses.swift @@ -1,6 +1,8 @@ import Foundation public enum Statuses: Endpoint { + case status(id: String) + case context(id: String) case favourite(id: String) case unfavourite(id: String) case reblog(id: String) @@ -8,6 +10,10 @@ public enum Statuses: Endpoint { public func path() -> String { switch self { + case .status(let id): + return "statuses/\(id)" + case .context(let id): + return "statuses/\(id)/context" case .favourite(let id): return "statuses/\(id)/favourite" case .unfavourite(let id): diff --git a/Packages/Notifications/Sources/Notifications/NotificationsListView.swift b/Packages/Notifications/Sources/Notifications/NotificationsListView.swift index 51b3f2f0..ffbea64c 100644 --- a/Packages/Notifications/Sources/Notifications/NotificationsListView.swift +++ b/Packages/Notifications/Sources/Notifications/NotificationsListView.swift @@ -24,6 +24,7 @@ public struct NotificationsListView: View { .padding(.top, DS.Constants.layoutPadding) } .task { + viewModel.client = client await viewModel.fetchNotifications() } .refreshable { diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailVIew.swift b/Packages/Status/Sources/Status/Detail/StatusDetailVIew.swift index c21d6ae6..7bd58fdb 100644 --- a/Packages/Status/Sources/Status/Detail/StatusDetailVIew.swift +++ b/Packages/Status/Sources/Status/Detail/StatusDetailVIew.swift @@ -1,13 +1,68 @@ import SwiftUI +import Models +import Shimmer +import Routeur +import Network +import DesignSystem public struct StatusDetailView: View { - private let statusId: String - + @EnvironmentObject private var client: Client + @EnvironmentObject private var routeurPath: RouterPath + @StateObject private var viewModel: StatusDetailViewModel + @State private var isLoaded: Bool = false + public init(statusId: String) { - self.statusId = statusId + _viewModel = StateObject(wrappedValue: .init(statusId: statusId)) } public var body: some View { - Text("Status id \(statusId)") + ScrollViewReader { proxy in + ScrollView { + LazyVStack { + switch viewModel.state { + case .loading: + ForEach(Status.placeholders()) { status in + StatusRowView(viewModel: .init(status: status, isEmbed: false)) + .redacted(reason: .placeholder) + .shimmering() + } + case let.display(status, context): + if !context.ancestors.isEmpty { + ForEach(context.ancestors) { ancestor in + StatusRowView(viewModel: .init(status: ancestor, isEmbed: false)) + Divider() + .padding(.vertical, DS.Constants.dividerPadding) + } + } + StatusRowView(viewModel: .init(status: status, isEmbed: false)) + .id(status.id) + Divider() + .padding(.vertical, DS.Constants.dividerPadding) + if !context.descendants.isEmpty { + ForEach(context.descendants) { descendant in + StatusRowView(viewModel: .init(status: descendant, isEmbed: false)) + Divider() + .padding(.vertical, DS.Constants.dividerPadding) + } + } + + case let .error(error): + Text(error.localizedDescription) + } + } + .padding(.horizontal, DS.Constants.layoutPadding) + } + .task { + guard !isLoaded else { return } + isLoaded = true + viewModel.client = client + await viewModel.fetchStatusDetail() + DispatchQueue.main.async { + proxy.scrollTo(viewModel.statusId, anchor: .center) + } + } + } + .navigationTitle(viewModel.title) + .navigationBarTitleDisplayMode(.inline) } } diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift b/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift new file mode 100644 index 00000000..72965f28 --- /dev/null +++ b/Packages/Status/Sources/Status/Detail/StatusDetailViewModel.swift @@ -0,0 +1,36 @@ +import Foundation +import SwiftUI +import Models +import Network + +@MainActor +class StatusDetailViewModel: ObservableObject { + public let statusId: String + + var client: Client? + + enum State { + case loading, display(status: Status, context: StatusContext), error(error: Error) + } + + @Published var state: State = .loading + @Published var title: String = "" + + init(statusId: String) { + state = .loading + self.statusId = statusId + } + + func fetchStatusDetail() async { + guard let client else { return } + do { + state = .loading + let status: Status = try await client.get(endpoint: Statuses.status(id: statusId)) + let context: StatusContext = try await client.get(endpoint: Statuses.context(id: statusId)) + state = .display(status: status, context: context) + title = "Post from \(status.account.displayName)" + } catch { + state = .error(error: error) + } + } +}