|
|
import SwiftUI
|
|
|
import CoreData
|
|
|
import QRCode
|
|
|
internal import SwiftImageReadWrite
|
|
|
|
|
|
struct QRCodeDetailView: View {
|
|
|
let historyItem: HistoryItem
|
|
|
@StateObject private var coreDataManager = CoreDataManager.shared
|
|
|
@State private var qrCodeImage: UIImage?
|
|
|
@State private var showingShareSheet = false
|
|
|
@State private var showingAlert = false
|
|
|
@State private var alertMessage = ""
|
|
|
|
|
|
var body: some View {
|
|
|
ScrollView {
|
|
|
VStack(spacing: 20) {
|
|
|
// 二维码图片
|
|
|
qrCodeImageView
|
|
|
|
|
|
// 二维码类型信息
|
|
|
qrCodeTypeSection
|
|
|
|
|
|
// 解析后的详细信息
|
|
|
parsedInfoSection
|
|
|
|
|
|
// 原始内容
|
|
|
originalContentSection
|
|
|
|
|
|
// 操作按钮
|
|
|
actionButtonsSection
|
|
|
}
|
|
|
.padding()
|
|
|
}
|
|
|
.navigationTitle("二维码详情")
|
|
|
.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("提示", isPresented: $showingAlert) {
|
|
|
Button("确定") { }
|
|
|
} message: {
|
|
|
Text(alertMessage)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 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("扫描此二维码")
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.secondary)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 二维码类型信息
|
|
|
private var qrCodeTypeSection: some View {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
HStack {
|
|
|
Image(systemName: "qrcode")
|
|
|
.font(.title2)
|
|
|
.foregroundColor(.blue)
|
|
|
|
|
|
Text("二维码类型")
|
|
|
.font(.headline)
|
|
|
|
|
|
Spacer()
|
|
|
}
|
|
|
|
|
|
if let qrCodeTypeString = historyItem.qrCodeType,
|
|
|
let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) {
|
|
|
HStack {
|
|
|
Image(systemName: qrCodeType.icon)
|
|
|
.font(.title3)
|
|
|
.foregroundColor(.orange)
|
|
|
|
|
|
Text(qrCodeType.displayName)
|
|
|
.font(.title3)
|
|
|
.fontWeight(.medium)
|
|
|
|
|
|
Spacer()
|
|
|
}
|
|
|
.padding()
|
|
|
.background(Color.orange.opacity(0.1))
|
|
|
.cornerRadius(8)
|
|
|
}
|
|
|
}
|
|
|
.padding()
|
|
|
.background(Color(.systemBackground))
|
|
|
.cornerRadius(12)
|
|
|
.shadow(radius: 2)
|
|
|
}
|
|
|
|
|
|
// MARK: - 解析后的详细信息
|
|
|
private var parsedInfoSection: some View {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
HStack {
|
|
|
Image(systemName: "info.circle")
|
|
|
.font(.title2)
|
|
|
.foregroundColor(.green)
|
|
|
|
|
|
Text("解析信息")
|
|
|
.font(.headline)
|
|
|
|
|
|
Spacer()
|
|
|
}
|
|
|
|
|
|
if let content = historyItem.content {
|
|
|
let parsedData = QRCodeParser.parseQRCode(content)
|
|
|
|
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
|
HStack {
|
|
|
Image(systemName: parsedData.icon)
|
|
|
.font(.title3)
|
|
|
.foregroundColor(.green)
|
|
|
|
|
|
Text(parsedData.title)
|
|
|
.font(.title3)
|
|
|
.fontWeight(.medium)
|
|
|
|
|
|
Spacer()
|
|
|
}
|
|
|
|
|
|
if let subtitle = parsedData.subtitle {
|
|
|
Text(subtitle)
|
|
|
.font(.body)
|
|
|
.foregroundColor(.secondary)
|
|
|
.multilineTextAlignment(.leading)
|
|
|
}
|
|
|
}
|
|
|
.padding()
|
|
|
.background(Color.green.opacity(0.1))
|
|
|
.cornerRadius(8)
|
|
|
}
|
|
|
}
|
|
|
.padding()
|
|
|
.background(Color(.systemBackground))
|
|
|
.cornerRadius(12)
|
|
|
.shadow(radius: 2)
|
|
|
}
|
|
|
|
|
|
// MARK: - 原始内容
|
|
|
private var originalContentSection: some View {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
HStack {
|
|
|
Image(systemName: "doc.text")
|
|
|
.font(.title2)
|
|
|
.foregroundColor(.purple)
|
|
|
|
|
|
Text("原始内容")
|
|
|
.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 actionButtonsSection: some View {
|
|
|
VStack(spacing: 12) {
|
|
|
// 收藏按钮
|
|
|
Button(action: toggleFavorite) {
|
|
|
HStack {
|
|
|
Image(systemName: historyItem.isFavorite ? "heart.fill" : "heart")
|
|
|
.foregroundColor(historyItem.isFavorite ? .red : .gray)
|
|
|
|
|
|
Text(historyItem.isFavorite ? "取消收藏" : "收藏")
|
|
|
.fontWeight(.medium)
|
|
|
}
|
|
|
.frame(maxWidth: .infinity)
|
|
|
.padding()
|
|
|
.background(historyItem.isFavorite ? Color.red.opacity(0.1) : Color.gray.opacity(0.1))
|
|
|
.foregroundColor(historyItem.isFavorite ? .red : .gray)
|
|
|
.cornerRadius(10)
|
|
|
}
|
|
|
|
|
|
// 复制内容按钮
|
|
|
Button(action: copyContent) {
|
|
|
HStack {
|
|
|
Image(systemName: "doc.on.doc")
|
|
|
.foregroundColor(.blue)
|
|
|
|
|
|
Text("复制内容")
|
|
|
.fontWeight(.medium)
|
|
|
}
|
|
|
.frame(maxWidth: .infinity)
|
|
|
.padding()
|
|
|
.background(Color.blue.opacity(0.1))
|
|
|
.foregroundColor(.blue)
|
|
|
.cornerRadius(10)
|
|
|
}
|
|
|
|
|
|
// 打开链接按钮(如果是URL类型)
|
|
|
if let content = historyItem.content, canOpenURL(content) {
|
|
|
Button(action: { openURL(content) }) {
|
|
|
HStack {
|
|
|
Image(systemName: "arrow.up.right.square")
|
|
|
.foregroundColor(.green)
|
|
|
|
|
|
Text("打开链接")
|
|
|
.fontWeight(.medium)
|
|
|
}
|
|
|
.frame(maxWidth: .infinity)
|
|
|
.padding()
|
|
|
.background(Color.green.opacity(0.1))
|
|
|
.foregroundColor(.green)
|
|
|
.cornerRadius(10)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.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 ? "已添加到收藏" : "已取消收藏"
|
|
|
alertMessage = message
|
|
|
showingAlert = true
|
|
|
}
|
|
|
|
|
|
// MARK: - 复制内容
|
|
|
private func copyContent() {
|
|
|
if let content = historyItem.content {
|
|
|
UIPasteboard.general.string = content
|
|
|
alertMessage = "内容已复制到剪贴板"
|
|
|
showingAlert = true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 检查是否可以打开URL
|
|
|
private func canOpenURL(_ string: String) -> Bool {
|
|
|
guard let url = URL(string: string) else { return false }
|
|
|
return UIApplication.shared.canOpenURL(url)
|
|
|
}
|
|
|
|
|
|
// MARK: - 打开URL
|
|
|
private func openURL(_ string: String) {
|
|
|
guard let url = URL(string: string) else { return }
|
|
|
UIApplication.shared.open(url)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 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)
|
|
|
return NavigationView { QRCodeDetailView(historyItem: item) }
|
|
|
}
|
|
|
|
|
|
#Preview("URL") {
|
|
|
let ctx = PreviewData.context
|
|
|
let item = PreviewData.urlSample(in: ctx)
|
|
|
return NavigationView { QRCodeDetailView(historyItem: item) }
|
|
|
}
|
|
|
|
|
|
#Preview("SMS") {
|
|
|
let ctx = PreviewData.context
|
|
|
let item = PreviewData.smsSample(in: ctx)
|
|
|
return NavigationView { QRCodeDetailView(historyItem: item) }
|
|
|
}
|
|
|
|
|
|
#Preview("vCard") {
|
|
|
let ctx = PreviewData.context
|
|
|
let item = PreviewData.vcardSample(in: ctx)
|
|
|
return NavigationView { QRCodeDetailView(historyItem: item) }
|
|
|
}
|
|
|
|
|
|
#Preview("Instagram") {
|
|
|
let ctx = PreviewData.context
|
|
|
let item = PreviewData.instagramSample(in: ctx)
|
|
|
return NavigationView { QRCodeDetailView(historyItem: item) }
|
|
|
}
|
|
|
|
|
|
#Preview("Text") {
|
|
|
let ctx = PreviewData.context
|
|
|
let item = PreviewData.textSample(in: ctx)
|
|
|
return NavigationView { QRCodeDetailView(historyItem: item) }
|
|
|
}
|
|
|
|
|
|
// MARK: - Preview Data
|
|
|
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:+8613800138000: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://example.com
|
|
|
END:VCARD
|
|
|
""".trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
return makeBaseItem(in: context, content: content, qrType: .vcard)
|
|
|
}
|
|
|
|
|
|
static func instagramSample(in context: NSManagedObjectContext) -> HistoryItem {
|
|
|
let content = "https://www.instagram.com/example_user/"
|
|
|
return makeBaseItem(in: context, content: content, qrType: .instagram)
|
|
|
}
|
|
|
|
|
|
static func textSample(in context: NSManagedObjectContext) -> HistoryItem {
|
|
|
let content = "Hello, this is a text message!"
|
|
|
return makeBaseItem(in: context, content: content, qrType: .text)
|
|
|
}
|
|
|
}
|