MobileMkch-iOS/MobileMkch/CreateThreadView.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
10 KiB
Swift

import SwiftUI
import PhotosUI
struct CreateThreadView: View {
let boardCode: String
@EnvironmentObject var settings: Settings
@EnvironmentObject var apiClient: APIClient
@Environment(\.dismiss) private var dismiss
@State private var title = ""
@State private var text = ""
@State private var isLoading = false
@State private var errorMessage: String?
@State private var showingSuccess = false
@FocusState private var titleFocused: Bool
@FocusState private var textFocused: Bool
@State private var pickedImages: [UIImage] = []
@State private var showPicker: Bool = false
var body: some View {
NavigationView {
VStack(spacing: 0) {
ScrollView {
VStack(spacing: 20) {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Заголовок")
.font(.headline)
.foregroundColor(.primary)
Spacer()
Text("\(title.count)")
.font(.caption)
.foregroundColor(.secondary)
}
TextField("Введите заголовок треда", text: $title)
.focused($titleFocused)
.padding(16)
.background(Color(.secondarySystemBackground))
.cornerRadius(12)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(titleFocused ? Color.accentColor : Color.clear, lineWidth: 2)
)
}
VStack(alignment: .leading, spacing: 8) {
Text("Фото")
.font(.headline)
.foregroundColor(.primary)
Button {
showPicker = true
} label: {
HStack {
Image(systemName: "photo.on.rectangle")
Text("Выбрать фото")
Spacer()
}
.padding(12)
.background(Color(.secondarySystemBackground))
.cornerRadius(12)
}
if !pickedImages.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(Array(pickedImages.enumerated()), id: \.offset) { _, img in
Image(uiImage: img)
.resizable()
.scaledToFill()
.frame(width: 64, height: 64)
.clipped()
.cornerRadius(8)
}
}
}
}
}
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Содержание")
.font(.headline)
.foregroundColor(.primary)
Spacer()
Text("\(text.count)")
.font(.caption)
.foregroundColor(.secondary)
}
TextEditor(text: $text)
.focused($textFocused)
.frame(minHeight: 140)
.padding(12)
.background(Color(.secondarySystemBackground))
.cornerRadius(12)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(textFocused ? Color.accentColor : Color.clear, lineWidth: 2)
)
.overlay(
Group {
if text.isEmpty {
HStack {
VStack {
Text("Напишите содержание треда...")
.foregroundColor(.secondary)
.padding(.top, 20)
.padding(.leading, 16)
Spacer()
}
Spacer()
}
}
}
)
}
HStack(spacing: 8) {
Image(systemName: settings.passcode.isEmpty ? "exclamationmark.triangle.fill" : "checkmark.circle.fill")
.foregroundColor(settings.passcode.isEmpty ? .orange : .green)
Text(settings.passcode.isEmpty ? "Passcode не настроен" : "Passcode настроен")
.font(.caption)
.foregroundColor(settings.passcode.isEmpty ? .orange : .green)
Spacer()
}
.padding(.horizontal, 4)
if let error = errorMessage {
HStack {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.red)
Text(error)
.font(.caption)
.foregroundColor(.red)
Spacer()
}
.padding(.horizontal, 4)
}
}
.padding(.horizontal, 20)
.padding(.top, 20)
}
VStack(spacing: 12) {
Button(action: createThread) {
HStack {
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.scaleEffect(0.8)
} else {
Image(systemName: "plus.circle.fill")
}
Text(isLoading ? "Создание..." : "Создать тред")
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.background(title.isEmpty || text.isEmpty || isLoading ? Color.gray : Color.accentColor)
.foregroundColor(.white)
.cornerRadius(25)
.animation(.easeInOut(duration: 0.2), value: title.isEmpty || text.isEmpty)
}
.disabled(title.isEmpty || text.isEmpty || isLoading)
Button("Отмена") {
dismiss()
}
.foregroundColor(.secondary)
}
.padding(.horizontal, 20)
.padding(.bottom, 20)
}
.navigationTitle("/\(boardCode)/")
.navigationBarTitleDisplayMode(.inline)
.onAppear {
titleFocused = true
}
.alert("Успешно!", isPresented: $showingSuccess) {
Button("OK") {
dismiss()
}
} message: {
Text("Тред создан")
}
.sheet(isPresented: $showPicker) {
ImagePickerView(selectionLimit: 4) { images in
pickedImages = images
}
}
}
}
private func createThread() {
guard !title.isEmpty && !text.isEmpty else { return }
isLoading = true
errorMessage = nil
apiClient.createThread(
boardCode: boardCode,
title: title,
text: text,
passcode: settings.passcode,
files: buildUploadFiles()
) { error in
DispatchQueue.main.async {
self.isLoading = false
if let error = error {
self.errorMessage = error.localizedDescription
} else {
self.showingSuccess = true
}
}
}
}
private func buildUploadFiles() -> [UploadFile] {
var result: [UploadFile] = []
for (idx, img) in pickedImages.enumerated() {
if let data = img.jpegData(compressionQuality: 0.9) {
let file = UploadFile(
name: "files",
filename: "photo_\(idx + 1).jpg",
mimeType: "image/jpeg",
data: data
)
result.append(file)
}
}
return result
}
}
#Preview {
CreateThreadView(boardCode: "test")
.environmentObject(Settings())
.environmentObject(APIClient())
}