MobileMkch-iOS/MobileMkch/ThreadDetailView.swift
Lain Iwakura 8cab7bac36
Some checks failed
Build IPA / build (push) Has been cancelled
add pics support
2025-08-08 15:45:20 +03:00

241 lines
8.5 KiB
Swift

import SwiftUI
struct ThreadDetailView: View {
let board: Board
let thread: Thread
@EnvironmentObject var settings: Settings
@EnvironmentObject var apiClient: APIClient
@EnvironmentObject var networkMonitor: NetworkMonitor
@State private var threadDetail: ThreadDetail?
@State private var comments: [Comment] = []
@State private var isLoading = false
@State private var errorMessage: String?
@State private var showingAddComment = false
@State private var activityOn = false
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
if networkMonitor.offlineEffective {
HStack(spacing: 8) {
Image(systemName: "wifi.slash")
.foregroundColor(.orange)
Text("Оффлайн режим. Показаны сохранённые данные")
.foregroundColor(.secondary)
.font(.caption)
}
}
if isLoading {
VStack {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
Text("Загрузка треда...")
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
} else if let error = errorMessage {
VStack(alignment: .leading, spacing: 8) {
Text("Ошибка загрузки")
.font(.headline)
.foregroundColor(.red)
Text(error)
.font(.body)
.foregroundColor(.secondary)
Button("Повторить") {
loadThreadDetail()
}
.buttonStyle(.bordered)
}
.padding()
} else if let detail = threadDetail {
ThreadContentView(thread: detail, showFiles: settings.showFiles)
Divider()
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Комментарии (\(comments.count))")
.font(.headline)
Spacer()
Button("Добавить") {
showingAddComment = true
}
.buttonStyle(.bordered)
}
if comments.isEmpty {
Text("Комментариев пока нет")
.foregroundColor(.secondary)
.italic()
} else {
LazyVStack(alignment: .leading, spacing: 16) {
ForEach(comments) { comment in
CommentView(comment: comment, showFiles: settings.showFiles)
}
}
}
}
}
}
.padding()
}
.refreshable {
loadThreadDetail()
}
.navigationTitle("#\(thread.id)")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Обновить") { loadThreadDetail() }
}
}
.sheet(isPresented: $showingAddComment) {
AddCommentView(boardCode: board.code, threadId: thread.id)
.environmentObject(settings)
.environmentObject(apiClient)
}
.onAppear {
if threadDetail == nil {
loadThreadDetail()
}
}
}
private func loadThreadDetail() {
isLoading = true
errorMessage = nil
apiClient.getFullThread(boardCode: board.code, threadId: thread.id) { result in
DispatchQueue.main.async {
self.isLoading = false
switch result {
case .success(let (detail, loadedComments)):
self.threadDetail = detail
self.comments = loadedComments
if #available(iOS 16.1, *), settings.liveActivityEnabled, activityOn {
LiveActivityManager.shared.update(threadId: thread.id, comments: loadedComments, settings: settings)
}
case .failure(let error):
self.errorMessage = error.localizedDescription
}
}
}
}
}
struct ThreadContentView: View {
let thread: ThreadDetail
let showFiles: Bool
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(thread.title)
.font(.title2)
.fontWeight(.bold)
HStack {
Text(thread.creationDate, style: .date)
.font(.caption)
.foregroundColor(.secondary)
Spacer()
}
if showFiles && !thread.files.isEmpty {
FilesView(files: thread.files)
}
if !thread.text.isEmpty {
Text(thread.text)
.font(.body)
}
}
}
}
struct CommentView: View {
let comment: Comment
let showFiles: Bool
@EnvironmentObject var settings: Settings
var body: some View {
if settings.compactMode {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text("ID: \(comment.id)")
.font(.caption2)
.foregroundColor(.secondary)
Spacer()
Text(comment.creationDate, style: .date)
.font(.caption2)
.foregroundColor(.secondary)
}
if !comment.text.isEmpty {
Text(comment.formattedText)
.font(.caption)
.lineLimit(2)
}
if showFiles && !comment.files.isEmpty {
HStack {
Image(systemName: "paperclip")
.foregroundColor(.blue)
.font(.caption2)
Text("\(comment.files.count)")
.font(.caption2)
.foregroundColor(.secondary)
Spacer()
}
}
}
.padding(.horizontal, 8)
.padding(.vertical, 6)
.background(Color(.systemGray6))
.cornerRadius(6)
} else {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("ID: \(comment.id)")
.font(.caption)
.foregroundColor(.secondary)
Spacer()
Text(comment.creationDate, style: .date)
.font(.caption)
.foregroundColor(.secondary)
}
if showFiles && !comment.files.isEmpty {
FilesView(files: comment.files)
}
if !comment.text.isEmpty {
Text(comment.formattedText)
.font(.body)
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(8)
}
}
}
#Preview {
NavigationView {
ThreadDetailView(
board: Board(code: "test", description: "Test board"),
thread: Thread(id: 1, title: "Test Thread", text: "Test content", creation: "2023-01-01T00:00:00Z", board: "test", rating: nil, pinned: nil, files: [])
)
.environmentObject(Settings())
.environmentObject(APIClient())
}
}