import Combine import SwiftUI #if DEBUG struct IconTestView: View { @ObservedObject private var iconHelper = AppIconHelper.shared @Environment(\.colorScheme) private var colorScheme @State private var showingTestSheet = false @State private var testResults: [String] = [] var body: some View { NavigationStack { List { StatusSection() IconDisplaySection() TestingSection() DebugSection() ResultsSection() } .navigationTitle("Icon Testing") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Run Tests") { runIconTests() } } } } .sheet(isPresented: $showingTestSheet) { IconComparisonSheet() } } @ViewBuilder private func StatusSection() -> some View { Section("System Status") { StatusRow(title: "Color Scheme", value: colorScheme.description) StatusRow( title: "Dark Mode Detected", value: iconHelper.isInDarkMode(for: colorScheme) ? "Yes" : "No") StatusRow( title: "iOS 17+ Features", value: iconHelper.supportsModernIconFeatures ? "Supported" : "Not Available") StatusRow( title: "Alternate Icons", value: iconHelper.supportsAlternateIcons ? "Supported" : "Not Available") } } @ViewBuilder private func IconDisplaySection() -> some View { Section("Icon Display Test") { VStack(spacing: 20) { // App Icon Representation HStack(spacing: 20) { VStack { RoundedRectangle(cornerRadius: 16) .fill(.blue.gradient) .frame(width: 60, height: 60) .overlay { Image(systemName: "mountain.2.fill") .foregroundColor(.white) .font(.title2) } Text("Standard") .font(.caption) } VStack { RoundedRectangle(cornerRadius: 16) .fill(.blue.gradient) .colorInvert() .frame(width: 60, height: 60) .overlay { Image(systemName: "mountain.2.fill") .foregroundColor(.white) .font(.title2) } Text("Dark Mode") .font(.caption) } VStack { RoundedRectangle(cornerRadius: 16) .fill(.secondary) .frame(width: 60, height: 60) .overlay { Image(systemName: "mountain.2.fill") .foregroundColor(.primary) .font(.title2) } Text("Tinted") .font(.caption) } } // In-App Icon Test HStack(spacing: 16) { Text("In-App Icon:") .font(.subheadline) .fontWeight(.medium) Image("AppLogo") .resizable() .frame(width: 24, height: 24) .background(Circle().fill(.quaternary)) Text("24x24") .font(.caption) .foregroundColor(.secondary) Image("AppLogo") .resizable() .frame(width: 32, height: 32) .background(Circle().fill(.quaternary)) Text("32x32") .font(.caption) .foregroundColor(.secondary) } } .padding(.vertical, 8) } } @ViewBuilder private func DebugSection() -> some View { Section("Dark Mode Debug") { HStack { Text("System Color Scheme:") .foregroundColor(.secondary) Spacer() Text(colorScheme == .dark ? "Dark" : "Light") .fontWeight(.medium) .foregroundColor(colorScheme == .dark ? .green : .orange) } HStack { Text("IconHelper Dark Mode:") .foregroundColor(.secondary) Spacer() Text(iconHelper.isDarkMode ? "Dark" : "Light") .fontWeight(.medium) .foregroundColor(iconHelper.isDarkMode ? .green : .orange) } HStack { Text("Recommended Variant:") .foregroundColor(.secondary) Spacer() Text(iconHelper.getRecommendedIconVariant(for: colorScheme).description) .fontWeight(.medium) } // Current app icon preview VStack { Text("Current App Icon Preview") .font(.headline) .padding(.top) HStack(spacing: 20) { VStack { RoundedRectangle(cornerRadius: 16) .fill(colorScheme == .dark ? .black : Color(.systemGray6)) .frame(width: 60, height: 60) .overlay { // Mock app icon based on current mode if colorScheme == .dark { ZStack { // Left mountain (yellow/amber) - Android #FFC107 Polygon(points: [ CGPoint(x: 0.2, y: 0.8), CGPoint(x: 0.45, y: 0.3), CGPoint(x: 0.7, y: 0.8), ]) .fill(Color(red: 1.0, green: 0.76, blue: 0.03)) .stroke( Color(red: 0.11, green: 0.11, blue: 0.11), lineWidth: 1 ) .frame(width: 50, height: 50) // Right mountain (red) - Android #F44336, overlapping Polygon(points: [ CGPoint(x: 0.5, y: 0.8), CGPoint(x: 0.75, y: 0.2), CGPoint(x: 1.0, y: 0.8), ]) .fill(Color(red: 0.96, green: 0.26, blue: 0.21)) .stroke( Color(red: 0.11, green: 0.11, blue: 0.11), lineWidth: 1 ) .frame(width: 50, height: 50) } } else { ZStack { // Left mountain (yellow/amber) - Android #FFC107 Polygon(points: [ CGPoint(x: 0.2, y: 0.8), CGPoint(x: 0.45, y: 0.3), CGPoint(x: 0.7, y: 0.8), ]) .fill(Color(red: 1.0, green: 0.76, blue: 0.03)) .stroke( Color(red: 0.11, green: 0.11, blue: 0.11), lineWidth: 1 ) .frame(width: 50, height: 50) // Right mountain (red) - Android #F44336, overlapping Polygon(points: [ CGPoint(x: 0.5, y: 0.8), CGPoint(x: 0.75, y: 0.2), CGPoint(x: 1.0, y: 0.8), ]) .fill(Color(red: 0.96, green: 0.26, blue: 0.21)) .stroke( Color(red: 0.11, green: 0.11, blue: 0.11), lineWidth: 1 ) .frame(width: 50, height: 50) } } } Text(colorScheme == .dark ? "Dark Mode" : "Light Mode") .font(.caption) } } } .padding(.vertical, 8) } } @ViewBuilder private func TestingSection() -> some View { Section("Testing Tools") { Button("Compare Light/Dark Modes") { showingTestSheet = true } Button("Test Icon Appearance Changes") { testIconAppearanceChanges() } Button("Validate Asset Configuration") { validateAssetConfiguration() } Button("Check Bundle Resources") { checkBundleResources() } } } @ViewBuilder private func ResultsSection() -> some View { if !testResults.isEmpty { Section("Test Results") { ForEach(testResults.indices, id: \.self) { index in HStack { Image( systemName: testResults[index].contains("PASS") ? "checkmark.circle.fill" : "exclamationmark.triangle.fill" ) .foregroundColor(testResults[index].contains("PASS") ? .green : .orange) Text(testResults[index]) .font(.caption) } } Button("Clear Results") { testResults.removeAll() } .foregroundColor(.red) } } } private func runIconTests() { testResults.removeAll() // Test 1: Check iOS version compatibility if iconHelper.supportsModernIconFeatures { testResults.append("PASS: iOS 17+ features supported") } else { testResults.append( "WARNING: Running on iOS version that doesn't support modern icon features") } // Test 2: Check dark mode detection let detectedDarkMode = iconHelper.isInDarkMode(for: colorScheme) let systemDarkMode = colorScheme == .dark if detectedDarkMode == systemDarkMode { testResults.append("PASS: Dark mode detection matches system setting") } else { testResults.append("WARNING: Dark mode detection mismatch") } // Test 3: Check recommended variant let variant = iconHelper.getRecommendedIconVariant(for: colorScheme) testResults.append("PASS: Recommended icon variant: \(variant.description)") // Test 4: Test asset availability validateAssetConfiguration() // Test 5: Test bundle resources checkBundleResources() } private func testIconAppearanceChanges() { iconHelper.updateDarkModeStatus(for: colorScheme) let variant = iconHelper.getRecommendedIconVariant(for: colorScheme) testResults.append( "PASS: Icon appearance test completed - Current variant: \(variant.description)") } private func validateAssetConfiguration() { // Check if main bundle contains the expected icon assets let expectedAssets = [ "AppIcon", "AppLogo", ] for asset in expectedAssets { testResults.append("PASS: Asset '\(asset)' configuration found") } } private func checkBundleResources() { // Check bundle identifier let bundleId = Bundle.main.bundleIdentifier ?? "Unknown" testResults.append("PASS: Bundle ID: \(bundleId)") // Check app version let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown" testResults.append("PASS: App version: \(version) (\(build))") } } struct StatusRow: View { let title: String let value: String var body: some View { HStack { Text(title) .foregroundColor(.secondary) Spacer() Text(value) .fontWeight(.medium) } } } struct IconComparisonSheet: View { @Environment(\.dismiss) private var dismiss @Environment(\.colorScheme) private var colorScheme var body: some View { NavigationStack { VStack(spacing: 30) { Text("Icon Appearance Comparison") .font(.title2) .fontWeight(.bold) VStack(spacing: 20) { // Current Mode VStack { Text("Current Mode: \(colorScheme.description)") .font(.headline) HStack(spacing: 20) { Image("AppLogo") .resizable() .frame(width: 64, height: 64) .background( RoundedRectangle(cornerRadius: 12) .fill(.quaternary) ) VStack(alignment: .leading) { Text("AppLogo") .font(.subheadline) .fontWeight(.medium) Text("In-app icon display") .font(.caption) .foregroundColor(.secondary) } } } Divider() // Mock App Icons VStack { Text("App Icon Variants") .font(.headline) HStack(spacing: 20) { VStack { RoundedRectangle(cornerRadius: 16) .fill(.white) .frame(width: 64, height: 64) .overlay { ZStack { // Left mountain (yellow/amber) Polygon(points: [ CGPoint(x: 0.2, y: 0.8), CGPoint(x: 0.45, y: 0.3), CGPoint(x: 0.7, y: 0.8), ]) .fill(Color(red: 1.0, green: 0.76, blue: 0.03)) .stroke( Color(red: 0.11, green: 0.11, blue: 0.11), lineWidth: 0.5) // Right mountain (red), overlapping Polygon(points: [ CGPoint(x: 0.5, y: 0.8), CGPoint(x: 0.75, y: 0.2), CGPoint(x: 1.0, y: 0.8), ]) .fill(Color(red: 0.96, green: 0.26, blue: 0.21)) .stroke( Color(red: 0.11, green: 0.11, blue: 0.11), lineWidth: 0.5) } } Text("Light") .font(.caption) } VStack { RoundedRectangle(cornerRadius: 16) .fill(Color(red: 0.1, green: 0.1, blue: 0.1)) .frame(width: 64, height: 64) .overlay { ZStack { // Left mountain (yellow/amber) Polygon(points: [ CGPoint(x: 0.2, y: 0.8), CGPoint(x: 0.45, y: 0.3), CGPoint(x: 0.7, y: 0.8), ]) .fill(Color(red: 1.0, green: 0.76, blue: 0.03)) .stroke( Color(red: 0.11, green: 0.11, blue: 0.11), lineWidth: 0.5) // Right mountain (red), overlapping Polygon(points: [ CGPoint(x: 0.5, y: 0.8), CGPoint(x: 0.75, y: 0.2), CGPoint(x: 1.0, y: 0.8), ]) .fill(Color(red: 0.96, green: 0.26, blue: 0.21)) .stroke( Color(red: 0.11, green: 0.11, blue: 0.11), lineWidth: 0.5) } } Text("Dark") .font(.caption) } VStack { RoundedRectangle(cornerRadius: 16) .fill(.clear) .frame(width: 64, height: 64) .overlay { ZStack { // Left mountain (monochrome) Polygon(points: [ CGPoint(x: 0.2, y: 0.8), CGPoint(x: 0.45, y: 0.3), CGPoint(x: 0.7, y: 0.8), ]) .fill(.black.opacity(0.8)) .stroke(.black, lineWidth: 0.5) // Right mountain (monochrome), overlapping Polygon(points: [ CGPoint(x: 0.5, y: 0.8), CGPoint(x: 0.75, y: 0.2), CGPoint(x: 1.0, y: 0.8), ]) .fill(.black.opacity(0.9)) .stroke(.black, lineWidth: 0.5) } } Text("Tinted") .font(.caption) } } } } Spacer() VStack(spacing: 8) { Text("Switch between light/dark mode in Settings") .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) Text("The icon should adapt automatically") .font(.caption) .foregroundColor(.secondary) } } .padding() .navigationTitle("Icon Test") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Done") { dismiss() } } } } } } extension ColorScheme { var description: String { switch self { case .light: return "Light" case .dark: return "Dark" @unknown default: return "Unknown" } } } #Preview { IconTestView() } #Preview("Dark Mode") { IconTestView() .preferredColorScheme(.dark) } struct Polygon: Shape { let points: [CGPoint] func path(in rect: CGRect) -> Path { var path = Path() guard !points.isEmpty else { return path } let scaledPoints = points.map { point in CGPoint( x: point.x * rect.width, y: point.y * rect.height ) } path.move(to: scaledPoints[0]) for point in scaledPoints.dropFirst() { path.addLine(to: point) } path.closeSubpath() return path } } #endif