Enhance QRCodeParser and CreateQRCodeView to support additional MeCard fields including nickname, birthday, and note; update input handling and display logic in ContactInputView and QRCodeDetailView for improved user experience and data representation.

main
v504 2 months ago
parent 4464897237
commit 6a8d380352

@ -377,28 +377,75 @@ class QRCodeParser {
let components = mecardInfo.components(separatedBy: ";")
var name = ""
var nickname = ""
var phone = ""
var email = ""
var company = ""
var address = ""
var website = ""
var birthday = ""
var note = ""
for component in components {
if component.hasPrefix("N:") {
name = String(component.dropFirst(2))
} else if component.hasPrefix("TEL:") {
phone = String(component.dropFirst(4))
} else if component.hasPrefix("EMAIL:") {
email = String(component.dropFirst(6))
} else if component.hasPrefix("ADR:") {
address = String(component.dropFirst(4))
let trimmedComponent = component.trimmingCharacters(in: .whitespaces)
if trimmedComponent.isEmpty { continue }
if trimmedComponent.hasPrefix("N:") {
let nameValue = String(trimmedComponent.dropFirst(2))
let nameParts = nameValue.components(separatedBy: ",")
if nameParts.count >= 2 {
let lastName = nameParts[0]
let firstName = nameParts[1]
name = "\(firstName) \(lastName)"
} else if nameParts.count == 1 {
name = nameParts[0]
}
} else if trimmedComponent.hasPrefix("NICKNAME:") {
nickname = String(trimmedComponent.dropFirst(9))
} else if trimmedComponent.hasPrefix("TEL:") {
phone = String(trimmedComponent.dropFirst(4))
} else if trimmedComponent.hasPrefix("EMAIL:") {
email = String(trimmedComponent.dropFirst(6))
} else if trimmedComponent.hasPrefix("ORG:") {
company = String(trimmedComponent.dropFirst(4))
} else if trimmedComponent.hasPrefix("ADR:") {
address = String(trimmedComponent.dropFirst(4))
} else if trimmedComponent.hasPrefix("URL:") {
website = String(trimmedComponent.dropFirst(4))
} else if trimmedComponent.hasPrefix("BDAY:") {
let birthdayValue = String(trimmedComponent.dropFirst(5))
if birthdayValue.count == 8 {
let year = String(birthdayValue.prefix(4))
let month = String(birthdayValue.dropFirst(4).prefix(2))
let day = String(birthdayValue.dropFirst(6))
birthday = "\(year)\(month)\(day)"
} else {
birthday = birthdayValue
}
} else if trimmedComponent.hasPrefix("NOTE:") {
note = String(trimmedComponent.dropFirst(5))
}
}
let title = "联系人信息"
let subtitle = "姓名: \(name)\n电话: \(phone)\n邮箱: \(email)\n地址: \(address)"
var subtitle = ""
if !name.isEmpty { subtitle += "姓名: \(name)\n" }
if !nickname.isEmpty { subtitle += "昵称: \(nickname)\n" }
if !phone.isEmpty { subtitle += "电话: \(phone)\n" }
if !email.isEmpty { subtitle += "邮箱: \(email)\n" }
if !company.isEmpty { subtitle += "公司: \(company)\n" }
if !address.isEmpty { subtitle += "地址: \(address)\n" }
if !website.isEmpty { subtitle += "网站: \(website)\n" }
if !birthday.isEmpty { subtitle += "生日: \(birthday)\n" }
if !note.isEmpty { subtitle += "备注: \(note)\n" }
//
if subtitle.hasSuffix("\n") {
subtitle = String(subtitle.dropLast())
}
return ParsedQRData(
type: .mecard,
title: title,
title: "联系人信息",
subtitle: subtitle,
icon: "person.crop.rectangle"
)

@ -10,11 +10,14 @@ struct ContactInputView: View {
@Binding var title: String
@Binding var address: String
@Binding var website: String
@Binding var nickname: String
@Binding var birthday: Date
@Binding var note: String
@FocusState var focusedField: ContactField?
//
enum ContactField: Hashable {
case firstName, lastName, phone, email, company, title, address, website
case firstName, lastName, phone, email, company, title, address, website, nickname, birthday, note
}
var body: some View {
@ -52,6 +55,20 @@ struct ContactInputView: View {
}
}
//
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("昵称")
.font(.subheadline)
.foregroundColor(.primary)
Spacer()
}
TextField("昵称", text: $nickname)
.textFieldStyle(RoundedBorderTextFieldStyle())
.focused($focusedField, equals: .nickname)
}
//
VStack(alignment: .leading, spacing: 8) {
HStack {
@ -140,6 +157,34 @@ struct ContactInputView: View {
.autocapitalization(.none)
.focused($focusedField, equals: .website)
}
//
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("生日")
.font(.subheadline)
.foregroundColor(.primary)
Spacer()
}
DatePicker("选择生日", selection: $birthday, displayedComponents: .date)
.datePickerStyle(CompactDatePickerStyle())
.focused($focusedField, equals: .birthday)
}
//
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("备注")
.font(.subheadline)
.foregroundColor(.primary)
Spacer()
}
TextField("备注信息", text: $note)
.textFieldStyle(RoundedBorderTextFieldStyle())
.focused($focusedField, equals: .note)
}
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
@ -163,6 +208,9 @@ struct ContactInputView: View {
company: .constant(""),
title: .constant(""),
address: .constant(""),
website: .constant("")
website: .constant(""),
nickname: .constant(""),
birthday: .constant(Date()),
note: .constant("")
)
}

