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.

425 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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("WiFi") {
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 = "sms:+8613800138000?body=Hello"
return makeBaseItem(in: context, content: content, qrType: .sms)
}
static func vcardSample(in context: NSManagedObjectContext) -> HistoryItem {
let content = """
BEGIN:VCARD
VERSION:3.0
FN:John Doe
TEL:+1234567890
EMAIL:example@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)
}
}