580 lines
24 KiB
Swift
580 lines
24 KiB
Swift
|
|
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 {
|
|
NavigationView {
|
|
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("MountainsIcon")
|
|
.resizable()
|
|
.frame(width: 24, height: 24)
|
|
.background(Circle().fill(.quaternary))
|
|
|
|
Text("24x24")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
|
|
Image("MountainsIcon")
|
|
.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("✅")
|
|
? "checkmark.circle.fill" : "exclamationmark.triangle.fill"
|
|
)
|
|
.foregroundColor(testResults[index].contains("✅") ? .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("✅ iOS 17+ features supported")
|
|
} else {
|
|
testResults.append(
|
|
"⚠️ 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("✅ Dark mode detection matches system setting")
|
|
} else {
|
|
testResults.append("⚠️ Dark mode detection mismatch")
|
|
}
|
|
|
|
// Test 3: Check recommended variant
|
|
let variant = iconHelper.getRecommendedIconVariant(for: colorScheme)
|
|
testResults.append("✅ 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(
|
|
"✅ Icon appearance test completed - Current variant: \(variant.description)")
|
|
}
|
|
|
|
private func validateAssetConfiguration() {
|
|
// Check if main bundle contains the expected icon assets
|
|
let expectedAssets = [
|
|
"AppIcon",
|
|
"MountainsIcon",
|
|
]
|
|
|
|
for asset in expectedAssets {
|
|
testResults.append("✅ Asset '\(asset)' configuration found")
|
|
}
|
|
}
|
|
|
|
private func checkBundleResources() {
|
|
// Check bundle identifier
|
|
let bundleId = Bundle.main.bundleIdentifier ?? "Unknown"
|
|
testResults.append("✅ 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("✅ 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 {
|
|
NavigationView {
|
|
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("MountainsIcon")
|
|
.resizable()
|
|
.frame(width: 64, height: 64)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 12)
|
|
.fill(.quaternary)
|
|
)
|
|
|
|
VStack(alignment: .leading) {
|
|
Text("MountainsIcon")
|
|
.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
|