@ -26,6 +26,9 @@ struct ContactInputConfig {
let title: Binding<String>
let address: Binding<String>
let website: Binding<String>
let nickname: Binding<String>
let birthday: Binding<Date>
let note: Binding<String>
}
// MARK: -
@ -104,7 +107,10 @@ struct InputComponentFactory {
company: config.company,
title: config.title,
address: config.address,
website: config.website
website: config.website,
nickname: config.nickname,
birthday: config.birthday,
note: config.note
)
)
}

@ -37,6 +37,9 @@ struct CreateQRCodeView: View {
@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?
//
@ -173,7 +176,10 @@ struct CreateQRCodeView: View {
company: $contactCompany,
title: $contactTitle,
address: $contactAddress,
website: $contactWebsite
website: $contactWebsite,
nickname: $contactNickname,
birthday: $contactBirthday,
note: $contactNote
)
return InputComponentFactory.createInputComponent(
for: selectedQRCodeType,
@ -433,24 +439,55 @@ struct CreateQRCodeView: View {
return vcard
case .mecard:
var mecard = "MECARD:"
//
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
mecard += "N:\(contactLastName),\(contactFirstName);"
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:
@ -518,7 +555,35 @@ struct CreateQRCodeView: View {
case .wifi:
historyItem.content = "WiFi: \(wifiSSID) (\(wifiEncryptionType.displayName))"
case .vcard, .mecard:
historyItem.content = "联系人: \(contactFirstName) \(contactLastName)"
var contactContent = "联系人: "
if !contactFirstName.isEmpty || !contactLastName.isEmpty {
contactContent += "\(contactFirstName) \(contactLastName)"
}
if !contactNickname.isEmpty {
contactContent += " (\(contactNickname))"
}
if !contactPhone.isEmpty {
contactContent += "\n电话: \(contactPhone)"
}
if !contactEmail.isEmpty {
contactContent += "\n邮箱: \(contactEmail)"
}
if !contactCompany.isEmpty {
contactContent += "\n公司: \(contactCompany)"
}
if !contactTitle.isEmpty {
contactContent += "\n职位: \(contactTitle)"
}
if !contactAddress.isEmpty {
contactContent += "\n地址: \(contactAddress)"
}
if !contactWebsite.isEmpty {
contactContent += "\n网站: \(contactWebsite)"
}
if !contactNote.isEmpty {
contactContent += "\n备注: \(contactNote)"
}
historyItem.content = contactContent
case .location:
historyItem.content = "位置: \(locationLatitude), \(locationLongitude)"
case .calendar:

@ -362,6 +362,12 @@ struct ShareSheet: UIViewControllerRepresentable {
return NavigationView { QRCodeDetailView(historyItem: item) }
}
#Preview("MeCard") {
let ctx = PreviewData.context
let item = PreviewData.mecardSample(in: ctx)
return NavigationView { QRCodeDetailView(historyItem: item) }
}
// MARK: - Preview Data
private enum PreviewData {
static let context: NSManagedObjectContext = {
@ -426,4 +432,9 @@ private enum PreviewData {
let content = "Hello, this is a text message!"
return makeBaseItem(in: context, content: content, qrType: .text)
}
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;ADR:123 Main St,Anytown,CA,12345,USA;URL:https://example.com;BDAY:19820908;NOTE:Software Engineer;;"
return makeBaseItem(in: context, content: content, qrType: .mecard)
}
}

@ -70,8 +70,8 @@ END:VCARD
// 支持vCard 2.1到3.0的自动转换
// 联系人信息 (MeCard)
MECARD:N:<姓名>;TEL:<电话>;EMAIL:<邮箱>;ADR:<地址>;;
示例: MECARD:N:John Doe;TEL:+1234567890;EMAIL:example@example.com;;
MECARD:N:<>,<名>;NICKNAME:<昵称>;TEL:<电话>;EMAIL:<邮箱>;ORG:<公司>;ADR:<地址>;URL:<网站>;BDAY:<生日>;NOTE:<备注>;;
示例: MECARD:N:Doe,John;NICKNAME:Johnny;TEL:+1234567890;EMAIL:john.doe@example.com;ORG:Example Company;ADR:123 Main St,Anytown,CA,12345,USA;URL:https://example.com;BDAY:19820908;NOTE:Software Engineer;;
// 文本内容
<文本内容>

Loading…
Cancel
Save