import SwiftUI import Alamofire struct Message: Identifiable, Codable { let id: UUID let text: String let sender: String let timestamp: String let isSelf: Bool let avatar: String let num: Int let image: Data? init(id: UUID = UUID(), text: String = "", sender: String, timestamp: String, isSelf: Bool, avatar: String, num: Int = 0, image: UIImage? = nil) { self.id = id self.text = text self.sender = sender self.timestamp = timestamp self.isSelf = isSelf self.avatar = avatar self.num = num self.image = image?.jpegData(compressionQuality: 0.8) } } struct MessageDetailView: View { @State private var messages: [Message] = [] @State private var replyText = "" @State private var selectedImage: UIImage? @State private var selectedFileURL: URL? @State private var isImagePickerPresented = false @State private var isDocumentPickerPresented = false @State private var uploadProgress: Double = 0.0 @Environment(\.presentationMode) var presentationMode var body: some View { VStack { HStack { Button(action: { presentationMode.wrappedValue.dismiss() }) { Image(systemName: "chevron.left") .font(.system(size: 18)) .foregroundColor(.black) } Spacer() Text("详细信息") .font(.system(size: 18)) .fontWeight(.bold) Spacer() Button(action: {}) { Image(systemName: "ellipsis") .font(.system(size: 18)) .foregroundColor(.black) } .opacity(0) } .padding(.horizontal, 12) .padding(.vertical, 8) ScrollView { VStack(alignment: .leading, spacing: 10) { ForEach(messages) { message in HStack(alignment: .top, spacing: 10) { if !message.isSelf { Image("avatar") .CircleImage(size: 30) if let imageData = message.image, let image = UIImage(data: imageData) { ZStack { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 200, height: 200) .cornerRadius(10) if uploadProgress < 1.0 { ProgressView(value: uploadProgress) .progressViewStyle(CircularProgressViewStyle()) .scaleEffect(2) .frame(width: 50, height: 50) .background(Color.white.opacity(0.7)) .cornerRadius(25) } } } else { TextBlackbble(message: message.text) } Spacer() } else { Spacer() if let imageData = message.image, let image = UIImage(data: imageData) { ZStack { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 200, height: 200) .cornerRadius(10) if uploadProgress < 1.0 { ProgressView(value: uploadProgress) .progressViewStyle(CircularProgressViewStyle()) .scaleEffect(2) .frame(width: 50, height: 50) .background(Color.white.opacity(0.7)) .cornerRadius(25) } } } else { TextBubble(message: message.text) } Image("avatar") .CircleImage(size: 30) } } .padding(.vertical, 5) } } .padding() } Spacer() HStack { Button(action: { isImagePickerPresented = true }) { Image(systemName: "paperclip") .font(.system(size: 18)) .foregroundColor(.white) .padding(10) .background(Color.black.opacity(0.1)) .clipShape(Circle()) } TextField("给“系统消息”发送消息", text: $replyText) .padding(10) .background(Color.black.opacity(0.1)) .cornerRadius(20) Button(action: { sendMessage() }) { Image(systemName: "arrow.up.circle.fill") .font(.system(size: 18)) .foregroundColor(.white) .padding(10) .background(Color.black.opacity(0.1)) .clipShape(Circle()) } } .padding() .background(Color.black.opacity(0.2)) .cornerRadius(30) .padding(.horizontal, 12) .padding(.bottom, 8) .sheet(isPresented: $isImagePickerPresented) { ImagePicker(selectedImage: $selectedImage, onImagePicked: { image in sendImageMessage(image: image) }) } .sheet(isPresented: $isDocumentPickerPresented) { DocumentPicker(selectedFileURL: $selectedFileURL) } } .navigationBarBackButtonHidden(true) .onAppear { loadMessages() } .onTapGesture { hideKeyboard() } } private func sendMessage() { let newMessage = Message(text: replyText, sender: "你", timestamp: currentTimestamp(), isSelf: true, avatar: "avatar") messages.append(newMessage) saveMessages() replyText = "" } private func sendImageMessage(image: UIImage) { let newMessage = Message(sender: "你", timestamp: currentTimestamp(), isSelf: true, avatar: "avatar", image: image) messages.append(newMessage) //saveMessages() uploadProgress = 0.0 if let imageData = image.jpegData(compressionQuality: 0.8) { uploadFile(api: apiprefixV1+upload, file: imageData) { progress in self.uploadProgress = progress } completion: { (result: Result, AFError>) in print(result) } } } private func saveMessages() { if let encodedMessages = try? JSONEncoder().encode(messages) { UserDefaults.standard.set(encodedMessages, forKey: "messages") } } private func loadMessages() { UserDefaults.standard.removeObject(forKey: "messages") if let savedMessagesData = UserDefaults.standard.data(forKey: "messages"), let decodedMessages = try? JSONDecoder().decode([Message].self, from: savedMessagesData) { messages = decodedMessages } } private func currentTimestamp() -> String { let formatter = DateFormatter() formatter.dateFormat = "h:mm a" return formatter.string(from: Date()) } private func hideKeyboard() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } } struct TextBlackbble: View { var message: String var body: some View { Text(message) .padding(10) .background(Color.black.opacity(0.1)) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.black, lineWidth: 1) ) } } struct TextBubble: View { var message: String var body: some View { Text(message) .padding(10) .background(Color.blue.opacity(0.1)) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.blue, lineWidth: 1) ) } } // ImagePicker implementation struct ImagePicker: UIViewControllerRepresentable { class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { let parent: ImagePicker let onImagePicked: (UIImage) -> Void init(parent: ImagePicker, onImagePicked: @escaping (UIImage) -> Void) { self.parent = parent self.onImagePicked = onImagePicked } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let image = info[.originalImage] as? UIImage { parent.selectedImage = image onImagePicked(image) } picker.dismiss(animated: true) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) } } @Binding var selectedImage: UIImage? let onImagePicked: (UIImage) -> Void func makeCoordinator() -> Coordinator { Coordinator(parent: self, onImagePicked: onImagePicked) } func makeUIViewController(context: Context) -> UIImagePickerController { let picker = UIImagePickerController() picker.delegate = context.coordinator return picker } func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {} } // DocumentPicker implementation struct DocumentPicker: UIViewControllerRepresentable { @Binding var selectedFileURL: URL? class Coordinator: NSObject, UIDocumentPickerDelegate { let parent: DocumentPicker init(parent: DocumentPicker) { self.parent = parent } func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { parent.selectedFileURL = urls.first } func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { parent.selectedFileURL = nil } } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } func makeUIViewController(context: Context) -> UIDocumentPickerViewController { let controller = UIDocumentPickerViewController(forOpeningContentTypes: [.item]) controller.delegate = context.coordinator return controller } func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} } #Preview { MessageDetailView() }