333 lines
12 KiB
Swift
333 lines
12 KiB
Swift
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<HTTPBinResponse<Token>, 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()
|
|
}
|
|
|
|
|