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
// D e c o r a t e c o d e 按 钮
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 {
// E m a i l 信 息 详 细 显 示
emailInfoDetailView ( parsedData : parsedData )
} else if parsedData . type = = . calendar {
// C a l e n d a r 信 息 详 细 显 示
calendarInfoDetailView ( parsedData : parsedData )
} else if parsedData . type = = . sms {
// S M S 信 息 详 细 显 示
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: - E m a i l 信 息 详 细 视 图
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 {
// 如 果 无 法 解 析 E m a i l 信 息 , 显 示 原 始 内 容
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: - C a l e n d a r 信 息 详 细 视 图
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 {
// 如 果 无 法 解 析 C a l e n d a r 信 息 , 显 示 原 始 内 容
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: - S M S 信 息 详 细 视 图
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 {
// 如 果 无 法 解 析 S M S 信 息 , 显 示 原 始 内 容
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
)
// 根 据 Q R 码 类 型 添 加 特 定 按 钮
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 {
// 从 电 话 U R L 中 提 取 电 话 号 码
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 {
// 从 邮 件 U R L 中 提 取 邮 箱 地 址
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: - 检 查 是 否 可 以 打 开 U R L
private func canOpenURL ( _ string : String ) -> Bool {
guard let url = URL ( string : string ) else { return false }
return UIApplication . shared . canOpenURL ( url )
}
// MARK: - 打 开 U R L
private func openURL ( _ string : String ) {
guard let url = URL ( string : string ) else { return }
UIApplication . shared . open ( url )
}
// MARK: - 获 取 W i F i 详 细 信 息
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: - 获 取 S M S 详 细 信 息
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: - 复 制 W i F i 密 码
private func copyWiFiPassword ( ) {
guard let wifiDetails = getWiFiDetails ( ) else { return }
UIPasteboard . general . string = wifiDetails . password
alertMessage = " wifi_password_copied " . localized
showingAlert = true
}
// MARK: - 设 置 W i F i
private func setupWiFi ( ) {
guard let wifiDetails = getWiFiDetails ( ) else { return }
// 使 用 W i F i 连 接 管 理 器
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: - 从 解 析 数 据 中 获 取 E m a i l 信 息
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: - 从 解 析 数 据 中 获 取 C a l e n d a r 信 息
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: - 从 解 析 数 据 中 获 取 S M S 信 息
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: - P r e v i e w D a t a
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 : // e x a m p l e . c o m
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 : 20241201 T140000
DTEND : 20241201 T150000
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 )
}
// 设 置 L o g o ( 如 果 有 的 话 )
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: - L o g o 转 换
private func getLogoFromString ( _ logoString : String ) -> QRCode . LogoTemplate ? {
// 检 查 是 否 是 自 定 义 L o g o
if logoString . hasPrefix ( " custom_ " ) {
// 从 样 式 数 据 中 获 取 自 定 义 L o g o 图 片
if let styleData = getStyleData ( ) ,
let customImage = styleData . customLogoImage ,
let cgImage = customImage . cgImage {
return QRCode . LogoTemplate . CircleCenter ( image : cgImage , inset : 0 )
}
} else {
// 预 设 L o g o
if let logo = QRCodeLogo ( rawValue : logoString ) ,
let image = logo . image ,
let cgImage = image . cgImage {
return QRCode . LogoTemplate . CircleCenter ( image : cgImage )
}
}
return nil
}
// MARK: - 从 J S O N 字 符 串 解 析 样 式 数 据
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 // 默 认 返 回 t e x t 类 型
}
// MARK: - 获 取 导 航 标 题
private func getNavigationTitle ( ) -> String {
if let qrCodeTypeString = historyItem . qrCodeType ,
let qrCodeType = QRCodeType ( rawValue : qrCodeTypeString ) {
// v C a r d 和 M C a r d 类 型 统 一 使 用 C o n t a c t 标 题
if qrCodeType = = . vcard || qrCodeType = = . mecard {
return " contact " . localized
}
return qrCodeType . displayName
}
return " qr_code_detail " . localized
}
// MARK: - D e c o r a t e c o d e 按 钮
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 {
// 如 果 U R L 无 效 , 显 示 错 误 提 示
alertMessage = " app_open_failed " . localized
showingAlert = true
}
}
// MARK: - 跳 转 到 自 定 义 样 式 界 面
private func navigateToCustomStyle ( ) {
navigateToStyleView = true
}
}
// MARK: - F l o w L a y o u t 流 布 局 组 件
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
}
}
}