Implements a new UI to show recommended image counts for ML training. Uses color-coded indicators (orange/green/blue) for Functional, Solid, and High-Accuracy thresholds across all 28 training categories (Foil, Stamp, and Condition models). Critical damage types (Inking, Rips, Water Damage) carry higher recommended counts to minimise false positives on NM grades. Accessible via a "?" toolbar button in Library. Bumps app version to 1.1.0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
105 lines
3.2 KiB
Swift
105 lines
3.2 KiB
Swift
// IYmtg_App_iOS/Features/Help/TrainingGuideView.swift
|
|
import SwiftUI
|
|
|
|
struct TrainingGuideView: View {
|
|
@StateObject private var viewModel = TrainingGuideViewModel()
|
|
|
|
var body: some View {
|
|
List {
|
|
Section {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text("These are the recommended image counts for each ML training category. Collect cropped card photos and drop them into the matching `IYmtg_Training/` subfolder.")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
HStack(spacing: 16) {
|
|
LegendChip(color: .orange, label: "Functional")
|
|
LegendChip(color: .green, label: "Solid")
|
|
LegendChip(color: .blue, label: "High-Accuracy")
|
|
}
|
|
.padding(.top, 2)
|
|
}
|
|
.listRowBackground(Color.clear)
|
|
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
|
}
|
|
|
|
ForEach(viewModel.groups, id: \.self) { group in
|
|
Section(header: GroupHeader(title: group)) {
|
|
ForEach(viewModel.categories(for: group)) { category in
|
|
TrainingCategoryRow(category: category)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Training Guide")
|
|
.listStyle(.insetGrouped)
|
|
}
|
|
}
|
|
|
|
// MARK: - Supporting Views
|
|
|
|
private struct GroupHeader: View {
|
|
let title: String
|
|
|
|
private var icon: String {
|
|
switch title {
|
|
case "Foil Model": return "sparkles"
|
|
case "Stamp Model": return "seal.fill"
|
|
case "Condition Model": return "exclamationmark.triangle"
|
|
default: return "circle.grid.2x2"
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
Label(title, systemImage: icon)
|
|
}
|
|
}
|
|
|
|
private struct TrainingCategoryRow: View {
|
|
let category: TrainingCategory
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text(category.name)
|
|
.font(.subheadline)
|
|
.bold()
|
|
HStack(spacing: 0) {
|
|
StatusIndicator(level: "Functional", count: category.functionalCount, color: .orange)
|
|
Spacer()
|
|
StatusIndicator(level: "Solid", count: category.solidCount, color: .green)
|
|
Spacer()
|
|
StatusIndicator(level: "High-Accuracy", count: category.highAccuracyCount, color: .blue)
|
|
}
|
|
}
|
|
.padding(.vertical, 4)
|
|
}
|
|
}
|
|
|
|
struct StatusIndicator: View {
|
|
let level: String
|
|
let count: Int
|
|
let color: Color
|
|
|
|
var body: some View {
|
|
HStack(spacing: 5) {
|
|
Circle()
|
|
.fill(color)
|
|
.frame(width: 9, height: 9)
|
|
Text("\(level): \(count)+")
|
|
.font(.caption2)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct LegendChip: View {
|
|
let color: Color
|
|
let label: String
|
|
|
|
var body: some View {
|
|
HStack(spacing: 4) {
|
|
Circle().fill(color).frame(width: 8, height: 8)
|
|
Text(label).font(.caption2).foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|