|
|
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<Content: View>: 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
|
|
|
}
|
|
|
}
|
|
|
}
|