From 24c2ba0f12bef8ffdd5bd1b1971cd1621882554d Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Thu, 3 Jul 2025 11:35:36 -0700 Subject: [PATCH] HAM Operator Quiz for iOS Amateur Radio License Exam preparation app for iOS. --- .../project.pbxproj | 329 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../UserInterfaceState.xcuserstate | Bin 0 -> 27138 bytes .../xcschemes/xcschememanagement.plist | 14 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++ .../Assets.xcassets/Contents.json | 6 + .../HAM_Operator_Quiz_iPhoneApp.swift | 12 + .../HAM Operator Quiz iPhone/QuizView.swift | 238 +++++++++++++ .../TestSelectionView.swift | 34 ++ 10 files changed, 686 insertions(+) create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.pbxproj create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.xcworkspace/xcuserdata/justinoros.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/xcuserdata/justinoros.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/Contents.json create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/HAM_Operator_Quiz_iPhoneApp.swift create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/QuizView.swift create mode 100644 HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/TestSelectionView.swift diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.pbxproj b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ce1de1c --- /dev/null +++ b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.pbxproj @@ -0,0 +1,329 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXFileReference section */ + 976F818A2E16F953000D4A5B /* HAM Operator Quiz iPhone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "HAM Operator Quiz iPhone.app"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 976F818C2E16F953000D4A5B /* HAM Operator Quiz iPhone */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = "HAM Operator Quiz iPhone"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 976F81872E16F953000D4A5B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 976F81812E16F953000D4A5B = { + isa = PBXGroup; + children = ( + 976F818C2E16F953000D4A5B /* HAM Operator Quiz iPhone */, + 976F818B2E16F953000D4A5B /* Products */, + ); + sourceTree = ""; + }; + 976F818B2E16F953000D4A5B /* Products */ = { + isa = PBXGroup; + children = ( + 976F818A2E16F953000D4A5B /* HAM Operator Quiz iPhone.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 976F81892E16F953000D4A5B /* HAM Operator Quiz iPhone */ = { + isa = PBXNativeTarget; + buildConfigurationList = 976F81952E16F955000D4A5B /* Build configuration list for PBXNativeTarget "HAM Operator Quiz iPhone" */; + buildPhases = ( + 976F81862E16F953000D4A5B /* Sources */, + 976F81872E16F953000D4A5B /* Frameworks */, + 976F81882E16F953000D4A5B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 976F818C2E16F953000D4A5B /* HAM Operator Quiz iPhone */, + ); + name = "HAM Operator Quiz iPhone"; + packageProductDependencies = ( + ); + productName = "HAM Operator Quiz iPhone"; + productReference = 976F818A2E16F953000D4A5B /* HAM Operator Quiz iPhone.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 976F81822E16F953000D4A5B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + 976F81892E16F953000D4A5B = { + CreatedOnToolsVersion = 16.4; + }; + }; + }; + buildConfigurationList = 976F81852E16F953000D4A5B /* Build configuration list for PBXProject "HAM Operator Quiz iPhone" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 976F81812E16F953000D4A5B; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 976F818B2E16F953000D4A5B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 976F81892E16F953000D4A5B /* HAM Operator Quiz iPhone */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 976F81882E16F953000D4A5B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 976F81862E16F953000D4A5B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 976F81932E16F955000D4A5B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 5RZLK3W7ZH; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 976F81942E16F955000D4A5B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 5RZLK3W7ZH; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 976F81962E16F955000D4A5B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5RZLK3W7ZH; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.justinoros.HAM-Operator-Quiz-iPhone"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 976F81972E16F955000D4A5B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5RZLK3W7ZH; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.justinoros.HAM-Operator-Quiz-iPhone"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 976F81852E16F953000D4A5B /* Build configuration list for PBXProject "HAM Operator Quiz iPhone" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 976F81932E16F955000D4A5B /* Debug */, + 976F81942E16F955000D4A5B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 976F81952E16F955000D4A5B /* Build configuration list for PBXNativeTarget "HAM Operator Quiz iPhone" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 976F81962E16F955000D4A5B /* Debug */, + 976F81972E16F955000D4A5B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 976F81822E16F953000D4A5B /* Project object */; +} diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.xcworkspace/xcuserdata/justinoros.xcuserdatad/UserInterfaceState.xcuserstate b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/project.xcworkspace/xcuserdata/justinoros.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..479f75179cbdd00e4bdeb3ebe84cc99ee1aec786 GIT binary patch literal 27138 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 literal 0 HcmV?d00001 diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/xcuserdata/justinoros.xcuserdatad/xcschemes/xcschememanagement.plist b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/xcuserdata/justinoros.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..4a5b4ab --- /dev/null +++ b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone.xcodeproj/xcuserdata/justinoros.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + HAM Operator Quiz iPhone.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/AccentColor.colorset/Contents.json b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/AppIcon.appiconset/Contents.json b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/Contents.json b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/HAM_Operator_Quiz_iPhoneApp.swift b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/HAM_Operator_Quiz_iPhoneApp.swift new file mode 100644 index 0000000..77521f2 --- /dev/null +++ b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/HAM_Operator_Quiz_iPhoneApp.swift @@ -0,0 +1,12 @@ +import SwiftUI + +@main +struct HamOperatorQuizApp: App { + var body: some Scene { + WindowGroup { + NavigationStack { + TestSelectionView() + } + } + } +} diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/QuizView.swift b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/QuizView.swift new file mode 100644 index 0000000..b2127c7 --- /dev/null +++ b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/QuizView.swift @@ -0,0 +1,238 @@ +import SwiftUI + +struct QuizQuestion: Codable, Identifiable { + let id: String + let question: String + let correct: Int + let answers: [String] +} + +struct QuizView: View { + let testName: String + + @State private var questions: [QuizQuestion] = [] + @State private var currentIndex = 0 + @State private var selectedAnswer: String? = nil + @State private var score = 0 + @State private var feedbackText = "" + @State private var feedbackColor = Color.primary + @State private var loadError = false + @State private var showResults = false + @State private var showSummaryAlert = false + + var body: some View { + VStack(alignment: .leading, spacing: 20) { + if loadError { + Text("Could not load questions for \(testName). Make sure the .json file is included in the app bundle or Documents folder.") + .foregroundColor(.red) + .padding() + } else if questions.isEmpty { + Text("Loading questions...") + .padding() + } else if currentIndex < questions.count { + Text(questions[currentIndex].question) + .font(.headline) + .fixedSize(horizontal: false, vertical: true) + + ForEach(questions[currentIndex].answers, id: \.self) { answer in + Button(action: { + selectedAnswer = answer + checkAnswer() + + let correctAnswer = questions[currentIndex].answers[questions[currentIndex].correct] + let delay = (answer == correctAnswer) ? 3.0 : 5.0 + + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + if selectedAnswer != nil { + nextQuestion() + } + } + }) { + Text(answer) + .padding(10) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + Capsule() + .fill(bubbleColor(for: answer)) + ) + .foregroundColor(.white) + } + .buttonStyle(PlainButtonStyle()) + } + + Text(feedbackText) + .foregroundColor(feedbackColor) + + HStack { + Button("Next") { + nextQuestion() + } + .disabled(selectedAnswer == nil) + .tint(Color.blue) + + Spacer() + + Button("Quit") { + showSummaryAlert = true + } + .tint(Color.gray) + } + .padding(.top, 10) + + } else { + VStack(spacing: 10) { + Text("Quiz Complete!") + .font(.title) + + let percentage = questions.count > 0 ? Int((Double(score) / Double(questions.count)) * 100) : 0 + Text("You got \(score) out of \(questions.count) correct. (\(percentage)%)") + } + } + } + .padding() + .onAppear(perform: syncAndLoadQuestions) + .navigationTitle(testName + " Test") + .alert(isPresented: $showSummaryAlert) { + let percentage = currentIndex > 0 ? Int((Double(score) / Double(currentIndex + 1)) * 100) : 0 + return Alert( + title: Text("Quiz Summary"), + message: Text("You answered \(score) out of \(currentIndex + 1) correctly. (\(percentage)% correct)"), + dismissButton: .default(Text("OK"), action: { + exit(0) + }) + ) + } + } + + func bubbleColor(for answer: String) -> Color { + let imessageBlue = Color(red: 0.0, green: 122/255, blue: 1.0) + let correctAnswer = questions[currentIndex].answers[questions[currentIndex].correct] + if let selected = selectedAnswer { + if selected == answer && selected == correctAnswer { + return .green + } else if selected == answer && selected != correctAnswer { + return .red + } else if answer == correctAnswer && selected != correctAnswer { + return .green + } + } + return imessageBlue + } + + func syncAndLoadQuestions() { + guard let documentsDir = getDocumentsDirectory() else { + loadError = true + return + } + + let lowerName = testName.lowercased() + let filename = "\(lowerName).json" + let urlMap = [ + "technician": "https://raw.githubusercontent.com/russolsen/ham_radio_question_pool/master/technician-2022-2026/technician.json", + "general": "https://raw.githubusercontent.com/russolsen/ham_radio_question_pool/master/general-2023-2027/general.json", + "extra": "https://raw.githubusercontent.com/russolsen/ham_radio_question_pool/master/extra-2024-2028/extra.json" + ] + + guard let remoteUrlString = urlMap[lowerName], + let remoteUrl = URL(string: remoteUrlString) else { + loadError = true + return + } + + let localFileURL = documentsDir.appendingPathComponent(filename) + + let fileExists = FileManager.default.fileExists(atPath: localFileURL.path) + + if !fileExists { + downloadAndSave(remoteUrl: remoteUrl, to: localFileURL) + } else { + URLSession.shared.dataTask(with: remoteUrl) { data, response, error in + if let data = data, + let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 { + do { + let remoteQuestions = try JSONDecoder().decode([QuizQuestion].self, from: data) + if let localData = try? Data(contentsOf: localFileURL), + let localQuestions = try? JSONDecoder().decode([QuizQuestion].self, from: localData), + localQuestions.count == remoteQuestions.count { + loadQuestions(from: localFileURL) + } else { + try data.write(to: localFileURL) + loadQuestions(from: localFileURL) + } + } catch { + print("Failed sync check: \(error)") + loadQuestions(from: localFileURL) + } + } else { + print("Remote check failed: \(error?.localizedDescription ?? "Unknown")") + loadQuestions(from: localFileURL) + } + }.resume() + } + } + + func downloadAndSave(remoteUrl: URL, to localUrl: URL) { + URLSession.shared.dataTask(with: remoteUrl) { data, response, error in + if let data = data, + let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 { + do { + try data.write(to: localUrl) + loadQuestions(from: localUrl) + } catch { + print("Failed to save downloaded JSON: \(error)") + loadError = true + } + } else { + print("Download failed: \(error?.localizedDescription ?? "Unknown")") + loadError = true + } + }.resume() + } + + func loadQuestions(from fileURL: URL) { + DispatchQueue.global(qos: .background).async { + do { + let data = try Data(contentsOf: fileURL) + let loadedQuestions = try JSONDecoder().decode([QuizQuestion].self, from: data) + + DispatchQueue.main.async { + if loadedQuestions.isEmpty { + loadError = true + } else { + questions = loadedQuestions.shuffled() + loadError = false + } + } + } catch { + print("Failed to parse questions: \(error)") + DispatchQueue.main.async { + loadError = true + } + } + } + } + + func checkAnswer() { + let correctAnswer = questions[currentIndex].answers[questions[currentIndex].correct] + if selectedAnswer == correctAnswer { + score += 1 + feedbackText = "Correct!" + feedbackColor = .green + } else { + feedbackText = "Incorrect! Correct answer: \(correctAnswer)" + feedbackColor = .red + } + } + + func nextQuestion() { + currentIndex += 1 + selectedAnswer = nil + feedbackText = "" + } + + func getDocumentsDirectory() -> URL? { + FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first + } +} diff --git a/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/TestSelectionView.swift b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/TestSelectionView.swift new file mode 100644 index 0000000..3c03205 --- /dev/null +++ b/HAM Operator Quiz iPhone/HAM Operator Quiz iPhone/TestSelectionView.swift @@ -0,0 +1,34 @@ +import SwiftUI + +struct TestSelectionView: View { + let testOptions = ["Technician Class", "General Class", "Extra Class"] + + var body: some View { + NavigationStack { + VStack(spacing: 30) { + Spacer() // Push content down for vertical centering + + ForEach(testOptions, id: \.self) { test in + NavigationLink(value: test) { + Text(test) + .frame(width: 250, height: 50) + .background(Color.blue.opacity(0.7)) + .foregroundColor(.white) + .cornerRadius(8) + } + } + + Link("Find an Exam Center", destination: URL(string: "https://www.arrl.org/find-an-amateur-radio-license-exam-session")!) + .foregroundColor(.blue) + .padding(.top, 20) + + Spacer() // Push content up for vertical centering + } + .navigationTitle("Select Test") + .navigationDestination(for: String.self) { selectedTest in + QuizView(testName: selectedTest.replacingOccurrences(of: " Class", with: "")) + } + .padding() + } + } +}