|
|
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("create".localized) {
|
|
|
if canCreateQRCode() {
|
|
|
navigateToStyleView = true
|
|
|
}
|
|
|
}
|
|
|
.disabled(!canCreateQRCode())
|
|
|
.font(.system(size: 16, weight: .semibold))
|
|
|
}
|
|
|
}
|
|
|
.alert("tip".localized, isPresented: $showingAlert) {
|
|
|
Button("confirm".localized) { }
|
|
|
} message: { Text(alertMessage) }
|
|
|
.background(
|
|
|
NavigationLink(
|
|
|
destination: QRCodeStyleView(qrCodeContent: generateQRCodeContent(), qrCodeType: selectedQRCodeType, existingStyleData: nil, historyItem: nil),
|
|
|
isActive: $navigateToStyleView
|
|
|
) {
|
|
|
EmptyView()
|
|
|
}
|
|
|
)
|
|
|
.onChange(of: navigateToStyleView) { newValue in
|
|
|
if newValue {
|
|
|
// 记录二维码生成事件
|
|
|
FacebookEventManager.shared.logQRCodeGeneration(type: selectedQRCodeType.rawValue)
|
|
|
}
|
|
|
}
|
|
|
.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: "email_content_format".localized, emailAddress, emailSubject, emailBody)
|
|
|
if !emailCc.isEmpty {
|
|
|
mailContent += String(format: "email_cc_format".localized, emailCc)
|
|
|
}
|
|
|
if !emailBcc.isEmpty {
|
|
|
mailContent += String(format: "email_bcc_format".localized, emailBcc)
|
|
|
}
|
|
|
historyItem.content = mailContent
|
|
|
case .wifi:
|
|
|
historyItem.content = String(format: "wifi_content_format".localized, wifiSSID, wifiEncryptionType.displayName)
|
|
|
case .vcard, .mecard:
|
|
|
var contactContent = "contact_content_prefix".localized
|
|
|
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
|
|
|
contactContent += "\(contactFirstName) \(contactLastName)"
|
|
|
}
|
|
|
if !contactNickname.isEmpty {
|
|
|
contactContent += String(format: "contact_nickname_format".localized, contactNickname)
|
|
|
}
|
|
|
if !contactPhone.isEmpty {
|
|
|
contactContent += String(format: "contact_phone_format".localized, contactPhone)
|
|
|
}
|
|
|
if !contactEmail.isEmpty {
|
|
|
contactContent += String(format: "contact_email_format".localized, contactEmail)
|
|
|
}
|
|
|
if !contactCompany.isEmpty {
|
|
|
contactContent += String(format: "contact_company_format".localized, contactCompany)
|
|
|
}
|
|
|
if !contactTitle.isEmpty {
|
|
|
contactContent += String(format: "contact_title_format".localized, contactTitle)
|
|
|
}
|
|
|
if !contactAddress.isEmpty {
|
|
|
contactContent += String(format: "contact_address_format".localized, contactAddress)
|
|
|
}
|
|
|
if !contactWebsite.isEmpty {
|
|
|
contactContent += String(format: "contact_website_format".localized, contactWebsite)
|
|
|
}
|
|
|
if !contactNote.isEmpty {
|
|
|
contactContent += String(format: "contact_note_format".localized, contactNote)
|
|
|
}
|
|
|
historyItem.content = contactContent
|
|
|
case .location:
|
|
|
historyItem.content = String(format: "location_content_format".localized, locationLatitude, locationLongitude)
|
|
|
case .calendar:
|
|
|
historyItem.content = String(format: "calendar_content_format".localized, eventTitle)
|
|
|
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber:
|
|
|
historyItem.content = generateSocialMediaContent()
|
|
|
case .phone, .sms:
|
|
|
historyItem.content = String(format: "phone_content_format".localized, phoneNumber)
|
|
|
case .url:
|
|
|
historyItem.content = String(format: "url_content_format".localized, urlString)
|
|
|
default:
|
|
|
historyItem.content = content
|
|
|
}
|
|
|
|
|
|
do {
|
|
|
try context.save()
|
|
|
alertMessage = "qrcode_created_successfully".localized
|
|
|
showingAlert = true
|
|
|
// 创建成功后返回
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
|
|
dismiss()
|
|
|
}
|
|
|
} catch {
|
|
|
alertMessage = String(format: "save_failed_error".localized, 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[..<questionMarkIndex])
|
|
|
}
|
|
|
if let slashIndex = cleanedInput.firstIndex(of: "/") {
|
|
|
cleanedInput = String(cleanedInput[..<slashIndex])
|
|
|
}
|
|
|
|
|
|
return cleanedInput.isEmpty ? "unknown" : cleanedInput
|
|
|
}
|
|
|
|
|
|
// 如果输入的不是链接,直接返回(可能是用户名或ID)
|
|
|
return input
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#Preview {
|
|
|
NavigationView {
|
|
|
CreateQRCodeView(selectedQRCodeType: .spotify)
|
|
|
}
|
|
|
}
|