This commit is contained in:
parent
4b20775c61
commit
8cab7bac36
BIN
MobileMkch.xcodeproj/project.xcworkspace/xcuserdata/platon.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
BIN
MobileMkch.xcodeproj/project.xcworkspace/xcuserdata/platon.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
Binary file not shown.
@ -1,6 +1,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
|
|
||||||
|
struct UploadFile {
|
||||||
|
let name: String
|
||||||
|
let filename: String
|
||||||
|
let mimeType: String
|
||||||
|
let data: Data
|
||||||
|
}
|
||||||
|
|
||||||
class APIClient: ObservableObject {
|
class APIClient: ObservableObject {
|
||||||
private let baseURL = "https://mkch.pooziqo.xyz"
|
private let baseURL = "https://mkch.pooziqo.xyz"
|
||||||
private let apiURL = "https://mkch.pooziqo.xyz/api"
|
private let apiURL = "https://mkch.pooziqo.xyz/api"
|
||||||
@ -437,21 +444,21 @@ class APIClient: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createThread(boardCode: String, title: String, text: String, passcode: String, completion: @escaping (Error?) -> Void) {
|
func createThread(boardCode: String, title: String, text: String, passcode: String, files: [UploadFile] = [], completion: @escaping (Error?) -> Void) {
|
||||||
if !passcode.isEmpty {
|
if !passcode.isEmpty {
|
||||||
loginWithPasscode(passcode: passcode) { error in
|
loginWithPasscode(passcode: passcode) { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
completion(error)
|
completion(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.performCreateThread(boardCode: boardCode, title: title, text: text, completion: completion)
|
self.performCreateThread(boardCode: boardCode, title: title, text: text, files: files, completion: completion)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
performCreateThread(boardCode: boardCode, title: title, text: text, completion: completion)
|
performCreateThread(boardCode: boardCode, title: title, text: text, files: files, completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func performCreateThread(boardCode: String, title: String, text: String, completion: @escaping (Error?) -> Void) {
|
private func performCreateThread(boardCode: String, title: String, text: String, files: [UploadFile] = [], completion: @escaping (Error?) -> Void) {
|
||||||
let formURL = "\(baseURL)/boards/board/\(boardCode)/new"
|
let formURL = "\(baseURL)/boards/board/\(boardCode)/new"
|
||||||
let url = URL(string: formURL)!
|
let url = URL(string: formURL)!
|
||||||
|
|
||||||
@ -485,19 +492,30 @@ class APIClient: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var postRequest = URLRequest(url: url)
|
||||||
|
postRequest.httpMethod = "POST"
|
||||||
|
postRequest.setValue(formURL, forHTTPHeaderField: "Referer")
|
||||||
|
postRequest.setValue(self.userAgent, forHTTPHeaderField: "User-Agent")
|
||||||
|
|
||||||
|
if files.isEmpty {
|
||||||
var formData = URLComponents()
|
var formData = URLComponents()
|
||||||
formData.queryItems = [
|
formData.queryItems = [
|
||||||
URLQueryItem(name: "csrfmiddlewaretoken", value: csrfToken),
|
URLQueryItem(name: "csrfmiddlewaretoken", value: csrfToken),
|
||||||
URLQueryItem(name: "title", value: title),
|
URLQueryItem(name: "title", value: title),
|
||||||
URLQueryItem(name: "text", value: text)
|
URLQueryItem(name: "text", value: text)
|
||||||
]
|
]
|
||||||
|
|
||||||
var postRequest = URLRequest(url: url)
|
|
||||||
postRequest.httpMethod = "POST"
|
|
||||||
postRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
postRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||||
postRequest.setValue(formURL, forHTTPHeaderField: "Referer")
|
|
||||||
postRequest.setValue(self.userAgent, forHTTPHeaderField: "User-Agent")
|
|
||||||
postRequest.httpBody = formData.query?.data(using: .utf8)
|
postRequest.httpBody = formData.query?.data(using: .utf8)
|
||||||
|
} else {
|
||||||
|
let boundary = "Boundary-\(UUID().uuidString)"
|
||||||
|
postRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||||
|
let body = self.buildMultipartBody(parameters: [
|
||||||
|
"csrfmiddlewaretoken": csrfToken,
|
||||||
|
"title": title,
|
||||||
|
"text": text
|
||||||
|
], files: files, boundary: boundary)
|
||||||
|
postRequest.httpBody = body
|
||||||
|
}
|
||||||
|
|
||||||
self.session.dataTask(with: postRequest) { _, postResponse, postError in
|
self.session.dataTask(with: postRequest) { _, postResponse, postError in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@ -520,21 +538,21 @@ class APIClient: ObservableObject {
|
|||||||
}.resume()
|
}.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
func addComment(boardCode: String, threadId: Int, text: String, passcode: String, completion: @escaping (Error?) -> Void) {
|
func addComment(boardCode: String, threadId: Int, text: String, passcode: String, files: [UploadFile] = [], completion: @escaping (Error?) -> Void) {
|
||||||
if !passcode.isEmpty {
|
if !passcode.isEmpty {
|
||||||
loginWithPasscode(passcode: passcode) { error in
|
loginWithPasscode(passcode: passcode) { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
completion(error)
|
completion(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.performAddComment(boardCode: boardCode, threadId: threadId, text: text, completion: completion)
|
self.performAddComment(boardCode: boardCode, threadId: threadId, text: text, files: files, completion: completion)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
performAddComment(boardCode: boardCode, threadId: threadId, text: text, completion: completion)
|
performAddComment(boardCode: boardCode, threadId: threadId, text: text, files: files, completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func performAddComment(boardCode: String, threadId: Int, text: String, completion: @escaping (Error?) -> Void) {
|
private func performAddComment(boardCode: String, threadId: Int, text: String, files: [UploadFile] = [], completion: @escaping (Error?) -> Void) {
|
||||||
let formURL = "\(baseURL)/boards/board/\(boardCode)/thread/\(threadId)/comment"
|
let formURL = "\(baseURL)/boards/board/\(boardCode)/thread/\(threadId)/comment"
|
||||||
let url = URL(string: formURL)!
|
let url = URL(string: formURL)!
|
||||||
|
|
||||||
@ -568,18 +586,28 @@ class APIClient: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var postRequest = URLRequest(url: url)
|
||||||
|
postRequest.httpMethod = "POST"
|
||||||
|
postRequest.setValue(formURL, forHTTPHeaderField: "Referer")
|
||||||
|
postRequest.setValue(self.userAgent, forHTTPHeaderField: "User-Agent")
|
||||||
|
|
||||||
|
if files.isEmpty {
|
||||||
var formData = URLComponents()
|
var formData = URLComponents()
|
||||||
formData.queryItems = [
|
formData.queryItems = [
|
||||||
URLQueryItem(name: "csrfmiddlewaretoken", value: csrfToken),
|
URLQueryItem(name: "csrfmiddlewaretoken", value: csrfToken),
|
||||||
URLQueryItem(name: "text", value: text)
|
URLQueryItem(name: "text", value: text)
|
||||||
]
|
]
|
||||||
|
|
||||||
var postRequest = URLRequest(url: url)
|
|
||||||
postRequest.httpMethod = "POST"
|
|
||||||
postRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
postRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
||||||
postRequest.setValue(formURL, forHTTPHeaderField: "Referer")
|
|
||||||
postRequest.setValue(self.userAgent, forHTTPHeaderField: "User-Agent")
|
|
||||||
postRequest.httpBody = formData.query?.data(using: .utf8)
|
postRequest.httpBody = formData.query?.data(using: .utf8)
|
||||||
|
} else {
|
||||||
|
let boundary = "Boundary-\(UUID().uuidString)"
|
||||||
|
postRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||||
|
let body = self.buildMultipartBody(parameters: [
|
||||||
|
"csrfmiddlewaretoken": csrfToken,
|
||||||
|
"text": text
|
||||||
|
], files: files, boundary: boundary)
|
||||||
|
postRequest.httpBody = body
|
||||||
|
}
|
||||||
|
|
||||||
self.session.dataTask(with: postRequest) { _, postResponse, postError in
|
self.session.dataTask(with: postRequest) { _, postResponse, postError in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@ -617,6 +645,25 @@ class APIClient: ObservableObject {
|
|||||||
return String(html[range])
|
return String(html[range])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func buildMultipartBody(parameters: [String: String], files: [UploadFile], boundary: String) -> Data {
|
||||||
|
var body = Data()
|
||||||
|
let boundaryPrefix = "--\(boundary)\r\n"
|
||||||
|
for (key, value) in parameters {
|
||||||
|
body.append(boundaryPrefix.data(using: .utf8)!)
|
||||||
|
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
|
||||||
|
body.append("\(value)\r\n".data(using: .utf8)!)
|
||||||
|
}
|
||||||
|
for file in files {
|
||||||
|
body.append(boundaryPrefix.data(using: .utf8)!)
|
||||||
|
body.append("Content-Disposition: form-data; name=\"\(file.name)\"; filename=\"\(file.filename)\"\r\n".data(using: .utf8)!)
|
||||||
|
body.append("Content-Type: \(file.mimeType)\r\n\r\n".data(using: .utf8)!)
|
||||||
|
body.append(file.data)
|
||||||
|
body.append("\r\n".data(using: .utf8)!)
|
||||||
|
}
|
||||||
|
body.append("--\(boundary)--\r\n".data(using: .utf8)!)
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
func checkNewThreads(forBoard boardCode: String, lastKnownThreadId: Int, completion: @escaping (Result<[Thread], Error>) -> Void) {
|
func checkNewThreads(forBoard boardCode: String, lastKnownThreadId: Int, completion: @escaping (Result<[Thread], Error>) -> Void) {
|
||||||
let url = URL(string: "\(apiURL)/board/\(boardCode)")!
|
let url = URL(string: "\(apiURL)/board/\(boardCode)")!
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import PhotosUI
|
||||||
|
|
||||||
struct AddCommentView: View {
|
struct AddCommentView: View {
|
||||||
let boardCode: String
|
let boardCode: String
|
||||||
@ -12,6 +13,8 @@ struct AddCommentView: View {
|
|||||||
@State private var errorMessage: String?
|
@State private var errorMessage: String?
|
||||||
@State private var showingSuccess = false
|
@State private var showingSuccess = false
|
||||||
@FocusState private var isTextFocused: Bool
|
@FocusState private var isTextFocused: Bool
|
||||||
|
@State private var pickedImages: [UIImage] = []
|
||||||
|
@State private var showPicker: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
@ -56,6 +59,38 @@ struct AddCommentView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Image(systemName: settings.passcode.isEmpty ? "exclamationmark.triangle.fill" : "checkmark.circle.fill")
|
Image(systemName: settings.passcode.isEmpty ? "exclamationmark.triangle.fill" : "checkmark.circle.fill")
|
||||||
.foregroundColor(settings.passcode.isEmpty ? .orange : .green)
|
.foregroundColor(settings.passcode.isEmpty ? .orange : .green)
|
||||||
@ -126,6 +161,11 @@ struct AddCommentView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text("Комментарий добавлен")
|
Text("Комментарий добавлен")
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showPicker) {
|
||||||
|
ImagePickerView(selectionLimit: 4) { images in
|
||||||
|
pickedImages = images
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +179,8 @@ struct AddCommentView: View {
|
|||||||
boardCode: boardCode,
|
boardCode: boardCode,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
text: text,
|
text: text,
|
||||||
passcode: settings.passcode
|
passcode: settings.passcode,
|
||||||
|
files: buildUploadFiles()
|
||||||
) { error in
|
) { error in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
@ -152,6 +193,22 @@ struct AddCommentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
#Preview {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import PhotosUI
|
||||||
|
|
||||||
struct CreateThreadView: View {
|
struct CreateThreadView: View {
|
||||||
let boardCode: String
|
let boardCode: String
|
||||||
@ -13,6 +14,8 @@ struct CreateThreadView: View {
|
|||||||
@State private var showingSuccess = false
|
@State private var showingSuccess = false
|
||||||
@FocusState private var titleFocused: Bool
|
@FocusState private var titleFocused: Bool
|
||||||
@FocusState private var textFocused: Bool
|
@FocusState private var textFocused: Bool
|
||||||
|
@State private var pickedImages: [UIImage] = []
|
||||||
|
@State private var showPicker: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
@ -41,6 +44,38 @@ struct CreateThreadView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Содержание")
|
Text("Содержание")
|
||||||
@ -149,6 +184,11 @@ struct CreateThreadView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text("Тред создан")
|
Text("Тред создан")
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showPicker) {
|
||||||
|
ImagePickerView(selectionLimit: 4) { images in
|
||||||
|
pickedImages = images
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +202,8 @@ struct CreateThreadView: View {
|
|||||||
boardCode: boardCode,
|
boardCode: boardCode,
|
||||||
title: title,
|
title: title,
|
||||||
text: text,
|
text: text,
|
||||||
passcode: settings.passcode
|
passcode: settings.passcode,
|
||||||
|
files: buildUploadFiles()
|
||||||
) { error in
|
) { error in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
@ -175,6 +216,22 @@ struct CreateThreadView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
#Preview {
|
||||||
|
|||||||
57
MobileMkch/ImagePicker.swift
Normal file
57
MobileMkch/ImagePicker.swift
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import PhotosUI
|
||||||
|
|
||||||
|
struct ImagePickerView: UIViewControllerRepresentable {
|
||||||
|
let selectionLimit: Int
|
||||||
|
let onComplete: ([UIImage]) -> Void
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> PHPickerViewController {
|
||||||
|
var configuration = PHPickerConfiguration()
|
||||||
|
configuration.filter = .images
|
||||||
|
configuration.selectionLimit = selectionLimit
|
||||||
|
let picker = PHPickerViewController(configuration: configuration)
|
||||||
|
picker.delegate = context.coordinator
|
||||||
|
return picker
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(onComplete: onComplete)
|
||||||
|
}
|
||||||
|
|
||||||
|
final class Coordinator: NSObject, PHPickerViewControllerDelegate {
|
||||||
|
let onComplete: ([UIImage]) -> Void
|
||||||
|
|
||||||
|
init(onComplete: @escaping ([UIImage]) -> Void) {
|
||||||
|
self.onComplete = onComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||||
|
guard !results.isEmpty else {
|
||||||
|
picker.dismiss(animated: true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let providers = results.map { $0.itemProvider }
|
||||||
|
var images: [UIImage] = []
|
||||||
|
let group = DispatchGroup()
|
||||||
|
for provider in providers {
|
||||||
|
if provider.canLoadObject(ofClass: UIImage.self) {
|
||||||
|
group.enter()
|
||||||
|
provider.loadObject(ofClass: UIImage.self) { object, _ in
|
||||||
|
if let img = object as? UIImage {
|
||||||
|
images.append(img)
|
||||||
|
}
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.notify(queue: .main) {
|
||||||
|
self.onComplete(images)
|
||||||
|
picker.dismiss(animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -8,6 +8,8 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>NSSupportsLiveActivities</key>
|
<key>NSSupportsLiveActivities</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Нужно для выбора фото и загрузки вложений</string>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>background-processing</string>
|
<string>background-processing</string>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ struct MobileMkchApp: App {
|
|||||||
|
|
||||||
private func handleNotificationLaunch() {
|
private func handleNotificationLaunch() {
|
||||||
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||||
let userInfo = scene.session.userInfo {
|
let _ = scene.session.userInfo {
|
||||||
print("Приложение запущено из уведомления")
|
print("Приложение запущено из уведомления")
|
||||||
}
|
}
|
||||||
notificationManager.clearBadge()
|
notificationManager.clearBadge()
|
||||||
|
|||||||
@ -89,29 +89,7 @@ struct ThreadDetailView: View {
|
|||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
HStack {
|
|
||||||
Button("Обновить") { loadThreadDetail() }
|
Button("Обновить") { loadThreadDetail() }
|
||||||
if #available(iOS 16.1, *) {
|
|
||||||
Toggle("", isOn: $activityOn)
|
|
||||||
.toggleStyle(SwitchToggleStyle(tint: .blue))
|
|
||||||
.labelsHidden()
|
|
||||||
.onChange(of: activityOn) { newValue in
|
|
||||||
guard settings.liveActivityEnabled else { return }
|
|
||||||
if newValue {
|
|
||||||
if let detail = threadDetail {
|
|
||||||
LiveActivityManager.shared.start(for: detail, comments: comments, settings: settings)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LiveActivityManager.shared.end(threadId: thread.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
if settings.liveActivityEnabled {
|
|
||||||
activityOn = LiveActivityManager.shared.isActive(threadId: thread.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showingAddComment) {
|
.sheet(isPresented: $showingAddComment) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user