import SwiftUI import CoreData import CoreImage // MARK: - 二维码创建界面 struct CreateQRCodeView: View { @Environment(\.dismiss) private var dismiss @StateObject private var coreDataManager = CoreDataManager.shared // 从类型选择界面传入的参数 let selectedQRCodeType: QRCodeType // 通用内容输入 @State private var content = "" @FocusState private var isContentFieldFocused: Bool // Email相关字段 @State private var emailAddress = "" @State private var emailSubject = "" @State private var emailBody = "" @State private var emailCc = "" @State private var emailBcc = "" @FocusState private var focusedEmailField: EmailInputView.EmailField? // WiFi相关字段 @State private var wifiSSID = "" @State private var wifiPassword = "" @State private var wifiEncryptionType: WiFiInputView.WiFiEncryptionType = .wpa2 @FocusState private var focusedWiFiField: WiFiInputView.WiFiField? // 联系人相关字段 @State private var contactFirstName = "" @State private var contactLastName = "" @State private var contactPhone = "" @State private var contactEmail = "" @State private var contactCompany = "" @State private var contactTitle = "" @State private var contactAddress = "" @State private var contactWebsite = "" @State private var contactNickname = "" @State private var contactBirthday = Date() @State private var contactNote = "" @FocusState private var focusedContactField: ContactInputView.ContactField? // 位置相关字段 @State private var locationLatitude = "" @State private var locationLongitude = "" @State private var locationName = "" @FocusState private var focusedLocationField: LocationInputView.LocationField? // 日历相关字段 @State private var eventTitle = "" @State private var eventDescription = "" @State private var eventLocation = "" @State private var startDate = Date() @State private var endDate = Date().addingTimeInterval(3600) @FocusState private var focusedCalendarField: CalendarInputView.CalendarField? // 社交平台相关字段 @State private var socialUsername = "" @State private var socialMessage = "" @FocusState private var focusedSocialField: SocialInputView.SocialField? // 电话相关字段 @State private var phoneNumber = "" @State private var phoneMessage = "" @FocusState private var focusedPhoneField: PhoneInputView.PhoneField? // URL相关字段 @State private var urlString = "" @FocusState private var isURLFieldFocused: Bool // 通用状态 @State private var showingAlert = false @State private var alertMessage = "" var body: some View { VStack(spacing: 0) { inputAndPreviewSection } .navigationTitle(selectedQRCodeType.displayName) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("创建") { createQRCode() } .disabled(!canCreateQRCode()) .font(.system(size: 16, weight: .semibold)) } } .alert("提示", isPresented: $showingAlert) { Button("确定") { } } message: { Text(alertMessage) } .onAppear { setupInitialFocus() } .onTapGesture { hideKeyboard() } } // MARK: - UI Components private var inputAndPreviewSection: some View { ScrollView { VStack(spacing: 24) { // 内容输入区域 VStack(spacing: 16) { // 使用InputComponentFactory动态选择输入组件 createInputComponentForType() .padding(.horizontal, 20) } // 预览区域 #if DEBUG if canCreateQRCode() { VStack(spacing: 16) { // 使用QRCodePreviewView组件 QRCodePreviewView( qrCodeImage: generateQRCodeImage(), formattedContent: formatContentForQRCodeType(), qrCodeType: selectedQRCodeType ) .padding(.horizontal, 20) } } Spacer(minLength: 100) #endif } .padding(.top, 20) } .background(Color(.systemGroupedBackground)) } // MARK: - Helper Methods private func createInputComponentForType() -> AnyView { switch selectedQRCodeType { case .mail: let emailConfig = EmailInputConfig( emailAddress: $emailAddress, emailSubject: $emailSubject, emailBody: $emailBody, emailCc: $emailCc, emailBcc: $emailBcc ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, emailConfig: emailConfig ) case .wifi: let wifiConfig = WiFiInputConfig( ssid: $wifiSSID, password: $wifiPassword, encryptionType: $wifiEncryptionType ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, wifiConfig: wifiConfig ) case .vcard, .mecard: let contactConfig = ContactInputConfig( firstName: $contactFirstName, lastName: $contactLastName, phone: $contactPhone, email: $contactEmail, company: $contactCompany, title: $contactTitle, address: $contactAddress, website: $contactWebsite, nickname: $contactNickname, birthday: $contactBirthday, note: $contactNote ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, contactConfig: contactConfig ) case .location: let locationConfig = LocationInputConfig( latitude: $locationLatitude, longitude: $locationLongitude, locationName: $locationName ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, locationConfig: locationConfig ) case .calendar: let calendarConfig = CalendarInputConfig( eventTitle: $eventTitle, eventDescription: $eventDescription, startDate: $startDate, endDate: $endDate, location: $eventLocation ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, calendarConfig: calendarConfig ) case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: let socialConfig = SocialInputConfig( username: $socialUsername, message: $socialMessage ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, socialConfig: socialConfig ) case .phone, .sms: let phoneConfig = PhoneInputConfig( phoneNumber: $phoneNumber, phoneMessage: $phoneMessage ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, phoneConfig: phoneConfig ) case .url: let urlConfig = URLInputConfig( url: $urlString ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, urlConfig: urlConfig ) default: let textConfig = TextInputConfig( content: $content ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, textConfig: textConfig ) } } private func setupInitialFocus() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { switch selectedQRCodeType { case .mail: focusedEmailField = .address case .wifi: focusedWiFiField = .ssid case .vcard, .mecard: focusedContactField = .firstName case .location: focusedLocationField = .latitude case .calendar: focusedCalendarField = .title case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok: focusedSocialField = .username case .phone, .sms: focusedPhoneField = .phoneNumber case .url: isURLFieldFocused = true default: isContentFieldFocused = true } } } private func hideKeyboard() { switch selectedQRCodeType { case .mail: focusedEmailField = nil case .wifi: focusedWiFiField = nil case .vcard, .mecard: focusedContactField = nil case .location: focusedLocationField = nil case .calendar: focusedCalendarField = nil case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok: focusedSocialField = nil case .phone, .sms: focusedPhoneField = nil case .url: isURLFieldFocused = false default: isContentFieldFocused = false } } private func getInputIcon() -> String { switch selectedQRCodeType { case .mail: return "envelope" case .wifi: return "wifi" case .vcard, .mecard: return "person" case .location: return "location" case .calendar: return "calendar" case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: return "globe" case .phone, .sms: return "phone" case .url: return "link" default: return "textformat" } } private func canCreateQRCode() -> Bool { switch selectedQRCodeType { case .mail: return !emailAddress.isEmpty && !emailSubject.isEmpty && !emailBody.isEmpty case .wifi: return !wifiSSID.isEmpty case .vcard, .mecard: return !contactFirstName.isEmpty || !contactLastName.isEmpty case .location: return !locationLatitude.isEmpty && !locationLongitude.isEmpty case .calendar: return !eventTitle.isEmpty case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: return !socialUsername.isEmpty case .phone, .sms: return !phoneNumber.isEmpty case .url: return !urlString.isEmpty default: return !content.isEmpty } } private func getContentHint() -> String { InputComponentFactory.getPlaceholderText(for: selectedQRCodeType) } private func generateQRCodeImage() -> UIImage? { guard canCreateQRCode() else { return nil } let formattedContent = formatContentForQRCodeType() let data = formattedContent.data(using: .utf8) let qrFilter = CIFilter.qrCodeGenerator() qrFilter.setValue(data, forKey: "inputMessage") qrFilter.setValue("H", forKey: "inputCorrectionLevel") guard let outputImage = qrFilter.outputImage else { return nil } let context = CIContext() guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return nil } return UIImage(cgImage: cgImage) } private func formatContentForQRCodeType() -> String { switch selectedQRCodeType { case .text: return content case .url: return urlString.hasPrefix("http") ? urlString : "https://\(urlString)" case .mail: var mailtoURL = "mailto:\(emailAddress)" var queryParams: [String] = [] if !emailSubject.isEmpty { queryParams.append("subject=\(emailSubject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? emailSubject)") } if !emailBody.isEmpty { queryParams.append("body=\(emailBody.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? emailBody)") } if !emailCc.isEmpty { queryParams.append("cc=\(emailCc)") } if !emailBcc.isEmpty { queryParams.append("bcc=\(emailBcc)") } if !queryParams.isEmpty { mailtoURL += "?" + queryParams.joined(separator: "&") } return mailtoURL case .phone: return "tel:\(phoneNumber)" case .sms: let smsContent = phoneMessage.isEmpty ? "Hello" : phoneMessage return "SMSTO:\(phoneNumber):\(smsContent)" case .wifi: return "WIFI:T:\(wifiEncryptionType.rawValue);S:\(wifiSSID);P:\(wifiPassword);;" case .vcard: var vcard = "BEGIN:VCARD\nVERSION:3.0\n" // 姓名字段 (N和FN) if !contactFirstName.isEmpty || !contactLastName.isEmpty { let lastName = contactLastName.isEmpty ? "" : contactLastName let firstName = contactFirstName.isEmpty ? "" : contactFirstName vcard += "N:\(lastName);\(firstName);;;\n" vcard += "FN:\(firstName) \(lastName)\n" } // 电话字段 if !contactPhone.isEmpty { vcard += "TEL;TYPE=WORK,CELL:\(contactPhone)\n" } // 邮箱字段 if !contactEmail.isEmpty { vcard += "EMAIL;TYPE=PREF,INTERNET:\(contactEmail)\n" } // 公司字段 if !contactCompany.isEmpty { vcard += "ORG:\(contactCompany)\n" } // 职位字段 if !contactTitle.isEmpty { vcard += "TITLE:\(contactTitle)\n" } // 地址字段 if !contactAddress.isEmpty { vcard += "ADR;TYPE=WORK:;;\(contactAddress);;;;\n" } // 网站字段 if !contactWebsite.isEmpty { vcard += "URL:\(contactWebsite)\n" } vcard += "END:VCARD" return vcard case .mecard: var mecard = "MECARD:" // 姓名字段 if !contactFirstName.isEmpty || !contactLastName.isEmpty { let lastName = contactLastName.isEmpty ? "" : contactLastName let firstName = contactFirstName.isEmpty ? "" : contactFirstName mecard += "N:\(lastName),\(firstName);" } // 昵称字段 if !contactNickname.isEmpty { mecard += "NICKNAME:\(contactNickname);" } // 电话字段 if !contactPhone.isEmpty { mecard += "TEL:\(contactPhone);" } // 邮箱字段 if !contactEmail.isEmpty { mecard += "EMAIL:\(contactEmail);" } // 公司字段 if !contactCompany.isEmpty { mecard += "ORG:\(contactCompany);" } // 地址字段 if !contactAddress.isEmpty { mecard += "ADR:\(contactAddress);" } // 网站字段 if !contactWebsite.isEmpty { mecard += "URL:\(contactWebsite);" } // 生日字段 let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyyMMdd" let birthdayString = dateFormatter.string(from: contactBirthday) mecard += "BDAY:\(birthdayString);" // 备注字段 if !contactNote.isEmpty { mecard += "NOTE:\(contactNote);" } mecard += ";" return mecard case .location: let coords = "\(locationLatitude),\(locationLongitude)" return locationName.isEmpty ? "geo:\(coords)" : "geo:\(coords)?q=\(locationName)" case .calendar: let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" dateFormatter.timeZone = TimeZone(abbreviation: "UTC") var ical = "BEGIN:VEVENT\n" ical += "SUMMARY:\(eventTitle)\n" if !eventDescription.isEmpty { ical += "DESCRIPTION:\(eventDescription)\n" } if !eventLocation.isEmpty { ical += "LOCATION:\(eventLocation)\n" } ical += "DTSTART:\(dateFormatter.string(from: startDate))\n" ical += "DTEND:\(dateFormatter.string(from: endDate))\n" ical += "END:VEVENT" return ical case .instagram: return "instagram://user?username=\(socialUsername)" case .facebook: // 处理Facebook输入:支持用户名/ID或完整链接 let facebookId = extractFacebookId(from: socialUsername) return "fb://profile/\(facebookId)" case .spotify: return "spotify:search:\(socialUsername);\(socialMessage)" case .twitter: return "twitter://user?screen_name=\(socialUsername)" case .whatsapp: return "whatsapp://send?phone=\(socialUsername)" case .viber: return "viber://add?number=\(socialUsername)" case .snapchat: return "https://snapchat.com/add/\(socialUsername)" case .tiktok: return "https://www.tiktok.com/@\(socialUsername)" } } // MARK: - 生成社交媒体内容 private func generateSocialMediaContent() -> String { switch selectedQRCodeType { case .instagram: return "instagram://user?username=\(socialUsername)" case .facebook: // 处理Facebook输入:支持用户名/ID或完整链接 let facebookId = extractFacebookId(from: socialUsername) return "fb://profile/\(facebookId)" case .spotify: return "spotify:search:\(socialUsername);\(socialMessage)" case .twitter: return "twitter://user?screen_name=\(socialUsername)" case .whatsapp: return "whatsapp://send?phone=\(socialUsername)" case .viber: return "viber://add?number=\(socialUsername)" case .snapchat: return "https://snapchat.com/add/\(socialUsername)" case .tiktok: return "https://www.tiktok.com/@\(socialUsername)" default: return socialUsername } } private func createQRCode() { let context = coreDataManager.container.viewContext let historyItem = HistoryItem(context: context) historyItem.id = UUID() historyItem.dataType = DataType.qrcode.rawValue historyItem.dataSource = DataSource.created.rawValue historyItem.createdAt = Date() historyItem.isFavorite = false historyItem.qrCodeType = selectedQRCodeType.rawValue // 根据类型设置内容 switch selectedQRCodeType { case .mail: var mailContent = "邮箱: \(emailAddress)\n主题: \(emailSubject)\n正文: \(emailBody)" if !emailCc.isEmpty { mailContent += "\n抄送: \(emailCc)" } if !emailBcc.isEmpty { mailContent += "\n密送: \(emailBcc)" } historyItem.content = mailContent case .wifi: historyItem.content = "WiFi: \(wifiSSID) (\(wifiEncryptionType.displayName))" case .vcard, .mecard: var contactContent = "联系人: " if !contactFirstName.isEmpty || !contactLastName.isEmpty { contactContent += "\(contactFirstName) \(contactLastName)" } if !contactNickname.isEmpty { contactContent += " (\(contactNickname))" } if !contactPhone.isEmpty { contactContent += "\n电话: \(contactPhone)" } if !contactEmail.isEmpty { contactContent += "\n邮箱: \(contactEmail)" } if !contactCompany.isEmpty { contactContent += "\n公司: \(contactCompany)" } if !contactTitle.isEmpty { contactContent += "\n职位: \(contactTitle)" } if !contactAddress.isEmpty { contactContent += "\n地址: \(contactAddress)" } if !contactWebsite.isEmpty { contactContent += "\n网站: \(contactWebsite)" } if !contactNote.isEmpty { contactContent += "\n备注: \(contactNote)" } historyItem.content = contactContent case .location: historyItem.content = "位置: \(locationLatitude), \(locationLongitude)" case .calendar: historyItem.content = "事件: \(eventTitle)" case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: historyItem.content = generateSocialMediaContent() case .phone, .sms: historyItem.content = "电话: \(phoneNumber)" case .url: historyItem.content = "URL: \(urlString)" default: historyItem.content = content } do { try context.save() alertMessage = "二维码创建成功!" showingAlert = true // 创建成功后返回 DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { dismiss() } } catch { alertMessage = "保存失败:\(error.localizedDescription)" showingAlert = true } } // MARK: - Facebook ID提取辅助函数 private func extractFacebookId(from input: String) -> String { // 如果输入的是Facebook链接,提取用户名/ID if input.hasPrefix("http") { // 处理各种Facebook链接格式 let patterns = [ "https://www.facebook.com/", "https://facebook.com/", "http://www.facebook.com/", "http://facebook.com/" ] var cleanedInput = input for pattern in patterns { if cleanedInput.hasPrefix(pattern) { cleanedInput = String(cleanedInput.dropFirst(pattern.count)) break } } // 移除查询参数和路径 if let questionMarkIndex = cleanedInput.firstIndex(of: "?") { cleanedInput = String(cleanedInput[..