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.

909 lines
32 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden 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 Combine
struct HistoryView: View {
@EnvironmentObject var coreDataManager: CoreDataManager
@EnvironmentObject var languageManager: LanguageManager
@State private var searchText = ""
@State private var selectedFilter: HistoryFilter = .all
@State private var itemToDelete: HistoryItem?
@State private var showingDeleteAlert = false
@State private var showingClearConfirmSheet = false
@State private var allHistoryItems: [HistoryItem] = []
@State private var currentPage = 0
@State private var isLoadingMore = false
@State private var hasMoreData = true
@State private var isLoading = false
@State private var refreshTrigger = false
@State private var isBatchDeleteMode = false
@State private var selectedItemsForDelete: Set<UUID> = []
@State private var itemToEdit: HistoryItem?
@State private var showingEditView = false
enum HistoryFilter: String, CaseIterable {
case all = "all"
case barcode = "barcode"
case qrcode = "qrcode"
case scanned = "scanned"
case created = "created"
case favorites = "favorites"
var displayName: String {
switch self {
case .all:
return "all".localized
case .barcode:
return "barcode".localized
case .qrcode:
return "qrcode".localized
case .scanned:
return "scanned".localized
case .created:
return "created".localized
case .favorites:
return "favorites".localized
}
}
var icon: String {
switch self {
case .all:
return "list.bullet"
case .barcode:
return "barcode"
case .qrcode:
return "qrcode"
case .scanned:
return "camera.viewfinder"
case .created:
return "plus.circle"
case .favorites:
return "heart.fill"
}
}
}
var filteredItems: [HistoryItem] {
// ä½¿ç¨ refreshTrigger 强åˆè§¦å<EFBFBD>计ç®å±žæ§æ´æ°
let _ = refreshTrigger
let searchResults = allHistoryItems.filter { item in
if !searchText.isEmpty {
let content = item.content ?? ""
let barcodeType = item.barcodeType ?? ""
let qrCodeType = item.qrCodeType ?? ""
return content.localizedCaseInsensitiveContains(searchText) ||
barcodeType.localizedCaseInsensitiveContains(searchText) ||
qrCodeType.localizedCaseInsensitiveContains(searchText)
}
return true
}
switch selectedFilter {
case .all:
return searchResults
case .barcode:
return searchResults.filter { $0.dataType == DataType.barcode.rawValue }
case .qrcode:
return searchResults.filter { $0.dataType == DataType.qrcode.rawValue }
case .scanned:
return searchResults.filter { $0.dataSource == DataSource.scanned.rawValue }
case .created:
return searchResults.filter { $0.dataSource == DataSource.created.rawValue }
case .favorites:
return searchResults.filter { $0.isFavorite }
}
}
var body: some View {
VStack(spacing: 0) {
// æ<EFBFBD>œç´¢æ <EFBFBD>
searchBar
// è¿æ»¤å¨
filterBar
// å容åˆè¡¨
if filteredItems.isEmpty {
emptyStateView
} else {
historyList
}
}
.navigationTitle("history_records".localized)
.id(languageManager.refreshTrigger)
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
HStack(spacing: 16) {
if isBatchDeleteMode {
// æ¹é<EFBFBD>åˆ é¤æ¨¡å¼<EFBFBD>ä¸çšæŒé®
Button(action: {
// å¨é/å<EFBFBD>æˆå¨é
if selectedItemsForDelete.count == filteredItems.count {
selectedItemsForDelete.removeAll()
} else {
selectedItemsForDelete = Set(filteredItems.compactMap { $0.id })
}
}) {
Image(systemName: selectedItemsForDelete.count == filteredItems.count ? "checkmark.rectangle.fill" : "rectangle.on.rectangle")
.foregroundColor(.blue)
}
Button(action: {
if !selectedItemsForDelete.isEmpty {
deleteSelectedItems()
}
}) {
Image(systemName: "trash.fill")
.foregroundColor(.red)
}
.disabled(selectedItemsForDelete.isEmpty)
// è¿åžæ­£å¸¸çŠæ<EFBFBD>æŒé®
Button(action: {
exitBatchDeleteMode()
}) {
Image(systemName: "xmark.circle")
.foregroundColor(.gray)
}
} else {
// 正常模å¼<EFBFBD>ä¸çšæŒé®
// å<EFBFBD>ªæœå½æœè®°å½ææ<EFBFBD>æ˜¾ç¤ºåˆ é¤æŒé®
if !allHistoryItems.isEmpty {
Button(action: {
enterBatchDeleteMode()
}) {
Image(systemName: "trash")
.foregroundColor(.red)
}
}
NavigationLink(destination: CodeTypeSelectionView()) {
Image(systemName: "plus")
}
}
}
}
}
.sheet(isPresented: $showingClearConfirmSheet) {
ClearHistoryConfirmView(
isPresented: $showingClearConfirmSheet,
onConfirm: clearHistory
)
}
.alert("delete_confirmation".localized, isPresented: $showingDeleteAlert) {
Button("cancel".localized, role: .cancel) { }
Button("delete".localized, role: .destructive) {
if let item = itemToDelete {
deleteHistoryItem(item)
itemToDelete = nil
}
}
} message: {
if let item = itemToDelete {
Text(String(format: "confirm_delete_record".localized, item.content ?? ""))
.id(languageManager.refreshTrigger)
}
}
.onAppear {
loadHistoryItems()
}
.onReceive(coreDataManager.objectWillChange) { _ in
// å½Core Dataæ°æ<EFBFBD>®å<EFBFBD>çŸå<EFBFBD>˜åŒæï¼Œé<EFBFBD>æ°åŠ è½½åŽå<EFBFBD>²è®°å½
loadHistoryItems()
}
.background(editNavigationLink)
}
// MARK: - 加载åŽå<EFBFBD>²è®°å½
private func loadHistoryItems() {
isLoading = true
currentPage = 0
hasMoreData = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
let items = coreDataManager.fetchHistoryItems(page: currentPage)
allHistoryItems = items
// æ£æŸ¥æ˜¯å<EFBFBD>¦è¿˜æœæ´å¤šæ°æ<EFBFBD>®
let nextPageItems = coreDataManager.fetchHistoryItems(page: currentPage + 1)
hasMoreData = !nextPageItems.isEmpty
isLoading = false
}
}
private func loadMoreHistoryItems() {
guard !isLoadingMore && hasMoreData else { return }
isLoadingMore = true
currentPage += 1
DispatchQueue.global(qos: .userInitiated).async {
let newItems = coreDataManager.fetchHistoryItems(page: currentPage)
DispatchQueue.main.async {
if newItems.isEmpty {
self.hasMoreData = false
} else {
self.allHistoryItems.append(contentsOf: newItems)
// æ£æŸ¥æ˜¯å<EFBFBD>¦è¿˜æœæ´å¤šæ°æ<EFBFBD>®
let nextPageItems = coreDataManager.fetchHistoryItems(page: self.currentPage + 1)
self.hasMoreData = !nextPageItems.isEmpty
}
self.isLoadingMore = false
}
}
}
// MARK: - è¿æ»¤å¨æ<EFBFBD>作
private func filterAction(for filter: HistoryFilter) {
// ç´æŽ¥åˆæ<EFBFBD>¢è¿æ»¤å¨ï¼Œæ ä»»ä½å»è¿Ÿ
selectedFilter = filter
}
// MARK: - æ¸ç©ºåŽå<EFBFBD>²è®°å½
private func clearHistory() {
coreDataManager.clearAllHistory()
allHistoryItems.removeAll()
refreshTrigger.toggle()
}
// MARK: - åˆæ<EFBFBD>¢æè<EFBFBD>çŠæ<EFBFBD>
private func toggleFavorite(_ item: HistoryItem) {
// åˆä¿<EFBFBD>存到 Core Data
item.isFavorite.toggle()
coreDataManager.save()
// æ´æ°æœ¬åœ°ç¼å­˜ï¼Œé<EFBFBD>¿å<EFBFBD>é<EFBFBD>æ°åŠ è½½æ°æ<EFBFBD>®
if let index = allHistoryItems.firstIndex(where: { $0.id == item.id }) {
allHistoryItems[index].isFavorite = item.isFavorite
}
// 强åˆè§¦å<EFBFBD>è§å¾åˆ·æ°
refreshTrigger.toggle()
}
// MARK: - 删é¤åŽå<EFBFBD>²è®°å½
private func deleteHistoryItem(_ item: HistoryItem) {
coreDataManager.deleteHistoryItem(item)
// 从本地ç¼å­˜ä¸­ç§»é¤
allHistoryItems.removeAll { $0.id == item.id }
refreshTrigger.toggle()
}
// MARK: - 显示删é¤ç¡®è®¤
private func showDeleteConfirmation(for item: HistoryItem) {
itemToDelete = item
showingDeleteAlert = true
}
// MARK: - 显示ç¼è¾çŒé<EFBFBD>¢
private func showEditView(for item: HistoryItem) {
itemToEdit = item
showingEditView = true
}
// MARK: - 获å<EFBFBD>二维ç <EFBFBD>ç±»åž
private func getQRCodeType(from item: HistoryItem) -> QRCodeType {
if let qrCodeTypeString = item.qrCodeType,
let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) {
return qrCodeType
}
return .text // 默认类åž
}
// MARK: - 获å<EFBFBD>æ ·å¼<EFBFBD>æ°æ<EFBFBD>®
private func getStyleData(from item: HistoryItem) -> QRCodeStyleData? {
// 从åŽå<EFBFBD>²è®°å½é¡¹ä¸­æ<EFBFBD><EFBFBD>å<EFBFBD>æ ·å¼<EFBFBD>æ°æ<EFBFBD>®
guard let jsonString = item.qrCodeStyleData,
let jsonData = jsonString.data(using: .utf8) else {
return nil
}
do {
let styleData = try JSONDecoder().decode(QRCodeStyleData.self, from: jsonData)
return styleData
} catch {
print("â<EFBFBD>Œ æ ·å¼<C3A5>æ•°æ<C2B0>®JSONè§£ç <C3A7>失败:\(error)")
return nil
}
}
// MARK: - ç¼è¾å¯¼èˆªé¾æŽ¥
private var editNavigationLink: some View {
NavigationLink(
destination: Group {
if let itemToEdit = itemToEdit {
QRCodeStyleView(
qrCodeContent: itemToEdit.content ?? "",
qrCodeType: getQRCodeType(from: itemToEdit),
existingStyleData: getStyleData(from: itemToEdit),
historyItem: itemToEdit
)
}
},
isActive: $showingEditView
) {
EmptyView()
}
}
// MARK: - æ¹é<EFBFBD>删é¤ç¸å³æ¹æ³
private func enterBatchDeleteMode() {
isBatchDeleteMode = true
// 默认å¨éå½å<EFBFBD>è¿æ»¤å<EFBFBD>Žçšé¡¹ç®
selectedItemsForDelete = Set(filteredItems.compactMap { $0.id })
}
private func exitBatchDeleteMode() {
isBatchDeleteMode = false
selectedItemsForDelete.removeAll()
}
private func deleteSelectedItems() {
// 获å<EFBFBD>é中çšé¡¹ç®
let itemsToDelete = allHistoryItems.filter { item in
guard let id = item.id else { return false }
return selectedItemsForDelete.contains(id)
}
// æ¹é<EFBFBD>删é¤
for item in itemsToDelete {
coreDataManager.deleteHistoryItem(item)
}
// 从本地ç¼å­˜ä¸­ç§»é¤
allHistoryItems.removeAll { item in
guard let id = item.id else { return false }
return selectedItemsForDelete.contains(id)
}
// æ¸ç©ºéæ©çŠæ<EFBFBD>å¹éåºæ¹é<EFBFBD>åˆ é¤æ¨¡å¼<EFBFBD>
selectedItemsForDelete.removeAll()
isBatchDeleteMode = false
// 强åˆåˆ·æ°æ°æ<EFBFBD>®
DispatchQueue.main.async {
allHistoryItems = coreDataManager.fetchHistoryItems()
refreshTrigger.toggle()
}
}
// MARK: - æ<EFBFBD>œç´¢æ <EFBFBD>
private var searchBar: some View {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
TextField("search_history_records".localized, text: $searchText)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
.padding(.horizontal)
.padding(.vertical, 8)
.background(Color(.systemBackground))
}
// MARK: - è¿æ»¤å¨æ <EFBFBD>
private var filterBar: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(HistoryFilter.allCases, id: \.self) { filter in
FilterChip(
filter: filter,
isSelected: selectedFilter == filter,
isLoading: false,
action: {
filterAction(for: filter)
}
)
}
}
.padding(.horizontal)
}
.padding(.vertical, 8)
.background(Color(.systemBackground))
}
// MARK: - åŽå<EFBFBD>²è®°å½åˆè¡¨
private var historyList: some View {
List {
if isLoading {
HStack {
Spacer()
VStack(spacing: 16) {
ProgressView()
.scaleEffect(1.2)
Text("loading".localized)
.font(.caption)
.foregroundColor(.secondary)
.id(languageManager.refreshTrigger)
}
.padding(.vertical, 40)
Spacer()
}
} else {
ForEach(filteredItems) { item in
HistoryItemRow(
item: item,
onToggleFavorite: {
toggleFavorite(item)
},
onDelete: {
showDeleteConfirmation(for: item)
},
onEdit: {
showEditView(for: item)
},
isBatchDeleteMode: isBatchDeleteMode,
isSelected: selectedItemsForDelete.contains(item.id ?? UUID()),
onToggleSelection: {
if let id = item.id {
if selectedItemsForDelete.contains(id) {
selectedItemsForDelete.remove(id)
} else {
selectedItemsForDelete.insert(id)
}
}
}
)
}
// 加载æ´å¤šæŒé®
if hasMoreData && !isLoadingMore {
Button(action: loadMoreHistoryItems) {
HStack {
Image(systemName: "arrow.down.circle")
Text("load_more".localized)
}
.foregroundColor(.blue)
.padding()
}
}
// 加载æ´å¤šæŒç¤ºå¨
if isLoadingMore {
HStack {
Spacer()
ProgressView()
.scaleEffect(0.8)
Text("loading_more".localized)
.font(.caption)
.foregroundColor(.secondary)
Spacer()
}
.padding()
}
}
}
.listStyle(PlainListStyle())
}
// MARK: - ç©ºçŠæ<EFBFBD>è§å¾
private var emptyStateView: some View {
VStack(spacing: 20) {
Image(systemName: "clock.arrow.circlepath")
.font(.system(size: 60))
.foregroundColor(.gray)
Text("no_history_records".localized)
.font(.title2)
.fontWeight(.medium)
.foregroundColor(.gray)
.id(languageManager.refreshTrigger)
Text("scan_or_create_to_start".localized)
.font(.body)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
.id(languageManager.refreshTrigger)
NavigationLink(destination: CodeTypeSelectionView()) {
HStack {
Image(systemName: "plus.circle.fill")
Text("create_first_record".localized)
.id(languageManager.refreshTrigger)
}
.font(.headline)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
#Preview {
NavigationView {
HistoryView()
.environmentObject(LanguageManager.shared)
}
}
// MARK: - è¿æ»¤å¨èНç
struct FilterChip: View {
let filter: HistoryView.HistoryFilter
let isSelected: Bool
let isLoading: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: 6) {
Image(systemName: filter.icon)
.font(.system(size: 14))
Text(filter.displayName)
.font(.system(size: 14, weight: .medium))
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(
RoundedRectangle(cornerRadius: 20)
.fill(isSelected ? Color.blue : Color(.systemGray5))
)
.foregroundColor(isSelected ? .white : .primary)
.scaleEffect(isSelected ? 1.05 : 1.0)
}
.buttonStyle(OptimizedFilterChipButtonStyle(isSelected: isSelected))
.animation(.easeInOut(duration: 0.2), value: isSelected)
}
}
// MARK: - 优åŒçšè¿æ»¤å¨èŠ¯çæŒé®æ ·å¼<EFBFBD>
struct OptimizedFilterChipButtonStyle: ButtonStyle {
let isSelected: Bool
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.opacity(configuration.isPressed ? 0.9 : 1.0)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}
// MARK: - è¿æ»¤å¨èŠ¯çæŒé®æ ·å¼<EFBFBD>(ä¿<EFBFBD>ç以防å<EFBFBD>å<EFBFBD>Žå¼å®¹ï¼
struct FilterChipButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.opacity(configuration.isPressed ? 0.8 : 1.0)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}
// MARK: - åŽå<EFBFBD>²è®°å½é¡¹è¡Œ
struct HistoryItemRow: View {
let item: HistoryItem
let onToggleFavorite: () -> Void
let onDelete: () -> Void
let onEdit: (() -> Void)?
let isBatchDeleteMode: Bool
let isSelected: Bool
let onToggleSelection: () -> Void
var body: some View {
HStack(spacing: 12) {
// æ¹é<EFBFBD>åˆ é¤æ¨¡å¼<EFBFBD>ä¸çšéæ©æ¡
if isBatchDeleteMode {
Button(action: onToggleSelection) {
Image(systemName: isSelected ? "checkmark.square.fill" : "square")
.font(.system(size: 20))
.foregroundColor(isSelected ? .blue : .gray)
}
.buttonStyle(PlainButtonStyle())
}
// ç±»åžå¾æ 
VStack {
if let dataTypeString = item.dataType,
let dataType = DataType(rawValue: dataTypeString) {
Image(systemName: dataType.icon)
.font(.system(size: 24))
.foregroundColor(.blue)
}
if let dataSourceString = item.dataSource,
let dataSource = DataSource(rawValue: dataSourceString) {
Image(systemName: dataSource.icon)
.font(.system(size: 12))
.foregroundColor(.gray)
}
}
.frame(width: 40)
// å容信æ<EFBFBD>¯
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(item.content ?? "")
.font(.headline)
.lineLimit(1)
Spacer()
Button(action: onToggleFavorite) {
Image(systemName: item.isFavorite ? "heart.fill" : "heart")
.foregroundColor(item.isFavorite ? .red : .gray)
}
.buttonStyle(PlainButtonStyle())
}
HStack {
// ç±»åžæ ç­¾
if let dataTypeString = item.dataType,
let dataType = DataType(rawValue: dataTypeString) {
HStack(spacing: 4) {
Image(systemName: dataType.icon)
.font(.system(size: 12))
Text(dataType.displayName)
.font(.caption)
}
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background(Color.blue.opacity(0.1))
.foregroundColor(.blue)
.cornerRadius(8)
}
// å·ä½ç±»åžæ ç­¾
if let barcodeTypeString = item.barcodeType,
let barcodeType = BarcodeType(rawValue: barcodeTypeString) {
HStack(spacing: 4) {
Image(systemName: barcodeType.icon)
.font(.system(size: 12))
Text(barcodeType.displayName)
.font(.caption)
}
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background(Color.green.opacity(0.1))
.foregroundColor(.green)
.cornerRadius(8)
}
if let qrCodeTypeString = item.qrCodeType,
let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) {
HStack(spacing: 4) {
Image(systemName: qrCodeType.icon)
.font(.system(size: 12))
Text(qrCodeType.displayName)
.font(.caption)
}
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background(Color.orange.opacity(0.1))
.foregroundColor(.orange)
.cornerRadius(8)
}
Spacer()
// æé´
if let createdAt = item.createdAt {
Text(formatDate(createdAt))
.font(.caption)
.foregroundColor(.gray)
}
}
}
Spacer()
}
.padding(.vertical, 8)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
// åˆäº«æŒé®
Button(action: {
shareItem(item)
}) {
Image(systemName: "square.and.arrow.up")
}
.tint(.gray)
// 为åˆå»ºç±»åžçšäºŒç»´ç <EFBFBD>æ<EFBFBD>¡ç®æ·»åŠ ç¼è¾æŒé®
if item.dataType == DataType.qrcode.rawValue &&
item.dataSource == DataSource.created.rawValue,
let onEdit = onEdit {
Button(action: onEdit) {
Image(systemName: "pencil")
}
.tint(.blue)
}
Button(action: onDelete) {
Image(systemName: "trash")
}
.tint(.red)
}
.background(
// æ ¹æ<EFBFBD>®æ°æ<EFBFBD>®ç±»åžåŒæ°æ<EFBFBD>®æº<EFBFBD>æ·»åŠ å¯¼èˆªé¾æŽ¥
Group {
if item.dataType == DataType.qrcode.rawValue {
if item.dataSource == DataSource.created.rawValue {
// åˆå»ºç±»åžçšäºŒç»´ç <EFBFBD>æ<EFBFBD>¡ç®ï¼Œå¯¼èˆªåˆ°ä¿<EFBFBD>å­˜çŒé<EFBFBD>¢
NavigationLink(
destination: QRCodeSavedView(
qrCodeImage: generateQRCodeImage(from: item),
qrCodeContent: item.content ?? "",
qrCodeType: getQRCodeType(from: item),
styleData: getStyleData(from: item),
historyItem: item
),
label: { EmptyView() }
)
} else {
// æ«æ<EFBFBD><EFBFBD>ç±»åžçšäºŒç»´ç <EFBFBD>æ<EFBFBD>¡ç®ï¼Œå¯¼èˆªåˆ°è¯¦æƒçŒé<EFBFBD>¢
NavigationLink(
destination: QRCodeDetailView(historyItem: item),
label: { EmptyView() }
)
}
} else if item.dataType == DataType.barcode.rawValue {
NavigationLink(
destination: BarcodeDetailView(historyItem: item),
label: { EmptyView() }
)
}
}
)
}
private func formatDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter.string(from: date)
}
// MARK: - QRCodeSavedView è¾åŠ©æ¹æ³
private func generateQRCodeImage(from item: HistoryItem) -> UIImage {
// 从åŽå<EFBFBD>²è®°å½é¡¹çŸæˆ<EFBFBD>二维ç <EFBFBD>å¾ç
let content = item.content ?? ""
// 获å<EFBFBD>æ ·å¼<EFBFBD>æ°æ<EFBFBD>®
let styleData = getStyleData(from: item)
// 使ç¨é«˜è´¨é<EFBFBD>二维ç <EFBFBD>çŸæˆ<EFBFBD>å¨
return QRCodeGenerator.generateHighQualityQRCode(
content: content,
styleData: styleData
)
}
// MARK: - åˆäº«åŠŸèƒ½
private func shareItem(_ item: HistoryItem) {
let content = item.content ?? ""
let activityVC = UIActivityViewController(
activityItems: [content],
applicationActivities: nil
)
// 在iPad上éœè¦<EFBFBD>设置popoverPresentationController
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first {
if let popover = activityVC.popoverPresentationController {
popover.sourceView = window
popover.sourceRect = CGRect(x: window.bounds.midX, y: window.bounds.midY, width: 0, height: 0)
popover.permittedArrowDirections = []
}
}
// 获å<EFBFBD>å½å<EFBFBD>è§å¾æŽ§åˆå¨å¹æ˜¾ç¤ºåˆäº«çŒé<EFBFBD>¢
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first,
let rootViewController = window.rootViewController {
rootViewController.present(activityVC, animated: true)
}
}
private func getQRCodeType(from item: HistoryItem) -> QRCodeType {
if let qrCodeTypeString = item.qrCodeType,
let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) {
return qrCodeType
}
return .text // 默认类åž
}
private func getStyleData(from item: HistoryItem) -> QRCodeStyleData? {
// 从åŽå<EFBFBD>²è®°å½é¡¹ä¸­æ<EFBFBD><EFBFBD>å<EFBFBD>æ ·å¼<EFBFBD>æ°æ<EFBFBD>®
guard let jsonString = item.qrCodeStyleData,
let jsonData = jsonString.data(using: .utf8) else {
return nil
}
do {
let styleData = try JSONDecoder().decode(QRCodeStyleData.self, from: jsonData)
return styleData
} catch {
print("â<EFBFBD>Œ æ ·å¼<C3A5>æ•°æ<C2B0>®JSONè§£ç <C3A7>失败:\(error)")
return nil
}
}
}
// MARK: - æ¸ç©ºåŽå<EFBFBD>²è®°å½ç¡®è®¤è§å¾
struct ClearHistoryConfirmView: View {
@EnvironmentObject var languageManager: LanguageManager
@Binding var isPresented: Bool
let onConfirm: () -> Void
var body: some View {
NavigationView {
VStack(spacing: 20) {
// è­¦åŠå¾æ 
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 50))
.foregroundColor(.red)
// æ é¢˜
Text("clear_history".localized)
.font(.title2)
.fontWeight(.bold)
.id(languageManager.refreshTrigger)
// ç®å<EFBFBD>说明
Text("clear_history_warning".localized)
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.id(languageManager.refreshTrigger)
Spacer()
// æŒé®åŒºåŸŸ
VStack(spacing: 12) {
// ç¡®è®¤åˆ é¤æŒé®
Button(action: {
onConfirm()
isPresented = false
}) {
HStack {
Image(systemName: "trash.fill")
Text("confirm_delete".localized)
.id(languageManager.refreshTrigger)
}
.frame(maxWidth: .infinity)
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(10)
}
// å<EFBFBD>æˆæŒé®
Button(action: {
isPresented = false
}) {
Text("cancel".localized)
.frame(maxWidth: .infinity)
.padding()
.background(Color(.systemGray5))
.foregroundColor(.primary)
.cornerRadius(10)
}
}
}
.padding(20)
.navigationTitle("confirm_delete".localized)
.id(languageManager.refreshTrigger)
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("close".localized) {
isPresented = false
}
.id(languageManager.refreshTrigger)
}
}
}
}
}