2023-01-17 10:36:01 +00:00
|
|
|
import DesignSystem
|
2022-12-22 09:53:36 +00:00
|
|
|
import Env
|
2023-01-17 10:36:01 +00:00
|
|
|
import Models
|
2022-12-21 17:28:21 +00:00
|
|
|
import Network
|
2023-01-17 10:36:01 +00:00
|
|
|
import Shimmer
|
|
|
|
import SwiftUI
|
2023-02-10 17:21:05 +00:00
|
|
|
import DesignSystem
|
2022-11-29 10:46:02 +00:00
|
|
|
|
|
|
|
public struct StatusDetailView: View {
|
2022-12-29 09:39:34 +00:00
|
|
|
@EnvironmentObject private var theme: Theme
|
2022-12-25 16:39:23 +00:00
|
|
|
@EnvironmentObject private var currentAccount: CurrentAccount
|
|
|
|
@EnvironmentObject private var watcher: StreamWatcher
|
2022-12-21 17:28:21 +00:00
|
|
|
@EnvironmentObject private var client: Client
|
2023-01-17 14:14:50 +00:00
|
|
|
@EnvironmentObject private var routerPath: RouterPath
|
2023-01-12 17:25:37 +00:00
|
|
|
@Environment(\.openURL) private var openURL
|
2022-12-21 17:28:21 +00:00
|
|
|
@StateObject private var viewModel: StatusDetailViewModel
|
|
|
|
@State private var isLoaded: Bool = false
|
2023-02-10 17:21:05 +00:00
|
|
|
@State private var statusHeight: CGFloat = 0
|
|
|
|
|
2022-11-29 10:46:02 +00:00
|
|
|
public init(statusId: String) {
|
2022-12-21 17:28:21 +00:00
|
|
|
_viewModel = StateObject(wrappedValue: .init(statusId: statusId))
|
2022-11-29 10:46:02 +00:00
|
|
|
}
|
2023-02-10 17:21:05 +00:00
|
|
|
|
|
|
|
public init(status: Status) {
|
|
|
|
_viewModel = StateObject(wrappedValue: .init(status: status))
|
|
|
|
}
|
|
|
|
|
2023-01-06 11:14:05 +00:00
|
|
|
public init(remoteStatusURL: URL) {
|
|
|
|
_viewModel = StateObject(wrappedValue: .init(remoteStatusURL: remoteStatusURL))
|
|
|
|
}
|
2023-02-10 17:21:05 +00:00
|
|
|
|
2022-11-29 10:46:02 +00:00
|
|
|
public var body: some View {
|
2023-02-10 17:21:05 +00:00
|
|
|
GeometryReader { reader in
|
|
|
|
ScrollViewReader { proxy in
|
|
|
|
List {
|
|
|
|
if isLoaded {
|
|
|
|
topPaddingView
|
|
|
|
}
|
|
|
|
|
|
|
|
switch viewModel.state {
|
|
|
|
case .loading:
|
|
|
|
loadingDetailView
|
|
|
|
|
|
|
|
case let .display(status, context):
|
|
|
|
if !context.ancestors.isEmpty {
|
|
|
|
ForEach(context.ancestors) { ancestor in
|
|
|
|
StatusRowView(viewModel: .init(status: ancestor, isCompact: false))
|
2023-01-07 17:01:06 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-10 17:21:05 +00:00
|
|
|
|
|
|
|
makeCurrentStatusView(status: status)
|
|
|
|
|
|
|
|
if !context.descendants.isEmpty {
|
|
|
|
ForEach(context.descendants) { descendant in
|
|
|
|
StatusRowView(viewModel: .init(status: descendant, isCompact: false))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isLoaded {
|
|
|
|
loadingContextView
|
|
|
|
}
|
|
|
|
|
|
|
|
Rectangle()
|
|
|
|
.foregroundColor(theme.secondaryBackgroundColor)
|
|
|
|
.frame(minHeight: reader.frame(in: .local).size.height - statusHeight)
|
|
|
|
.listRowSeparator(.hidden)
|
|
|
|
.listRowBackground(theme.secondaryBackgroundColor)
|
|
|
|
.listRowInsets(.init())
|
|
|
|
|
|
|
|
case .error:
|
|
|
|
errorView
|
2022-12-21 17:28:21 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-10 17:21:05 +00:00
|
|
|
.environment(\.defaultMinListRowHeight, 1)
|
|
|
|
.listStyle(.plain)
|
|
|
|
.scrollContentBackground(.hidden)
|
2023-01-29 07:14:08 +00:00
|
|
|
.background(theme.primaryBackgroundColor)
|
2023-02-10 17:21:05 +00:00
|
|
|
.onChange(of: viewModel.scrollToId, perform: { scrollToId in
|
|
|
|
if let scrollToId {
|
|
|
|
viewModel.scrollToId = nil
|
|
|
|
proxy.scrollTo(scrollToId, anchor: .top)
|
2023-01-12 17:25:37 +00:00
|
|
|
}
|
2023-02-10 17:21:05 +00:00
|
|
|
})
|
|
|
|
.task {
|
|
|
|
guard !isLoaded else { return }
|
|
|
|
viewModel.client = client
|
|
|
|
let result = await viewModel.fetch()
|
|
|
|
isLoaded = true
|
|
|
|
|
|
|
|
if !result {
|
|
|
|
if let url = viewModel.remoteStatusURL {
|
|
|
|
openURL(url)
|
|
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
_ = routerPath.path.popLast()
|
|
|
|
}
|
2023-01-12 17:25:37 +00:00
|
|
|
}
|
2023-01-06 11:14:05 +00:00
|
|
|
}
|
2022-12-21 17:28:21 +00:00
|
|
|
}
|
2022-12-25 16:39:23 +00:00
|
|
|
.onChange(of: watcher.latestEvent?.id) { _ in
|
|
|
|
guard let lastEvent = watcher.latestEvent else { return }
|
|
|
|
viewModel.handleEvent(event: lastEvent, currentAccount: currentAccount.account)
|
|
|
|
}
|
2022-12-21 17:28:21 +00:00
|
|
|
}
|
|
|
|
.navigationTitle(viewModel.title)
|
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
2022-11-29 10:46:02 +00:00
|
|
|
}
|
2023-02-10 17:21:05 +00:00
|
|
|
|
|
|
|
private func makeCurrentStatusView(status: Status) -> some View {
|
|
|
|
StatusRowView(viewModel: .init(status: status,
|
|
|
|
isCompact: false,
|
|
|
|
isFocused: true))
|
|
|
|
.overlay {
|
|
|
|
GeometryReader { reader in
|
|
|
|
VStack{}
|
|
|
|
.onAppear {
|
|
|
|
statusHeight = reader.size.height
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.id(status.id)
|
|
|
|
}
|
|
|
|
|
|
|
|
private var errorView: some View {
|
|
|
|
ErrorView(title: "status.error.title",
|
|
|
|
message: "status.error.message",
|
|
|
|
buttonTitle: "action.retry") {
|
|
|
|
Task {
|
|
|
|
await viewModel.fetch()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.listRowBackground(theme.primaryBackgroundColor)
|
|
|
|
.listRowSeparator(.hidden)
|
|
|
|
}
|
|
|
|
|
|
|
|
private var loadingDetailView: some View {
|
|
|
|
ForEach(Status.placeholders()) { status in
|
|
|
|
StatusRowView(viewModel: .init(status: status, isCompact: false))
|
|
|
|
.redacted(reason: .placeholder)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private var loadingContextView: some View {
|
|
|
|
HStack {
|
|
|
|
Spacer()
|
|
|
|
ProgressView()
|
|
|
|
Spacer()
|
|
|
|
}
|
|
|
|
.frame(height: 50)
|
|
|
|
.listRowSeparator(.hidden)
|
|
|
|
.listRowBackground(theme.secondaryBackgroundColor)
|
|
|
|
.listRowInsets(.init())
|
|
|
|
}
|
|
|
|
|
|
|
|
private var topPaddingView: some View {
|
|
|
|
HStack { EmptyView() }
|
|
|
|
.listRowBackground(theme.primaryBackgroundColor)
|
|
|
|
.listRowSeparator(.hidden)
|
|
|
|
.listRowInsets(.init())
|
|
|
|
.frame(height: .layoutPadding)
|
|
|
|
}
|
2022-11-29 10:46:02 +00:00
|
|
|
}
|