You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

479 lines
18 KiB

import SwiftUI
import CoreData
import CoreImage
// MARK: -
struct CreateQRCodeView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var coreDataManager = CoreDataManager.shared
//
let selectedQRCodeType: QRCodeType
//
@State private var content = ""
@FocusState private var isContentFieldFocused: Bool
// Email
@State private var emailAddress = ""
@State private var emailSubject = ""
@State private var emailBody = ""
@State private var emailCc = ""
@State private var emailBcc = ""
@FocusState private var focusedEmailField: EmailInputView.EmailField?
// WiFi
@State private var wifiSSID = ""
@State private var wifiPassword = ""
@State private var wifiEncryptionType: WiFiInputView.WiFiEncryptionType = .wpa2
@FocusState private var focusedWiFiField: WiFiInputView.WiFiField?
//
@State private var contactFirstName = ""
@State private var contactLastName = ""
@State private var contactPhone = ""
@State private var contactEmail = ""
@State private var contactCompany = ""
@State private var contactTitle = ""
@State private var contactAddress = ""
@State private var contactWebsite = ""
@FocusState private var focusedContactField: ContactInputView.ContactField?
//
@State private var locationLatitude = ""
@State private var locationLongitude = ""
@State private var locationName = ""
@FocusState private var focusedLocationField: LocationInputView.LocationField?
//
@State private var eventTitle = ""
@State private var eventDescription = ""
@State private var eventLocation = ""
@State private var startDate = Date()
@State private var endDate = Date().addingTimeInterval(3600)
@FocusState private var focusedCalendarField: CalendarInputView.CalendarField?
//
@State private var socialUsername = ""
@State private var socialMessage = ""
@FocusState private var focusedSocialField: SocialInputView.SocialField?
//
@State private var phoneNumber = ""
@State private var phoneMessage = ""
@FocusState private var focusedPhoneField: PhoneInputView.PhoneField?
// URL
@State private var urlString = ""
@FocusState private var isURLFieldFocused: Bool
//
@State private var showingAlert = false
@State private var alertMessage = ""
var body: some View {
VStack(spacing: 0) {
inputAndPreviewSection
}
.navigationTitle(selectedQRCodeType.displayName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("创建") { createQRCode() }
.disabled(!canCreateQRCode())
.font(.system(size: 16, weight: .semibold))
}
}
.alert("提示", isPresented: $showingAlert) {
Button("确定") { }
} message: { Text(alertMessage) }
.onAppear {
setupInitialFocus()
}
.onTapGesture {
hideKeyboard()
}
}
// MARK: - UI Components
private var inputAndPreviewSection: some View {
ScrollView {
VStack(spacing: 24) {
//
InputHintView.info(
hint: getContentHint()
)
.padding(.horizontal, 20)
//
VStack(spacing: 16) {
InputTitleView.required(
selectedQRCodeType == .mail ? "邮件信息" : "输入内容",
icon: getInputIcon()
)
.padding(.horizontal, 20)
// 使InputComponentFactory
InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
content: $content,
emailAddress: $emailAddress,
emailSubject: $emailSubject,
emailBody: $emailBody,
emailCc: $emailCc,
emailBcc: $emailBcc,
focusedEmailField: _focusedEmailField,
isContentFieldFocused: _isContentFieldFocused,
ssid: $wifiSSID,
password: $wifiPassword,
encryptionType: $wifiEncryptionType,
focusedWiFiField: _focusedWiFiField,
firstName: $contactFirstName,
lastName: $contactLastName,
phone: $contactPhone,
email: $contactEmail,
company: $contactCompany,
title: $contactTitle,
address: $contactAddress,
website: $contactWebsite,
focusedContactField: _focusedContactField,
latitude: $locationLatitude,
longitude: $locationLongitude,
locationName: $locationName,
focusedLocationField: _focusedLocationField,
eventTitle: $eventTitle,
eventDescription: $eventDescription,
startDate: $startDate,
endDate: $endDate,
location: $eventLocation,
focusedCalendarField: _focusedCalendarField,
username: $socialUsername,
message: $socialMessage,
focusedSocialField: _focusedSocialField,
phoneNumber: $phoneNumber,
phoneMessage: $phoneMessage,
focusedPhoneField: _focusedPhoneField,
url: $urlString,
isUrlFieldFocused: _isURLFieldFocused
)
.padding(.horizontal, 20)
}
//
if canCreateQRCode() {
VStack(spacing: 16) {
InputTitleView.required("预览", icon: "eye")
.padding(.horizontal, 20)
// 使QRCodePreviewView
QRCodePreviewView(
qrCodeImage: generateQRCodeImage(),
formattedContent: formatContentForQRCodeType(),
qrCodeType: selectedQRCodeType
)
.padding(.horizontal, 20)
}
}
Spacer(minLength: 100)
}
.padding(.top, 20)
}
.background(Color(.systemGroupedBackground))
}
// MARK: - Helper Methods
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: 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:
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 "sms:\(phoneNumber):\(smsContent)"
case .wifi:
return "WIFI:T:\(wifiEncryptionType.rawValue);S:\(wifiSSID);P:\(wifiPassword);;"
case .vcard:
var vcard = "BEGIN:VCARD\nVERSION:3.0\n"
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
vcard += "FN:\(contactFirstName) \(contactLastName)\n"
}
if !contactPhone.isEmpty {
vcard += "TEL:\(contactPhone)\n"
}
if !contactEmail.isEmpty {
vcard += "EMAIL:\(contactEmail)\n"
}
if !contactCompany.isEmpty {
vcard += "ORG:\(contactCompany)\n"
}
if !contactTitle.isEmpty {
vcard += "TITLE:\(contactTitle)\n"
}
if !contactAddress.isEmpty {
vcard += "ADR:\(contactAddress)\n"
}
if !contactWebsite.isEmpty {
vcard += "URL:\(contactWebsite)\n"
}
vcard += "END:VCARD"
return vcard
case .mecard:
var mecard = "MECARD:"
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
mecard += "N:\(contactLastName),\(contactFirstName);"
}
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);"
}
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 "https://instagram.com/\(socialUsername)"
case .facebook:
return "https://facebook.com/\(socialUsername)"
case .spotify:
return socialUsername.hasPrefix("http") ? socialUsername : "https://open.spotify.com/track/\(socialUsername)"
case .twitter:
return "https://twitter.com/\(socialUsername)"
case .whatsapp:
let message = socialMessage.isEmpty ? "Hello" : socialMessage
return "https://wa.me/\(socialUsername)?text=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? message)"
case .viber:
let message = socialMessage.isEmpty ? "Hello" : socialMessage
return "viber://chat?number=\(socialUsername)&text=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? message)"
case .snapchat:
return "https://snapchat.com/add/\(socialUsername)"
case .tiktok:
return "https://tiktok.com/@\(socialUsername)"
}
}
private func createQRCode() {
let context = coreDataManager.container.viewContext
let historyItem = HistoryItem(context: context)
historyItem.id = UUID()
historyItem.dataType = DataType.qrcode.rawValue
historyItem.dataSource = DataSource.created.rawValue
historyItem.createdAt = Date()
historyItem.isFavorite = false
historyItem.qrCodeType = selectedQRCodeType.rawValue
//
switch selectedQRCodeType {
case .mail:
var mailContent = "邮箱: \(emailAddress)\n主题: \(emailSubject)\n正文: \(emailBody)"
if !emailCc.isEmpty {
mailContent += "\n抄送: \(emailCc)"
}
if !emailBcc.isEmpty {
mailContent += "\n密送: \(emailBcc)"
}
historyItem.content = mailContent
case .wifi:
historyItem.content = "WiFi: \(wifiSSID) (\(wifiEncryptionType.displayName))"
case .vcard, .mecard:
historyItem.content = "联系人: \(contactFirstName) \(contactLastName)"
case .location:
historyItem.content = "位置: \(locationLatitude), \(locationLongitude)"
case .calendar:
historyItem.content = "事件: \(eventTitle)"
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok:
historyItem.content = "\(selectedQRCodeType.displayName): \(socialUsername)"
case .phone, .sms:
historyItem.content = "电话: \(phoneNumber)"
case .url:
historyItem.content = "URL: \(urlString)"
default:
historyItem.content = content
}
do {
try context.save()
alertMessage = "二维码创建成功!"
showingAlert = true
//
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
dismiss()
}
} catch {
alertMessage = "保存失败:\(error.localizedDescription)"
showingAlert = true
}
}
}
#Preview {
NavigationView {
CreateQRCodeView(selectedQRCodeType: .mail)
}
}