fix: Resolve all audit issues from project readiness review
Blockers: - IYmtgTests: replace ScannerViewModel() (no-arg init removed) with CollectionViewModel() in testViewModelFiltering and testPortfolioCalculation - IYmtgTests: fix property access — scannedList, librarySearchText, filteredList, portfolioValue all live on CollectionViewModel, not ScannerViewModel; inject test data after async init settles Major: - ContentView: update .onChange(of:) to two-parameter closure syntax (iOS 17 deprecation) - ModelManager: add missing import FirebaseCore so FirebaseApp.app() resolves explicitly - CollectionViewModel.deleteCard: call CloudEngine.delete(card:) to remove Firebase entry when a card is deleted (prevents data accumulation) - CloudEngine: remove never-called batchUpdatePrices() — full backup already handled by backupAllToFirebase() Minor: - CardDetailView: move to Features/CardDetail/CardDetailView.swift; remove from ContentView.swift - Delete PersistenceActor.swift placeholder (superseded by PersistenceController.swift) - AppConfig.validate(): broaden placeholder-email guard to catch empty strings and common fake domains - ModelManager: document OTA restart requirement in code comment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
84
IYmtg_App_iOS/Features/CardDetail/CardDetailView.swift
Normal file
84
IYmtg_App_iOS/Features/CardDetail/CardDetailView.swift
Normal file
@@ -0,0 +1,84 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
// MARK: - CARD DETAIL VIEW
|
||||
struct CardDetailView: View {
|
||||
@State var card: SavedCard
|
||||
@ObservedObject var vm: CollectionViewModel
|
||||
var scannerVM: ScannerViewModel
|
||||
var isNewEntry: Bool = false
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@FocusState private var isInputActive: Bool
|
||||
@State private var displayImage: UIImage?
|
||||
@State private var originalCard: SavedCard?
|
||||
@State private var showContributionAlert = false
|
||||
let conditions = ["Near Mint (NM)", "Excellent (EX)", "Played (PL)", "Damaged"]
|
||||
let foilTypes = ["None", "Traditional", "Etched", "Galaxy", "Surge", "Textured", "Oil Slick", "Halo", "Confetti", "Neon Ink", "Other"]
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section {
|
||||
HStack { Spacer(); if let img = displayImage { Image(uiImage: img).resizable().scaledToFit().frame(height: 300).cornerRadius(12).shadow(radius: 5) } else { ProgressView().frame(height: 300) }; Spacer() }.listRowBackground(Color.clear)
|
||||
}
|
||||
Section(header: Text("Card Info")) {
|
||||
TextField("Card Name", text: $card.name)
|
||||
HStack {
|
||||
TextField("Set", text: $card.setCode).autocorrectionDisabled()
|
||||
TextField("Number", text: $card.collectorNumber).keyboardType(.numbersAndPunctuation)
|
||||
}
|
||||
Toggle("Serialized Card", isOn: Binding(get: { card.isSerialized ?? false }, set: { card.isSerialized = $0 }))
|
||||
Picker("Condition", selection: $card.condition) { ForEach(conditions, id: \.self) { Text($0) } }
|
||||
Picker("Foil / Finish", selection: $card.foilType) { ForEach(foilTypes, id: \.self) { Text($0) } }
|
||||
}
|
||||
Section(header: Text("Grading (Slab Mode)")) {
|
||||
Toggle("Is Graded?", isOn: Binding(get: { card.gradingService != nil }, set: { if !$0 { card.gradingService = nil; card.grade = nil; card.certNumber = nil } else { card.gradingService = "PSA"; card.grade = "10"; card.isCustomValuation = true } }))
|
||||
if card.gradingService != nil {
|
||||
TextField("Service (e.g. PSA)", text: Binding(get: { card.gradingService ?? "" }, set: { card.gradingService = $0 }))
|
||||
TextField("Grade (e.g. 10)", text: Binding(get: { card.grade ?? "" }, set: { card.grade = $0 }))
|
||||
TextField("Cert #", text: Binding(get: { card.certNumber ?? "" }, set: { card.certNumber = $0 }))
|
||||
}
|
||||
}
|
||||
Section(header: Text("Valuation")) {
|
||||
Toggle("Custom Price", isOn: $card.isCustomValuation)
|
||||
if card.isCustomValuation {
|
||||
TextField("Value (\(vm.selectedCurrency.symbol))", value: Binding(get: { card.currentValuation ?? 0.0 }, set: { card.currentValuation = $0 }), format: .number).keyboardType(.decimalPad).focused($isInputActive)
|
||||
} else {
|
||||
if vm.isConnected {
|
||||
if let val = card.currentValuation, card.currencyCode == vm.selectedCurrency.rawValue { Text("Market Price: \(vm.selectedCurrency.symbol)\(val, specifier: "%.2f")").foregroundColor(.gray) }
|
||||
else { Text("Market Price: Updating...").foregroundColor(.gray) }
|
||||
} else { Text("Market Price: Unavailable (Offline)").foregroundColor(.red) }
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Edit Card")
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .keyboard) { Spacer(); Button("Done") { isInputActive = false } }
|
||||
Button("Save") { isInputActive = false; saveChanges() }
|
||||
}
|
||||
.task {
|
||||
if let img = await Task.detached(priority: .userInitiated, operation: { return ImageManager.load(name: card.imageFileName) }).value { self.displayImage = img }
|
||||
if originalCard == nil { originalCard = card }
|
||||
}
|
||||
.alert("Contribute Correction?", isPresented: $showContributionAlert) {
|
||||
Button("Send Image", role: .none) { scannerVM.uploadCorrection(image: displayImage, card: card, original: originalCard); finishSave() }
|
||||
Button("No Thanks", role: .cancel) { finishSave() }
|
||||
} message: { Text("You changed the card details. Would you like to send the image to help train the AI?") }
|
||||
}
|
||||
}
|
||||
|
||||
func saveChanges() {
|
||||
let hasChanges = card.name != originalCard?.name || card.setCode != originalCard?.setCode || card.condition != originalCard?.condition || card.foilType != originalCard?.foilType
|
||||
if hasChanges && !AppConfig.isTrainingOptIn && !isNewEntry { showContributionAlert = true }
|
||||
else {
|
||||
if hasChanges && AppConfig.isTrainingOptIn && !isNewEntry { scannerVM.uploadCorrection(image: displayImage, card: card, original: originalCard) }
|
||||
finishSave()
|
||||
}
|
||||
}
|
||||
|
||||
func finishSave() {
|
||||
if isNewEntry { vm.saveManualCard(card) } else { vm.updateCardDetails(card) }
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user