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.

812 lines
31 KiB

import SwiftUI
import CoreData
import CoreImage
// MARK: -
struct CreateQRCodeView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject var coreDataManager: CoreDataManager
//
let selectedQRCodeType: QRCodeType
//
@State private var content = ""
@FocusState private var isContentFieldFocused: Bool
// Email
@State private var emailAddress = ""
@State private var emailSubject = ""
@State private var emailBody = ""
@State private var emailCc = ""
@State private var emailBcc = ""
@FocusState private var focusedEmailField: EmailInputView.EmailField?
// WiFi
@State private var wifiSSID = ""
@State private var wifiPassword = ""
@State private var wifiEncryptionType: WiFiInputView.WiFiEncryptionType = .wpa2
@FocusState private var focusedWiFiField: WiFiInputView.WiFiField?
//
@State private var contactFirstName = ""
@State private var contactLastName = ""
@State private var contactPhone = ""
@State private var contactEmail = ""
@State private var contactCompany = ""
@State private var contactTitle = ""
@State private var contactAddress = ""
@State private var contactWebsite = ""
@State private var contactNickname = ""
@State private var contactBirthday = Date()
@State private var contactNote = ""
@FocusState private var focusedContactField: ContactInputView.ContactField?
//
@State private var locationLatitude = ""
@State private var locationLongitude = ""
@State private var locationName = ""
@FocusState private var focusedLocationField: LocationInputView.LocationField?
//
@State private var eventTitle = ""
@State private var eventDescription = ""
@State private var eventLocation = ""
@State private var startDate = Date()
@State private var endDate = Date().addingTimeInterval(3600)
@FocusState private var focusedCalendarField: CalendarInputView.CalendarField?
//
@State private var socialUsername = ""
@State private var socialMessage = ""
@FocusState private var focusedSocialField: SocialInputView.SocialField?
//
@State private var phoneNumber = ""
@State private var phoneMessage = ""
@FocusState private var focusedPhoneField: PhoneInputView.PhoneField?
// URL
@State private var urlString = ""
@FocusState private var isURLFieldFocused: Bool
//
@State private var showingAlert = false
@State private var alertMessage = ""
@State private var navigateToStyleView = false
var body: some View {
VStack(spacing: 0) {
inputAndPreviewSection
}
.navigationTitle(selectedQRCodeType.displayName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(NSLocalizedString("create", comment: "Create")) {
if canCreateQRCode() {
navigateToStyleView = true
}
}
.disabled(!canCreateQRCode())
.font(.system(size: 16, weight: .semibold))
}
}
.alert(NSLocalizedString("tip", comment: "Tip"), isPresented: $showingAlert) {
Button(NSLocalizedString("confirm", comment: "Confirm")) { }
} message: { Text(alertMessage) }
.background(
NavigationLink(
destination: QRCodeStyleView(qrCodeContent: generateQRCodeContent(), qrCodeType: selectedQRCodeType, existingStyleData: nil, historyItem: nil),
isActive: $navigateToStyleView
) {
EmptyView()
}
)
.onAppear {
setupInitialFocus()
}
.onTapGesture {
hideKeyboard()
}
}
// MARK: - UI Components
private var inputAndPreviewSection: some View {
ScrollView {
VStack(spacing: 24) {
// Content input area
VStack(spacing: 16) {
// Use InputComponentFactory to dynamically select input component
createInputComponentForType()
.padding(.horizontal, 20)
}
// Preview area
#if DEBUG
if canCreateQRCode() {
VStack(spacing: 16) {
// 使QRCodePreviewView
QRCodePreviewView(
qrCodeImage: generateQRCodeImage(),
formattedContent: formatContentForQRCodeType(),
qrCodeType: selectedQRCodeType
)
.padding(.horizontal, 20)
}
}
Spacer(minLength: 100)
#endif
}
.padding(.top, 20)
}
.background(Color(.systemGroupedBackground))
}
// MARK: - Helper Methods
private func createInputComponentForType() -> AnyView {
switch selectedQRCodeType {
case .mail:
let emailConfig = EmailInputConfig(
emailAddress: $emailAddress,
emailSubject: $emailSubject,
emailBody: $emailBody,
emailCc: $emailCc,
emailBcc: $emailBcc
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
emailConfig: emailConfig
)
case .wifi:
let wifiConfig = WiFiInputConfig(
ssid: $wifiSSID,
password: $wifiPassword,
encryptionType: $wifiEncryptionType
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
wifiConfig: wifiConfig
)
case .vcard, .mecard:
let contactConfig = ContactInputConfig(
firstName: $contactFirstName,
lastName: $contactLastName,
phone: $contactPhone,
email: $contactEmail,
company: $contactCompany,
title: $contactTitle,
address: $contactAddress,
website: $contactWebsite,
nickname: $contactNickname,
birthday: $contactBirthday,
note: $contactNote
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
contactConfig: contactConfig
)
case .location:
let locationConfig = LocationInputConfig(
latitude: $locationLatitude,
longitude: $locationLongitude,
locationName: $locationName
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
locationConfig: locationConfig
)
case .calendar:
let calendarConfig = CalendarInputConfig(
eventTitle: $eventTitle,
eventDescription: $eventDescription,
startDate: $startDate,
endDate: $endDate,
location: $eventLocation
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
calendarConfig: calendarConfig
)
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber:
let socialConfig = SocialInputConfig(
username: $socialUsername,
message: $socialMessage
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
socialConfig: socialConfig
)
case .phone, .sms:
let phoneConfig = PhoneInputConfig(
phoneNumber: $phoneNumber,
phoneMessage: $phoneMessage
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
phoneConfig: phoneConfig
)
case .url:
let urlConfig = URLInputConfig(
url: $urlString
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
urlConfig: urlConfig
)
default:
let textConfig = TextInputConfig(
content: $content
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
textConfig: textConfig
)
}
}
private func setupInitialFocus() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
switch selectedQRCodeType {
case .mail:
focusedEmailField = .address
case .wifi:
focusedWiFiField = .ssid
case .vcard, .mecard:
focusedContactField = .firstName
case .location:
focusedLocationField = .latitude
case .calendar:
focusedCalendarField = .title
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok:
focusedSocialField = .username
case .phone, .sms:
focusedPhoneField = .phoneNumber
case .url:
isURLFieldFocused = true
default:
isContentFieldFocused = true
}
}
}
private func hideKeyboard() {
switch selectedQRCodeType {
case .mail:
focusedEmailField = nil
case .wifi:
focusedWiFiField = nil
case .vcard, .mecard:
focusedContactField = nil
case .location:
focusedLocationField = nil
case .calendar:
focusedCalendarField = nil
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok:
focusedSocialField = nil
case .phone, .sms:
focusedPhoneField = nil
case .url:
isURLFieldFocused = false
default:
isContentFieldFocused = false
}
}
private func getInputIcon() -> String {
switch selectedQRCodeType {
case .mail: return "envelope"
case .wifi: return "wifi"
case .vcard, .mecard: return "person"
case .location: return "location"
case .calendar: return "calendar"
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: return "globe"
case .phone, .sms: return "phone"
case .url: return "link"
default: return "textformat"
}
}
private func canCreateQRCode() -> Bool {
switch selectedQRCodeType {
case .mail:
return !emailAddress.isEmpty && !emailSubject.isEmpty && !emailBody.isEmpty
case .wifi:
return !wifiSSID.isEmpty
case .vcard, .mecard:
return !contactFirstName.isEmpty || !contactLastName.isEmpty
case .location:
return !locationLatitude.isEmpty && !locationLongitude.isEmpty
case .calendar:
return !eventTitle.isEmpty
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber:
return !socialUsername.isEmpty
case .phone, .sms:
return !phoneNumber.isEmpty
case .url:
return !urlString.isEmpty
default:
return !content.isEmpty
}
}
private func getContentHint() -> String {
InputComponentFactory.getPlaceholderText(for: selectedQRCodeType)
}
private func generateQRCodeImage() -> UIImage? {
guard canCreateQRCode() else { return nil }
let formattedContent = formatContentForQRCodeType()
let data = formattedContent.data(using: .utf8)
let qrFilter = CIFilter.qrCodeGenerator()
qrFilter.setValue(data, forKey: "inputMessage")
qrFilter.setValue("H", forKey: "inputCorrectionLevel")
guard let outputImage = qrFilter.outputImage else { return nil }
let context = CIContext()
guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return nil }
return UIImage(cgImage: cgImage)
}
private func formatContentForQRCodeType() -> String {
switch selectedQRCodeType {
case .text:
return content
case .url:
return urlString.hasPrefix("http") ? urlString : "https://\(urlString)"
case .mail:
var mailtoURL = "mailto:\(emailAddress)"
var queryParams: [String] = []
if !emailSubject.isEmpty {
queryParams.append("subject=\(emailSubject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? emailSubject)")
}
if !emailBody.isEmpty {
queryParams.append("body=\(emailBody.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? emailBody)")
}
if !emailCc.isEmpty {
queryParams.append("cc=\(emailCc)")
}
if !emailBcc.isEmpty {
queryParams.append("bcc=\(emailBcc)")
}
if !queryParams.isEmpty {
mailtoURL += "?" + queryParams.joined(separator: "&")
}
return mailtoURL
case .phone:
return "tel:\(phoneNumber)"
case .sms:
let smsContent = phoneMessage.isEmpty ? "Hello" : phoneMessage
return "SMSTO:\(phoneNumber):\(smsContent)"
case .wifi:
return "WIFI:T:\(wifiEncryptionType.rawValue);S:\(wifiSSID);P:\(wifiPassword);;"
case .vcard:
var vcard = "BEGIN:VCARD\nVERSION:3.0\n"
// (NFN)
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
let lastName = contactLastName.isEmpty ? "" : contactLastName
let firstName = contactFirstName.isEmpty ? "" : contactFirstName
vcard += "N:\(lastName);\(firstName);;;\n"
vcard += "FN:\(firstName) \(lastName)\n"
}
//
if !contactPhone.isEmpty {
vcard += "TEL;TYPE=WORK,CELL:\(contactPhone)\n"
}
//
if !contactEmail.isEmpty {
vcard += "EMAIL;TYPE=PREF,INTERNET:\(contactEmail)\n"
}
//
if !contactCompany.isEmpty {
vcard += "ORG:\(contactCompany)\n"
}
//
if !contactTitle.isEmpty {
vcard += "TITLE:\(contactTitle)\n"
}
//
if !contactAddress.isEmpty {
vcard += "ADR;TYPE=WORK:;;\(contactAddress);;;;\n"
}
//
if !contactWebsite.isEmpty {
vcard += "URL:\(contactWebsite)\n"
}
vcard += "END:VCARD"
return vcard
case .mecard:
var mecard = "MECARD:"
//
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
let lastName = contactLastName.isEmpty ? "" : contactLastName
let firstName = contactFirstName.isEmpty ? "" : contactFirstName
mecard += "N:\(lastName),\(firstName);"
}
//
if !contactNickname.isEmpty {
mecard += "NICKNAME:\(contactNickname);"
}
//
if !contactPhone.isEmpty {
mecard += "TEL:\(contactPhone);"
}
//
if !contactEmail.isEmpty {
mecard += "EMAIL:\(contactEmail);"
}
//
if !contactCompany.isEmpty {
mecard += "ORG:\(contactCompany);"
}
//
if !contactAddress.isEmpty {
mecard += "ADR:\(contactAddress);"
}
//
if !contactWebsite.isEmpty {
mecard += "URL:\(contactWebsite);"
}
//
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd"
let birthdayString = dateFormatter.string(from: contactBirthday)
mecard += "BDAY:\(birthdayString);"
//
if !contactNote.isEmpty {
mecard += "NOTE:\(contactNote);"
}
mecard += ";"
return mecard
case .location:
let coords = "\(locationLatitude),\(locationLongitude)"
return locationName.isEmpty ? "geo:\(coords)" : "geo:\(coords)?q=\(locationName)"
case .calendar:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
var ical = "BEGIN:VEVENT\n"
ical += "SUMMARY:\(eventTitle)\n"
if !eventDescription.isEmpty {
ical += "DESCRIPTION:\(eventDescription)\n"
}
if !eventLocation.isEmpty {
ical += "LOCATION:\(eventLocation)\n"
}
ical += "DTSTART:\(dateFormatter.string(from: startDate))\n"
ical += "DTEND:\(dateFormatter.string(from: endDate))\n"
ical += "END:VEVENT"
return ical
case .instagram:
return "instagram://user?username=\(socialUsername)"
case .facebook:
// Facebook/ID
let facebookId = extractFacebookId(from: socialUsername)
return "fb://profile/\(facebookId)"
case .spotify:
return "spotify:search:\(socialUsername);\(socialMessage)"
case .twitter:
return "twitter://user?screen_name=\(socialUsername)"
case .whatsapp:
return "whatsapp://send?phone=\(socialUsername)"
case .viber:
return "viber://add?number=\(socialUsername)"
case .snapchat:
return "https://snapchat.com/add/\(socialUsername)"
case .tiktok:
return "https://www.tiktok.com/@\(socialUsername)"
}
}
// MARK: -
private func generateSocialMediaContent() -> String {
switch selectedQRCodeType {
case .instagram:
return "instagram://user?username=\(socialUsername)"
case .facebook:
// Facebook/ID
let facebookId = extractFacebookId(from: socialUsername)
return "fb://profile/\(facebookId)"
case .spotify:
return "spotify:search:\(socialUsername);\(socialMessage)"
case .twitter:
return "twitter://user?screen_name=\(socialUsername)"
case .whatsapp:
return "whatsapp://send?phone=\(socialUsername)"
case .viber:
return "viber://add?number=\(socialUsername)"
case .snapchat:
return "https://snapchat.com/add/\(socialUsername)"
case .tiktok:
return "https://www.tiktok.com/@\(socialUsername)"
default:
return socialUsername
}
}
private func createQRCode() {
let context = coreDataManager.container.viewContext
let historyItem = HistoryItem(context: context)
historyItem.id = UUID()
historyItem.dataType = DataType.qrcode.rawValue
historyItem.dataSource = DataSource.created.rawValue
historyItem.createdAt = Date()
historyItem.isFavorite = false
historyItem.qrCodeType = selectedQRCodeType.rawValue
//
switch selectedQRCodeType {
case .mail:
var mailContent = String(format: NSLocalizedString("email_content_format", comment: "Email content format"), emailAddress, emailSubject, emailBody)
if !emailCc.isEmpty {
mailContent += String(format: NSLocalizedString("email_cc_format", comment: "Email CC format"), emailCc)
}
if !emailBcc.isEmpty {
mailContent += String(format: NSLocalizedString("email_bcc_format", comment: "Email BCC format"), emailBcc)
}
historyItem.content = mailContent
case .wifi:
historyItem.content = String(format: NSLocalizedString("wifi_content_format", comment: "WiFi content format"), wifiSSID, wifiEncryptionType.displayName)
case .vcard, .mecard:
var contactContent = NSLocalizedString("contact_content_prefix", comment: "Contact content prefix")
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
contactContent += "\(contactFirstName) \(contactLastName)"
}
if !contactNickname.isEmpty {
contactContent += String(format: NSLocalizedString("contact_nickname_format", comment: "Contact nickname format"), contactNickname)
}
if !contactPhone.isEmpty {
contactContent += String(format: NSLocalizedString("contact_phone_format", comment: "Contact phone format"), contactPhone)
}
if !contactEmail.isEmpty {
contactContent += String(format: NSLocalizedString("contact_email_format", comment: "Contact email format"), contactEmail)
}
if !contactCompany.isEmpty {
contactContent += String(format: NSLocalizedString("contact_company_format", comment: "Contact company format"), contactCompany)
}
if !contactTitle.isEmpty {
contactContent += String(format: NSLocalizedString("contact_title_format", comment: "Contact title format"), contactTitle)
}
if !contactAddress.isEmpty {
contactContent += String(format: NSLocalizedString("contact_address_format", comment: "Contact address format"), contactAddress)
}
if !contactWebsite.isEmpty {
contactContent += String(format: NSLocalizedString("contact_website_format", comment: "Contact website format"), contactWebsite)
}
if !contactNote.isEmpty {
contactContent += String(format: NSLocalizedString("contact_note_format", comment: "Contact note format"), contactNote)
}
historyItem.content = contactContent
case .location:
historyItem.content = String(format: NSLocalizedString("location_content_format", comment: "Location content format"), locationLatitude, locationLongitude)
case .calendar:
historyItem.content = String(format: NSLocalizedString("calendar_content_format", comment: "Calendar content format"), eventTitle)
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber:
historyItem.content = generateSocialMediaContent()
case .phone, .sms:
historyItem.content = String(format: NSLocalizedString("phone_content_format", comment: "Phone content format"), phoneNumber)
case .url:
historyItem.content = String(format: NSLocalizedString("url_content_format", comment: "URL content format"), urlString)
default:
historyItem.content = content
}
do {
try context.save()
alertMessage = NSLocalizedString("qrcode_created_successfully", comment: "QR code created successfully")
showingAlert = true
//
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
dismiss()
}
} catch {
alertMessage = String(format: NSLocalizedString("save_failed_error", comment: "Save failed with error"), error.localizedDescription)
showingAlert = true
}
}
// MARK: -
private func generateQRCodeContent() -> String {
switch selectedQRCodeType {
case .mail:
var mailContent = "mailto:\(emailAddress)"
if !emailSubject.isEmpty || !emailBody.isEmpty || !emailCc.isEmpty || !emailBcc.isEmpty {
mailContent += "?"
var params: [String] = []
if !emailSubject.isEmpty {
params.append("subject=\(emailSubject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")")
}
if !emailBody.isEmpty {
params.append("body=\(emailBody.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")")
}
if !emailCc.isEmpty {
params.append("cc=\(emailCc.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")")
}
if !emailBcc.isEmpty {
params.append("bcc=\(emailBcc.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")")
}
mailContent += params.joined(separator: "&")
}
return mailContent
case .wifi:
var wifiContent = "WIFI:"
wifiContent += "S:\(wifiSSID);"
wifiContent += "T:\(wifiEncryptionType.rawValue.uppercased());"
if !wifiPassword.isEmpty {
wifiContent += "P:\(wifiPassword);"
}
wifiContent += ";"
return wifiContent
case .vcard:
var vcardContent = "BEGIN:VCARD\nVERSION:3.0\n"
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
vcardContent += "N:\(contactLastName);\(contactFirstName);;;\n"
vcardContent += "FN:\(contactFirstName) \(contactLastName)\n"
}
if !contactPhone.isEmpty {
vcardContent += "TEL;TYPE=WORK,PREF:\(contactPhone)\n"
}
if !contactEmail.isEmpty {
vcardContent += "EMAIL:\(contactEmail)\n"
}
if !contactCompany.isEmpty {
vcardContent += "ORG:\(contactCompany)\n"
}
if !contactTitle.isEmpty {
vcardContent += "TITLE:\(contactTitle)\n"
}
if !contactAddress.isEmpty {
vcardContent += "ADR;TYPE=WORK:;;\(contactAddress);;;;\n"
}
if !contactWebsite.isEmpty {
vcardContent += "URL:\(contactWebsite)\n"
}
vcardContent += "END:VCARD"
return vcardContent
case .mecard:
var mecardContent = "MECARD:"
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
mecardContent += "N:\(contactLastName),\(contactFirstName);"
}
if !contactNickname.isEmpty {
mecardContent += "NICKNAME:\(contactNickname);"
}
if !contactPhone.isEmpty {
mecardContent += "TEL:\(contactPhone);"
}
if !contactEmail.isEmpty {
mecardContent += "EMAIL:\(contactEmail);"
}
if !contactCompany.isEmpty {
mecardContent += "ORG:\(contactCompany);"
}
if !contactTitle.isEmpty {
mecardContent += "TITLE:\(contactTitle);"
}
if !contactAddress.isEmpty {
mecardContent += "ADR:,,\(contactAddress);"
}
if !contactWebsite.isEmpty {
mecardContent += "URL:\(contactWebsite);"
}
if !contactNote.isEmpty {
mecardContent += "NOTE:\(contactNote);"
}
mecardContent += ";"
return mecardContent
case .location:
return "geo:\(locationLatitude),\(locationLongitude)"
case .calendar:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss'Z'"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
let startDateString = dateFormatter.string(from: startDate)
let endDateString = dateFormatter.string(from: endDate)
var icalContent = "BEGIN:VEVENT\n"
icalContent += "SUMMARY:\(eventTitle)\n"
if !eventDescription.isEmpty {
icalContent += "DESCRIPTION:\(eventDescription)\n"
}
if !eventLocation.isEmpty {
icalContent += "LOCATION:\(eventLocation)\n"
}
icalContent += "DTSTART:\(startDateString)\n"
icalContent += "DTEND:\(endDateString)\n"
icalContent += "END:VEVENT"
return icalContent
case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber:
return generateSocialMediaContent()
case .phone:
return "tel:\(phoneNumber)"
case .sms:
return "SMSTO:\(phoneNumber)"
case .url:
return urlString
default:
return content
}
}
// MARK: - Facebook ID
private func extractFacebookId(from input: String) -> String {
// Facebook/ID
if input.hasPrefix("http") {
// Facebook
let patterns = [
"https://www.facebook.com/",
"https://facebook.com/",
"http://www.facebook.com/",
"http://facebook.com/"
]
var cleanedInput = input
for pattern in patterns {
if cleanedInput.hasPrefix(pattern) {
cleanedInput = String(cleanedInput.dropFirst(pattern.count))
break
}
}
//
if let questionMarkIndex = cleanedInput.firstIndex(of: "?") {
cleanedInput = String(cleanedInput[..<questionMarkIndex])
}
if let slashIndex = cleanedInput.firstIndex(of: "/") {
cleanedInput = String(cleanedInput[..<slashIndex])
}
return cleanedInput.isEmpty ? "unknown" : cleanedInput
}
// ID
return input
}
}
#Preview {
NavigationView {
CreateQRCodeView(selectedQRCodeType: .spotify)
}
}