import SwiftUI import CoreData import CoreImage // MARK: - 二维码创建界面 struct CreateQRCodeView: View { @Environment(\.dismiss) private var dismiss @EnvironmentObject var coreDataManager: CoreDataManager // 从类型选择界面传入的参数 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 = "" @State private var navigateToStyleView = false var body: some View { VStack(spacing: 0) { inputAndPreviewSection } .navigationTitle(selectedQRCodeType.displayName) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(NSLocalizedString("create", comment: "Create")) { if canCreateQRCode() { navigateToStyleView = true } } .disabled(!canCreateQRCode()) .font(.system(size: 16, weight: .semibold)) } } .alert(NSLocalizedString("tip", comment: "Tip"), isPresented: $showingAlert) { Button(NSLocalizedString("confirm", comment: "Confirm")) { } } message: { Text(alertMessage) } .background( NavigationLink( destination: QRCodeStyleView(qrCodeContent: generateQRCodeContent(), qrCodeType: selectedQRCodeType, existingStyleData: nil, historyItem: nil), isActive: $navigateToStyleView ) { EmptyView() } ) .onAppear { setupInitialFocus() } .onTapGesture { hideKeyboard() } } // MARK: - UI Components private var inputAndPreviewSection: some View { ScrollView { VStack(spacing: 24) { // Content input area VStack(spacing: 16) { // Use InputComponentFactory to dynamically select input component createInputComponentForType() .padding(.horizontal, 20) } // Preview area #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 = String(format: NSLocalizedString("email_content_format", comment: "Email content format"), emailAddress, emailSubject, emailBody) if !emailCc.isEmpty { mailContent += String(format: NSLocalizedString("email_cc_format", comment: "Email CC format"), emailCc) } if !emailBcc.isEmpty { mailContent += String(format: NSLocalizedString("email_bcc_format", comment: "Email BCC format"), emailBcc) } historyItem.content = mailContent case .wifi: historyItem.content = String(format: NSLocalizedString("wifi_content_format", comment: "WiFi content format"), wifiSSID, wifiEncryptionType.displayName) case .vcard, .mecard: var contactContent = NSLocalizedString("contact_content_prefix", comment: "Contact content prefix") if !contactFirstName.isEmpty || !contactLastName.isEmpty { contactContent += "\(contactFirstName) \(contactLastName)" } if !contactNickname.isEmpty { contactContent += String(format: NSLocalizedString("contact_nickname_format", comment: "Contact nickname format"), contactNickname) } if !contactPhone.isEmpty { contactContent += String(format: NSLocalizedString("contact_phone_format", comment: "Contact phone format"), contactPhone) } if !contactEmail.isEmpty { contactContent += String(format: NSLocalizedString("contact_email_format", comment: "Contact email format"), contactEmail) } if !contactCompany.isEmpty { contactContent += String(format: NSLocalizedString("contact_company_format", comment: "Contact company format"), contactCompany) } if !contactTitle.isEmpty { contactContent += String(format: NSLocalizedString("contact_title_format", comment: "Contact title format"), contactTitle) } if !contactAddress.isEmpty { contactContent += String(format: NSLocalizedString("contact_address_format", comment: "Contact address format"), contactAddress) } if !contactWebsite.isEmpty { contactContent += String(format: NSLocalizedString("contact_website_format", comment: "Contact website format"), contactWebsite) } if !contactNote.isEmpty { contactContent += String(format: NSLocalizedString("contact_note_format", comment: "Contact note format"), contactNote) } historyItem.content = contactContent case .location: historyItem.content = String(format: NSLocalizedString("location_content_format", comment: "Location content format"), locationLatitude, locationLongitude) case .calendar: historyItem.content = String(format: NSLocalizedString("calendar_content_format", comment: "Calendar content format"), eventTitle) case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: historyItem.content = generateSocialMediaContent() case .phone, .sms: historyItem.content = String(format: NSLocalizedString("phone_content_format", comment: "Phone content format"), phoneNumber) case .url: historyItem.content = String(format: NSLocalizedString("url_content_format", comment: "URL content format"), urlString) default: historyItem.content = content } do { try context.save() alertMessage = NSLocalizedString("qrcode_created_successfully", comment: "QR code created successfully") showingAlert = true // 创建成功后返回 DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { dismiss() } } catch { alertMessage = String(format: NSLocalizedString("save_failed_error", comment: "Save failed with error"), error.localizedDescription) showingAlert = true } } // MARK: - 生成二维码内容 private func generateQRCodeContent() -> String { switch selectedQRCodeType { case .mail: var mailContent = "mailto:\(emailAddress)" if !emailSubject.isEmpty || !emailBody.isEmpty || !emailCc.isEmpty || !emailBcc.isEmpty { mailContent += "?" var params: [String] = [] if !emailSubject.isEmpty { params.append("subject=\(emailSubject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") } if !emailBody.isEmpty { params.append("body=\(emailBody.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") } if !emailCc.isEmpty { params.append("cc=\(emailCc.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") } if !emailBcc.isEmpty { params.append("bcc=\(emailBcc.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") } mailContent += params.joined(separator: "&") } return mailContent case .wifi: var wifiContent = "WIFI:" wifiContent += "S:\(wifiSSID);" wifiContent += "T:\(wifiEncryptionType.rawValue.uppercased());" if !wifiPassword.isEmpty { wifiContent += "P:\(wifiPassword);" } wifiContent += ";" return wifiContent case .vcard: var vcardContent = "BEGIN:VCARD\nVERSION:3.0\n" if !contactFirstName.isEmpty || !contactLastName.isEmpty { vcardContent += "N:\(contactLastName);\(contactFirstName);;;\n" vcardContent += "FN:\(contactFirstName) \(contactLastName)\n" } if !contactPhone.isEmpty { vcardContent += "TEL;TYPE=WORK,PREF:\(contactPhone)\n" } if !contactEmail.isEmpty { vcardContent += "EMAIL:\(contactEmail)\n" } if !contactCompany.isEmpty { vcardContent += "ORG:\(contactCompany)\n" } if !contactTitle.isEmpty { vcardContent += "TITLE:\(contactTitle)\n" } if !contactAddress.isEmpty { vcardContent += "ADR;TYPE=WORK:;;\(contactAddress);;;;\n" } if !contactWebsite.isEmpty { vcardContent += "URL:\(contactWebsite)\n" } vcardContent += "END:VCARD" return vcardContent case .mecard: var mecardContent = "MECARD:" if !contactFirstName.isEmpty || !contactLastName.isEmpty { mecardContent += "N:\(contactLastName),\(contactFirstName);" } if !contactNickname.isEmpty { mecardContent += "NICKNAME:\(contactNickname);" } if !contactPhone.isEmpty { mecardContent += "TEL:\(contactPhone);" } if !contactEmail.isEmpty { mecardContent += "EMAIL:\(contactEmail);" } if !contactCompany.isEmpty { mecardContent += "ORG:\(contactCompany);" } if !contactTitle.isEmpty { mecardContent += "TITLE:\(contactTitle);" } if !contactAddress.isEmpty { mecardContent += "ADR:,,\(contactAddress);" } if !contactWebsite.isEmpty { mecardContent += "URL:\(contactWebsite);" } if !contactNote.isEmpty { mecardContent += "NOTE:\(contactNote);" } mecardContent += ";" return mecardContent case .location: return "geo:\(locationLatitude),\(locationLongitude)" case .calendar: let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'" dateFormatter.timeZone = TimeZone(abbreviation: "UTC") let startDateString = dateFormatter.string(from: startDate) let endDateString = dateFormatter.string(from: endDate) var icalContent = "BEGIN:VEVENT\n" icalContent += "SUMMARY:\(eventTitle)\n" if !eventDescription.isEmpty { icalContent += "DESCRIPTION:\(eventDescription)\n" } if !eventLocation.isEmpty { icalContent += "LOCATION:\(eventLocation)\n" } icalContent += "DTSTART:\(startDateString)\n" icalContent += "DTEND:\(endDateString)\n" icalContent += "END:VEVENT" return icalContent case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: return generateSocialMediaContent() case .phone: return "tel:\(phoneNumber)" case .sms: return "SMSTO:\(phoneNumber)" case .url: return urlString default: return content } } // 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[..