import SwiftUI import CoreData import QRCode import NetworkExtension import UIKit import FacebookCore internal import SwiftImageReadWrite struct QRCodeDetailView: View { let historyItem: HistoryItem @EnvironmentObject var coreDataManager: CoreDataManager @State private var qrCodeImage: UIImage? @State private var showingShareSheet = false @State private var showingAlert = false @State private var alertMessage = "" @State private var navigateToStyleView = false var body: some View { ScrollView { VStack(spacing: 20) { // 解析后的详细信息 parsedInfoSection #if DEBUG // 原始内容 originalContentSection #endif // 操作按钮 actionButtonsSection // Decorate code按钮 decorateCodeButton } .padding(.horizontal, 16) .padding(.vertical, 12) } .navigationTitle(getNavigationTitle()) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { showingShareSheet = true }) { Image(systemName: "square.and.arrow.up") } } } .onAppear { generateQRCodeImage() } .sheet(isPresented: $showingShareSheet) { ShareSheet(activityItems: [historyItem.content ?? ""]) } .alert("tip".localized, isPresented: $showingAlert) { Button("confirm".localized) { } } message: { Text(alertMessage) } .background( NavigationLink( destination: QRCodeStyleView( qrCodeContent: historyItem.content ?? "", qrCodeType: getQRCodeType(), existingStyleData: getStyleData(), historyItemId: historyItem.id?.uuidString ), isActive: $navigateToStyleView ) { EmptyView() } ) } // MARK: - 二维码图片视图 private var qrCodeImageView: some View { VStack(spacing: 16) { if let qrCodeImage = qrCodeImage { Image(uiImage: qrCodeImage) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 200, height: 200) .cornerRadius(12) .shadow(radius: 8) } else { RoundedRectangle(cornerRadius: 12) .fill(Color.gray.opacity(0.3)) .frame(width: 200, height: 200) .overlay( ProgressView() .scaleEffect(1.5) ) } Text("scan_this_qr_code".localized) .font(.caption) .foregroundColor(.secondary) } } // MARK: - 解析后的详细信息 private var parsedInfoSection: some View { VStack(alignment: .leading, spacing: 12) { if let content = historyItem.content { let parsedData = QRCodeParser.parseQRCode(content) HStack { Image(systemName: parsedData.icon) .font(.title3) .foregroundColor(.green) Text(parsedData.title) .font(.title3) .fontWeight(.medium) Spacer() } // 根据类型显示不同的详细信息 if parsedData.type == .vcard || parsedData.type == .mecard { // 联系人信息详细显示 contactInfoDetailView(parsedData: parsedData) } else if parsedData.type == .mail { // Email信息详细显示 emailInfoDetailView(parsedData: parsedData) } else if parsedData.type == .calendar { // Calendar信息详细显示 calendarInfoDetailView(parsedData: parsedData) } else if parsedData.type == .sms { // SMS信息详细显示 smsInfoDetailView(parsedData: parsedData) } else { // 其他类型的标准显示 VStack(alignment: .leading, spacing: 8) { if let subtitle = parsedData.subtitle { Text(subtitle) .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .leading) } } .padding() .background(Color.green.opacity(0.1)) .cornerRadius(8) } } } .padding() .background(Color(.systemBackground)) .cornerRadius(12) .shadow(radius: 2) } // MARK: - 联系人信息详细视图 private func contactInfoDetailView(parsedData: ParsedQRData) -> some View { VStack(alignment: .leading, spacing: 12) { if let contactInfo = getContactInfoFromParsedData(parsedData) { // 姓名 if !contactInfo.name.isEmpty { contactInfoRow(icon: "person.fill", title: "name".localized, value: contactInfo.name) } // 电话 if !contactInfo.phoneNumber.isEmpty { contactInfoRow(icon: "phone.fill", title: "phone".localized, value: contactInfo.phoneNumber) } // 邮箱 if !contactInfo.email.isEmpty { contactInfoRow(icon: "envelope.fill", title: "email".localized, value: contactInfo.email) } // 公司 if !contactInfo.organization.isEmpty { contactInfoRow(icon: "building.2.fill", title: "organization".localized, value: contactInfo.organization) } // 职位 if !contactInfo.title.isEmpty { contactInfoRow(icon: "briefcase.fill", title: "title".localized, value: contactInfo.title) } // 地址 if !contactInfo.address.isEmpty { contactInfoRow(icon: "location.fill", title: "address".localized, value: contactInfo.address) } } else { // 如果无法解析联系人信息,显示原始内容 VStack(alignment: .leading, spacing: 8) { if let subtitle = parsedData.subtitle { Text(subtitle) .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .leading) } } .padding() .background(Color.green.opacity(0.1)) .cornerRadius(8) } } } // MARK: - Email信息详细视图 private func emailInfoDetailView(parsedData: ParsedQRData) -> some View { VStack(alignment: .leading, spacing: 12) { if let emailDetails = getEmailDetails(parsedData: parsedData) { // 邮箱地址 if !emailDetails.emailAddress.isEmpty { contactInfoRow(icon: "envelope.fill", title: "email_address".localized, value: emailDetails.emailAddress) } // 主题 if !emailDetails.subject.isEmpty { contactInfoRow(icon: "text.bubble.fill", title: "email_subject".localized, value: emailDetails.subject) } // 内容 if !emailDetails.body.isEmpty { contactInfoRow(icon: "doc.text.fill", title: "email_body".localized, value: emailDetails.body) } } else { // 如果无法解析Email信息,显示原始内容 VStack(alignment: .leading, spacing: 8) { if let subtitle = parsedData.subtitle { Text(subtitle) .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .leading) } } .padding() .background(Color.blue.opacity(0.1)) .cornerRadius(8) } } } // MARK: - Calendar信息详细视图 private func calendarInfoDetailView(parsedData: ParsedQRData) -> some View { VStack(alignment: .leading, spacing: 12) { if let calendarDetails = getCalendarDetails(parsedData: parsedData) { // 事件标题 if !calendarDetails.summary.isEmpty { contactInfoRow(icon: "calendar.badge.plus", title: "calendar_event_title".localized, value: calendarDetails.summary) } // 开始时间 if !calendarDetails.startTime.isEmpty { let formattedStartTime = formatCalendarTime(calendarDetails.startTime) contactInfoRow(icon: "clock.fill", title: "calendar_start_time".localized, value: formattedStartTime) } // 结束时间 if !calendarDetails.endTime.isEmpty { let formattedEndTime = formatCalendarTime(calendarDetails.endTime) contactInfoRow(icon: "clock.badge.checkmark.fill", title: "calendar_end_time".localized, value: formattedEndTime) } // 地点 if !calendarDetails.location.isEmpty { contactInfoRow(icon: "location.fill", title: "calendar_location".localized, value: calendarDetails.location) } // 描述 if !calendarDetails.description.isEmpty { contactInfoRow(icon: "text.bubble.fill", title: "calendar_description".localized, value: calendarDetails.description) } } else { // 如果无法解析Calendar信息,显示原始内容 VStack(alignment: .leading, spacing: 8) { if let subtitle = parsedData.subtitle { Text(subtitle) .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .leading) } } .padding() .background(Color.orange.opacity(0.1)) .cornerRadius(8) } } } // MARK: - SMS信息详细视图 private func smsInfoDetailView(parsedData: ParsedQRData) -> some View { VStack(alignment: .leading, spacing: 12) { if let smsDetails = getSMSDetails(parsedData: parsedData) { // 电话号码 if !smsDetails.phoneNumber.isEmpty { contactInfoRow(icon: "phone.fill", title: "sms_phone_number".localized, value: smsDetails.phoneNumber) } // 短信内容 if !smsDetails.message.isEmpty { contactInfoRow(icon: "message.fill", title: "sms_message".localized, value: smsDetails.message) } } else { // 如果无法解析SMS信息,显示原始内容 VStack(alignment: .leading, spacing: 8) { if let subtitle = parsedData.subtitle { Text(subtitle) .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .leading) } } .padding() .background(Color.purple.opacity(0.1)) .cornerRadius(8) } } } // MARK: - 联系人信息行 private func contactInfoRow(icon: String, title: String, value: String) -> some View { HStack(alignment: .top, spacing: 12) { Image(systemName: icon) .font(.system(size: 16, weight: .medium)) .foregroundColor(.blue) .frame(width: 20, height: 20) .padding(.top, 2) VStack(alignment: .leading, spacing: 4) { Text(title) .font(.caption) .foregroundColor(.secondary) .textCase(.uppercase) Text(value) .font(.body) .foregroundColor(.primary) .multilineTextAlignment(.leading) } Spacer() } .padding(.vertical, 8) .padding(.horizontal, 16) .background(Color(.systemGray6)) .cornerRadius(8) } // MARK: - 原始内容 private var originalContentSection: some View { VStack(alignment: .leading, spacing: 12) { HStack { Image(systemName: "doc.text") .font(.title2) .foregroundColor(.purple) Text("original_content".localized) .font(.headline) Spacer() } if let content = historyItem.content { ScrollView { Text(content) .font(.system(.body, design: .monospaced)) .foregroundColor(.secondary) .multilineTextAlignment(.leading) .padding() .frame(maxWidth: .infinity, alignment: .leading) } .frame(maxHeight: 200) .background(Color.purple.opacity(0.1)) .cornerRadius(8) } } .padding() .background(Color(.systemBackground)) .cornerRadius(12) .shadow(radius: 2) } // MARK: - 二维码样式信息 private var qrCodeStyleSection: some View { VStack(spacing: 0) { if let styleData = getStyleData() { // 使用样式生成二维码预览 VStack(spacing: 16) { // 样式预览二维码 if let previewImage = generateStylePreviewImage(styleData: styleData) { Image(uiImage: previewImage) .resizable() .aspectRatio(contentMode: .fit) .frame(maxWidth: 200, maxHeight: 200) .padding() .background(Color.white) .cornerRadius(12) .shadow(radius: 4) } // 样式标签 HStack(spacing: 8) { Label("custom".localized, systemImage: "paintpalette") .font(.caption) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.purple.opacity(0.2)) .foregroundColor(.purple) .cornerRadius(6) } } .padding() .background(Color.purple.opacity(0.05)) .cornerRadius(12) } else { // 标准样式预览 VStack(spacing: 16) { if let standardImage = generateStandardQRCodeImage() { Image(uiImage: standardImage) .resizable() .aspectRatio(contentMode: .fit) .frame(maxWidth: 200, maxHeight: 200) .padding() .background(Color.white) .cornerRadius(12) .shadow(radius: 4) } Label("standard".localized, systemImage: "qrcode") .font(.caption) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.gray.opacity(0.2)) .foregroundColor(.gray) .cornerRadius(6) } .padding() .background(Color.gray.opacity(0.05)) .cornerRadius(12) } } .padding() } // MARK: - 操作按钮辅助函数 private func actionButton(icon: String, title: String, color: Color, action: @escaping () -> Void) -> some View { Button(action: action) { VStack(spacing: 6) { Image(systemName: icon) .font(.system(size: 22, weight: .semibold)) .foregroundColor(color) Text(title) .font(.caption) .foregroundColor(color) .lineLimit(2) .multilineTextAlignment(.center) } .frame(width: 70, height: 70) .background(color.opacity(0.08)) .cornerRadius(16) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(color.opacity(0.2), lineWidth: 1) ) } .buttonStyle(PlainButtonStyle()) } // MARK: - 操作按钮 private var actionButtonsSection: some View { Group { if let content = historyItem.content { let parsedData = QRCodeParser.parseQRCode(content) // 使用流布局来排列按钮 FlowLayoutView(spacing: 20) { // 通用按钮 actionButton( icon: historyItem.isFavorite ? "heart.fill" : "heart", title: "favorite".localized, color: historyItem.isFavorite ? .red : .gray, action: toggleFavorite ) actionButton( icon: "doc.on.doc", title: "copy".localized, color: .blue, action: copyContent ) // 根据QR码类型添加特定按钮 if parsedData.type == .wifi { actionButton( icon: "doc.on.doc.fill", title: "copy_password".localized, color: .orange, action: copyWiFiPassword ) actionButton( icon: "link", title: "connect_wifi".localized, color: .purple, action: setupWiFi ) } else if parsedData.type == .vcard || parsedData.type == .mecard { let contactInfo = getContactInfoFromParsedData(parsedData) actionButton( icon: "person.badge.plus", title: "add_contact".localized, color: .blue, action: addContact ) if let contactInfo = contactInfo, contactInfo.hasPhoneNumber { actionButton( icon: "phone", title: "call".localized, color: .green, action: { makePhoneCall(phoneNumber: contactInfo.phoneNumber) } ) actionButton( icon: "message", title: "send_sms".localized, color: .purple, action: { sendSMS(phoneNumber: contactInfo.phoneNumber) } ) } } else if parsedData.type == .phone { // 从电话URL中提取电话号码 let phoneNumber = content.replacingOccurrences(of: "tel:", with: "", options: .caseInsensitive) if !phoneNumber.isEmpty { actionButton( icon: "phone", title: "call".localized, color: .green, action: { makePhoneCall(phoneNumber: phoneNumber) } ) actionButton( icon: "message", title: "send_sms".localized, color: .purple, action: { sendSMS(phoneNumber: phoneNumber) } ) } } else if parsedData.type == .mail { // 从邮件URL中提取邮箱地址 let emailAddress = content.replacingOccurrences(of: "mailto:", with: "", options: .caseInsensitive) if !emailAddress.isEmpty { actionButton( icon: "envelope", title: "send_email".localized, color: .blue, action: { sendEmail(emailAddress: emailAddress) } ) } } else if parsedData.type == .sms { let smsDetails = getSMSDetails() if let smsDetails = smsDetails, !smsDetails.phoneNumber.isEmpty { actionButton( icon: "message", title: "send_sms".localized, color: .purple, action: { sendSMS(phoneNumber: smsDetails.phoneNumber, message: smsDetails.message) } ) } } else if parsedData.type == .calendar { actionButton( icon: "calendar.badge.plus", title: "add_to_calendar".localized, color: .orange, action: addEventToCalendar ) } else if parsedData.type == .instagram { actionButton( icon: "camera", title: "open".localized, color: .purple, action: { openSocialApp(content: content, appType: .instagram) } ) } else if parsedData.type == .facebook { actionButton( icon: "person.2", title: "open".localized, color: .blue, action: { openSocialApp(content: content, appType: .facebook) } ) } else if parsedData.type == .twitter { actionButton( icon: "bird", title: "open".localized, color: .black, action: { openSocialApp(content: content, appType: .twitter) } ) } else if parsedData.type == .whatsapp { actionButton( icon: "message.circle", title: "open".localized, color: .green, action: { openSocialApp(content: content, appType: .whatsapp) } ) } else if parsedData.type == .viber { actionButton( icon: "message.circle.fill", title: "open".localized, color: .purple, action: { openSocialApp(content: content, appType: .viber) } ) } else if parsedData.type == .spotify { actionButton( icon: "music.note", title: "open".localized, color: .green, action: { openSocialApp(content: content, appType: .spotify) } ) } else if canOpenURL(content) { actionButton( icon: "arrow.up.right.square", title: "open_link".localized, color: .green, action: { openURL(content) } ) } } } else { // 没有内容时只显示通用按钮 FlowLayoutView(spacing: 20) { actionButton( icon: historyItem.isFavorite ? "heart.fill" : "heart", title: "favorite".localized, color: historyItem.isFavorite ? .red : .gray, action: toggleFavorite ) actionButton( icon: "doc.on.doc", title: "copy".localized, color: .blue, action: copyContent ) } } } .padding() .background(Color(.systemBackground)) .cornerRadius(12) .shadow(radius: 2) } // MARK: - 生成二维码图片 private func generateQRCodeImage() { guard let content = historyItem.content else { return } do { let imageData = try QRCode.build .text(content) .quietZonePixelCount(3) .foregroundColor(CGColor(srgbRed: 1, green: 0, blue: 0.6, alpha: 1)) .backgroundColor(CGColor(srgbRed: 0, green: 0, blue: 0.2, alpha: 1)) .background.cornerRadius(3) .onPixels.shape(QRCode.PixelShape.CurvePixel()) .eye.shape(QRCode.EyeShape.Teardrop()) .generate.image(dimension: 600, representation: .png()) self.qrCodeImage = UIImage(data: imageData) } catch { print("生成二维码失败: \(error)") } } // MARK: - 切换收藏状态 private func toggleFavorite() { historyItem.isFavorite.toggle() coreDataManager.save() let message = historyItem.isFavorite ? "added_to_favorites".localized : "removed_from_favorites".localized alertMessage = message showingAlert = true } // MARK: - 复制内容 private func copyContent() { if let content = historyItem.content { UIPasteboard.general.string = content alertMessage = "content_copied_to_clipboard".localized showingAlert = true } } // MARK: - 检查是否可以打开URL private func canOpenURL(_ string: String) -> Bool { guard let url = URL(string: string) else { return false } return UIApplication.shared.canOpenURL(url) } // MARK: - 打开URL private func openURL(_ string: String) { guard let url = URL(string: string) else { return } UIApplication.shared.open(url) } // MARK: - 获取WiFi详细信息 private func getWiFiDetails() -> WiFiDetails? { guard let content = historyItem.content else { return nil } let parsedData = QRCodeParser.parseQRCode(content) guard parsedData.type == .wifi, let extraData = parsedData.extraData else { return nil } return try? JSONDecoder().decode(WiFiDetails.self, from: extraData) } // MARK: - 获取SMS详细信息 private func getSMSDetails() -> SMSDetails? { guard let content = historyItem.content else { return nil } let parsedData = QRCodeParser.parseQRCode(content) guard parsedData.type == .sms, let extraData = parsedData.extraData else { return nil } return try? JSONDecoder().decode(SMSDetails.self, from: extraData) } // MARK: - 获取日历详细信息 private func getCalendarDetails() -> CalendarDetails? { guard let content = historyItem.content else { return nil } let parsedData = QRCodeParser.parseQRCode(content) guard parsedData.type == .calendar, let extraData = parsedData.extraData else { return nil } return try? JSONDecoder().decode(CalendarDetails.self, from: extraData) } // MARK: - 复制WiFi密码 private func copyWiFiPassword() { guard let wifiDetails = getWiFiDetails() else { return } UIPasteboard.general.string = wifiDetails.password alertMessage = "wifi_password_copied".localized showingAlert = true } // MARK: - 设置WiFi private func setupWiFi() { guard let wifiDetails = getWiFiDetails() else { return } // 使用WiFi连接管理器 WiFiConnectionManager.shared.connectWithFallback(ssid: wifiDetails.ssid, password: wifiDetails.password) { success, error in DispatchQueue.main.async { if success { self.alertMessage = "wifi_connected_successfully".localized } else { self.alertMessage = error ?? "wifi_connection_failed".localized } self.showingAlert = true } } } // MARK: - 添加联系人 private func addContact() { guard let content = historyItem.content else { return } ContactManager.shared.addContactToAddressBook(vcardContent: content) { success, error in DispatchQueue.main.async { if success { self.alertMessage = "contact_added_successfully".localized } else { self.alertMessage = error ?? "contact_add_failed".localized } self.showingAlert = true } } } // MARK: - 打电话 private func makePhoneCall(phoneNumber: String) { ContactManager.shared.makePhoneCall(phoneNumber: phoneNumber) { success, error in DispatchQueue.main.async { if !success { self.alertMessage = error ?? "phone_call_failed".localized self.showingAlert = true } } } } // MARK: - 发短信 private func sendSMS(phoneNumber: String, message: String = "") { ContactManager.shared.sendSMS(phoneNumber: phoneNumber, message: message) { success, error in DispatchQueue.main.async { if !success { self.alertMessage = error ?? "sms_app_failed".localized self.showingAlert = true } } } } // MARK: - 发送邮件 private func sendEmail(emailAddress: String) { let mailtoURL = "mailto:\(emailAddress)" if let url = URL(string: mailtoURL), UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url) } else { alertMessage = "email_app_failed".localized showingAlert = true } } // MARK: - 添加事件到日历 private func addEventToCalendar() { guard let calendarDetails = getCalendarDetails() else { return } CalendarManager.shared.addEventToCalendar(calendarDetails: calendarDetails) { success, error in DispatchQueue.main.async { if success { self.alertMessage = "calendar_event_added_successfully".localized } else { self.alertMessage = error ?? "calendar_event_add_failed".localized } self.showingAlert = true } } } // MARK: - 从解析数据中获取联系人信息 private func getContactInfoFromParsedData(_ parsedData: ParsedQRData) -> ContactInfo? { guard let extraData = parsedData.extraData else { return nil } do { let contactInfo = try JSONDecoder().decode(ContactInfo.self, from: extraData) return contactInfo } catch { print("解析联系人信息失败: \(error)") return nil } } // MARK: - 从解析数据中获取Email信息 private func getEmailDetails(parsedData: ParsedQRData) -> EmailDetails? { guard let extraData = parsedData.extraData else { return nil } do { let emailDetails = try JSONDecoder().decode(EmailDetails.self, from: extraData) return emailDetails } catch { print("解析Email信息失败: \(error)") return nil } } // MARK: - 从解析数据中获取Calendar信息 private func getCalendarDetails(parsedData: ParsedQRData) -> CalendarDetails? { guard let extraData = parsedData.extraData else { return nil } do { let calendarDetails = try JSONDecoder().decode(CalendarDetails.self, from: extraData) return calendarDetails } catch { print("解析Calendar信息失败: \(error)") return nil } } // MARK: - 从解析数据中获取SMS信息 private func getSMSDetails(parsedData: ParsedQRData) -> SMSDetails? { guard let extraData = parsedData.extraData else { return nil } do { let smsDetails = try JSONDecoder().decode(SMSDetails.self, from: extraData) return smsDetails } catch { print("解析SMS信息失败: \(error)") return nil } } // MARK: - 格式化日历时间 private func formatCalendarTime(_ timeString: String) -> String { guard timeString.count >= 15 else { return timeString } let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss" if let date = dateFormatter.date(from: timeString) { let displayFormatter = DateFormatter() displayFormatter.dateStyle = .medium displayFormatter.timeStyle = .short return displayFormatter.string(from: date) } return timeString } } // MARK: - 分享表单 struct ShareSheet: UIViewControllerRepresentable { let activityItems: [Any] func makeUIViewController(context: Context) -> UIActivityViewController { let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) return controller } func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} } #Preview("Wi‑Fi") { let ctx = PreviewData.context let item = PreviewData.wifiSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("URL") { let ctx = PreviewData.context let item = PreviewData.urlSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("SMS") { let ctx = PreviewData.context let item = PreviewData.smsSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("vCard") { let ctx = PreviewData.context let item = PreviewData.vcardSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("Instagram") { let ctx = PreviewData.context let item = PreviewData.instagramSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("WhatsApp") { let ctx = PreviewData.context let item = PreviewData.whatsappSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("Viber") { let ctx = PreviewData.context let item = PreviewData.viberSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("Text") { let ctx = PreviewData.context let item = PreviewData.textSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("MeCard") { let ctx = PreviewData.context let item = PreviewData.mecardSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("Calendar") { let ctx = PreviewData.context let item = PreviewData.calendarSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } #Preview("Email") { let ctx = PreviewData.context let item = PreviewData.emailSample(in: ctx) NavigationView { QRCodeDetailView(historyItem: item) } } // MARK: - Preview Data private enum PreviewData { static let context: NSManagedObjectContext = { let container = NSPersistentContainer(name: "MyQrCode") let description = NSPersistentStoreDescription() description.type = NSInMemoryStoreType container.persistentStoreDescriptions = [description] container.loadPersistentStores { _, _ in } return container.viewContext }() private static func makeBaseItem(in context: NSManagedObjectContext, content: String, qrType: QRCodeType, favorite: Bool = false) -> HistoryItem { let item = HistoryItem(context: context) item.id = UUID() item.content = content item.dataType = DataType.qrcode.rawValue item.dataSource = DataSource.created.rawValue item.createdAt = Date() item.isFavorite = favorite item.qrCodeType = qrType.rawValue return item } static func wifiSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "WIFI:T:WPA;S:MyNetwork;P:MyPassword;;" return makeBaseItem(in: context, content: content, qrType: .wifi, favorite: true) } static func urlSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "https://www.example.com" return makeBaseItem(in: context, content: content, qrType: .url) } static func smsSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "SMSTO:+1 (555) 123-4567:Hello" return makeBaseItem(in: context, content: content, qrType: .sms) } static func vcardSample(in context: NSManagedObjectContext) -> HistoryItem { let content = """ BEGIN:VCARD VERSION:3.0 N:Doe;John;;; FN:John Doe TEL;TYPE=WORK,CELL:(123) 456-7890 EMAIL;TYPE=PREF,INTERNET:john.doe@example.com ORG:Example Company TITLE:Software Engineer ADR;TYPE=WORK:;;123 Main St;Anytown;CA;12345;USA URL:https://example.com END:VCARD """.trimmingCharacters(in: .whitespacesAndNewlines) return makeBaseItem(in: context, content: content, qrType: .vcard) } static func instagramSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "instagram://user?username=example_user" return makeBaseItem(in: context, content: content, qrType: .instagram) } static func whatsappSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "whatsapp://send?phone=+1234567890" return makeBaseItem(in: context, content: content, qrType: .whatsapp) } static func textSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "Hello, this is a text message!" return makeBaseItem(in: context, content: content, qrType: .text) } static func viberSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "viber://add?number=+1234567890" return makeBaseItem(in: context, content: content, qrType: .viber) } static func mecardSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "MECARD:N:Doe,John;NICKNAME:Johnny;TEL:+1234567890;EMAIL:john.doe@example.com;ORG:Example Company;TITLE:Software Engineer;ADR:123 Main St,Anytown,CA,12345,USA;URL:https://example.com;NOTE:This is a note;" return makeBaseItem(in: context, content: content, qrType: .mecard) } static func calendarSample(in context: NSManagedObjectContext) -> HistoryItem { let content = """ BEGIN:VEVENT SUMMARY:团队会议 DTSTART:20241201T140000 DTEND:20241201T150000 LOCATION:会议室A DESCRIPTION:讨论项目进度和下一步计划 END:VEVENT """.trimmingCharacters(in: .whitespacesAndNewlines) return makeBaseItem(in: context, content: content, qrType: .calendar) } static func emailSample(in context: NSManagedObjectContext) -> HistoryItem { let content = "mailto:example@email.com?subject=Hello&body=This is a test email message with some content to demonstrate the email QR code functionality." return makeBaseItem(in: context, content: content, qrType: .mail) } } // MARK: - 样式信息辅助方法 extension QRCodeDetailView { // MARK: - 生成样式预览图片 private func generateStylePreviewImage(styleData: QRCodeStyleData) -> UIImage? { guard let content = historyItem.content else { return nil } do { var qrCodeBuilder = try QRCode.build .text(content) .quietZonePixelCount(0) // 设置前景色 if let foregroundColor = getColorFromString(styleData.foregroundColor) { qrCodeBuilder = qrCodeBuilder.foregroundColor(foregroundColor) } // 设置背景色 if let backgroundColor = getColorFromString(styleData.backgroundColor) { qrCodeBuilder = qrCodeBuilder.backgroundColor(backgroundColor) } // 设置背景圆角 qrCodeBuilder = qrCodeBuilder.background.cornerRadius(3) // 设置点类型 if let dotType = getDotTypeFromString(styleData.dotType) { qrCodeBuilder = qrCodeBuilder.onPixels.shape(dotType) } // 设置眼睛类型 if let eyeType = getEyeTypeFromString(styleData.eyeType) { qrCodeBuilder = qrCodeBuilder.eye.shape(eyeType) } // 设置Logo(如果有的话) if let logo = styleData.logo, !logo.isEmpty { if let logoTemplate = getLogoFromString(logo) { qrCodeBuilder = qrCodeBuilder.logo(logoTemplate) } } // 生成图片 let imageData = try qrCodeBuilder.generate.image(dimension: 300, representation: .png()) return UIImage(data: imageData) } catch { print("生成样式预览图片失败: \(error)") return nil } } // MARK: - 生成标准二维码图片 private func generateStandardQRCodeImage() -> UIImage? { guard let content = historyItem.content else { return nil } do { let imageData = try QRCode.build .text(content) .quietZonePixelCount(0) .foregroundColor(CGColor(srgbRed: 0, green: 0, blue: 0, alpha: 1)) .backgroundColor(CGColor(srgbRed: 1, green: 1, blue: 1, alpha: 1)) .generate.image(dimension: 300, representation: .png()) return UIImage(data: imageData) } catch { print("生成标准二维码图片失败: \(error)") return nil } } // MARK: - 颜色转换 private func getColorFromString(_ colorString: String) -> CGColor? { if let color = QRCodeColor(rawValue: colorString) { return color.cgColor } return nil } // MARK: - 点类型转换 private func getDotTypeFromString(_ dotTypeString: String) -> QRCodePixelShapeGenerator? { if let dotType = QRCodeDotType(rawValue: dotTypeString) { return dotType.pixelShape } return nil } // MARK: - 眼睛类型转换 private func getEyeTypeFromString(_ eyeTypeString: String) -> QRCodeEyeShapeGenerator? { if let eyeType = QRCodeEyeType(rawValue: eyeTypeString) { return eyeType.eyeShape } return nil } // MARK: - Logo转换 private func getLogoFromString(_ logoString: String) -> QRCode.LogoTemplate? { // 检查是否是自定义Logo if logoString.hasPrefix("custom_") { // 从样式数据中获取自定义Logo图片 if let styleData = getStyleData(), let customImage = styleData.customLogoImage, let cgImage = customImage.cgImage { return QRCode.LogoTemplate.CircleCenter(image: cgImage, inset: 0) } } else { // 预设Logo if let logo = QRCodeLogo(rawValue: logoString), let image = logo.image, let cgImage = image.cgImage { return QRCode.LogoTemplate.CircleCenter(image: cgImage) } } return nil } // MARK: - 从JSON字符串解析样式数据 private func getStyleData() -> QRCodeStyleData? { guard let jsonString = historyItem.qrCodeStyleData, let jsonData = jsonString.data(using: .utf8) else { return nil } do { let styleData = try JSONDecoder().decode(QRCodeStyleData.self, from: jsonData) return styleData } catch { print("❌ 样式数据JSON解码失败:\(error)") return nil } } // MARK: - 显示名称转换方法(保留原有方法) private func getColorDisplayName(_ colorString: String) -> String { if let color = QRCodeColor(rawValue: colorString) { switch color { case .black: return "black".localized case .white: return "white".localized case .red: return "red".localized case .blue: return "blue".localized case .green: return "green".localized case .yellow: return "yellow".localized case .purple: return "purple".localized case .orange: return "orange".localized case .pink: return "pink".localized case .cyan: return "cyan".localized case .magenta: return "magenta".localized case .brown: return "brown".localized case .gray: return "gray".localized case .navy: return "navy".localized case .teal: return "teal".localized case .indigo: return "indigo".localized case .lime: return "lime".localized case .maroon: return "maroon".localized case .olive: return "olive".localized case .silver: return "silver".localized } } return colorString } private func getDotTypeDisplayName(_ dotTypeString: String) -> String { if let dotType = QRCodeDotType(rawValue: dotTypeString) { return dotType.displayName } return dotTypeString } private func getEyeTypeDisplayName(_ eyeTypeString: String) -> String { if let eyeType = QRCodeEyeType(rawValue: eyeTypeString) { return eyeType.displayName } return eyeTypeString } private func getLogoDisplayName(_ logoString: String) -> String { if let logo = QRCodeLogo(rawValue: logoString) { return logo.displayName } return logoString } // MARK: - 获取二维码类型 private func getQRCodeType() -> QRCodeType { if let qrCodeTypeString = historyItem.qrCodeType, let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) { return qrCodeType } return .text // 默认返回text类型 } // MARK: - 获取导航标题 private func getNavigationTitle() -> String { if let qrCodeTypeString = historyItem.qrCodeType, let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) { // vCard和MCard类型统一使用Contact标题 if qrCodeType == .vcard || qrCodeType == .mecard { return "contact".localized } return qrCodeType.displayName } return "qr_code_detail".localized } // MARK: - Decorate code按钮 private var decorateCodeButton: some View { VStack(spacing: 16) { Button(action: { navigateToCustomStyle() }) { HStack(spacing: 12) { Image(systemName: "paintpalette.fill") .font(.title2) .foregroundColor(.white) Text("decorate_code".localized) .font(.headline) .fontWeight(.semibold) .foregroundColor(.white) Spacer() Image(systemName: "chevron.right") .font(.system(size: 14, weight: .medium)) .foregroundColor(.white.opacity(0.8)) } .padding(.horizontal, 20) .padding(.vertical, 16) .background( LinearGradient( gradient: Gradient(colors: [Color.purple, Color.blue]), startPoint: .leading, endPoint: .trailing ) ) .cornerRadius(12) .shadow(color: .purple.opacity(0.3), radius: 8, x: 0, y: 4) } .buttonStyle(PlainButtonStyle()) // 如果有现有样式,显示提示 if getStyleData() != nil { HStack { Image(systemName: "info.circle.fill") .font(.caption) .foregroundColor(.orange) Text("qr_code_has_style".localized) .font(.caption) .foregroundColor(.secondary) Spacer() } .padding(.horizontal, 4) } } .padding() .cornerRadius(12) .shadow(radius: 2) } // MARK: - 社交应用类型枚举 private enum SocialAppType { case instagram case facebook case twitter case whatsapp case viber case spotify } // MARK: - 打开社交应用 private func openSocialApp(content: String, appType: SocialAppType) { // 直接使用原始链接打开 if let url = URL(string: content), UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url) { success in if !success { // 如果无法打开,显示错误提示 DispatchQueue.main.async { self.alertMessage = "app_open_failed".localized self.showingAlert = true } } } } else { // 如果URL无效,显示错误提示 alertMessage = "app_open_failed".localized showingAlert = true } } // MARK: - 跳转到自定义样式界面 private func navigateToCustomStyle() { navigateToStyleView = true } } // MARK: - FlowLayout 流布局组件 struct FlowLayoutView: View { let spacing: CGFloat let content: Content init(spacing: CGFloat = 20, @ViewBuilder content: () -> Content) { self.spacing = spacing self.content = content() } var body: some View { LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: spacing), count: 4), spacing: spacing) { content } } }