diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/project.pbxproj b/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/project.pbxproj deleted file mode 100644 index d5a966f..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/project.pbxproj +++ /dev/null @@ -1,326 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXFileReference section */ - 976BF73F2E15D180008D9B42 /* HAM Operator Quiz.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "HAM Operator Quiz.app"; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 976BF7412E15D180008D9B42 /* HAM Operator Quiz */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = "HAM Operator Quiz"; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - 976BF73C2E15D180008D9B42 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 976BF7362E15D180008D9B42 = { - isa = PBXGroup; - children = ( - 976BF7412E15D180008D9B42 /* HAM Operator Quiz */, - 976BF7402E15D180008D9B42 /* Products */, - ); - sourceTree = ""; - }; - 976BF7402E15D180008D9B42 /* Products */ = { - isa = PBXGroup; - children = ( - 976BF73F2E15D180008D9B42 /* HAM Operator Quiz.app */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 976BF73E2E15D180008D9B42 /* HAM Operator Quiz */ = { - isa = PBXNativeTarget; - buildConfigurationList = 976BF74B2E15D182008D9B42 /* Build configuration list for PBXNativeTarget "HAM Operator Quiz" */; - buildPhases = ( - 976BF73B2E15D180008D9B42 /* Sources */, - 976BF73C2E15D180008D9B42 /* Frameworks */, - 976BF73D2E15D180008D9B42 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - 976BF7412E15D180008D9B42 /* HAM Operator Quiz */, - ); - name = "HAM Operator Quiz"; - packageProductDependencies = ( - ); - productName = "HAM Operator Quiz"; - productReference = 976BF73F2E15D180008D9B42 /* HAM Operator Quiz.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 976BF7372E15D180008D9B42 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1640; - LastUpgradeCheck = 1640; - TargetAttributes = { - 976BF73E2E15D180008D9B42 = { - CreatedOnToolsVersion = 16.4; - }; - }; - }; - buildConfigurationList = 976BF73A2E15D180008D9B42 /* Build configuration list for PBXProject "HAM Operator Quiz" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 976BF7362E15D180008D9B42; - minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; - productRefGroup = 976BF7402E15D180008D9B42 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 976BF73E2E15D180008D9B42 /* HAM Operator Quiz */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 976BF73D2E15D180008D9B42 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 976BF73B2E15D180008D9B42 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 976BF7492E15D182008D9B42 /* 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; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 15.5; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 976BF74A2E15D182008D9B42 /* 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; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MACOSX_DEPLOYMENT_TARGET = 15.5; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - }; - name = Release; - }; - 976BF74C2E15D182008D9B42 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = "HAM Operator Quiz/HAM_Operator_Quiz.entitlements"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 5RZLK3W7ZH; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.justinoros.HAM-Operator-Quiz"; - PRODUCT_NAME = "$(TARGET_NAME)"; - REGISTER_APP_GROUPS = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 976BF74D2E15D182008D9B42 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = "HAM Operator Quiz/HAM_Operator_Quiz.entitlements"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 5RZLK3W7ZH; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.justinoros.HAM-Operator-Quiz"; - PRODUCT_NAME = "$(TARGET_NAME)"; - REGISTER_APP_GROUPS = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 976BF73A2E15D180008D9B42 /* Build configuration list for PBXProject "HAM Operator Quiz" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 976BF7492E15D182008D9B42 /* Debug */, - 976BF74A2E15D182008D9B42 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 976BF74B2E15D182008D9B42 /* Build configuration list for PBXNativeTarget "HAM Operator Quiz" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 976BF74C2E15D182008D9B42 /* Debug */, - 976BF74D2E15D182008D9B42 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 976BF7372E15D180008D9B42 /* Project object */; -} diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/project.xcworkspace/xcuserdata/justinoros.xcuserdatad/UserInterfaceState.xcuserstate b/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/project.xcworkspace/xcuserdata/justinoros.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index fbbfa93..0000000 Binary files a/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/project.xcworkspace/xcuserdata/justinoros.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/xcuserdata/justinoros.xcuserdatad/xcschemes/xcschememanagement.plist b/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/xcuserdata/justinoros.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 1e98778..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz.xcodeproj/xcuserdata/justinoros.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - HAM Operator Quiz.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz/Assets.xcassets/AccentColor.colorset/Contents.json b/Speedo/HAM Operator Quiz/HAM Operator Quiz/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz/Assets.xcassets/AppIcon.appiconset/Contents.json b/Speedo/HAM Operator Quiz/HAM Operator Quiz/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 3f00db4..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "images" : [ - { - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz/Assets.xcassets/Contents.json b/Speedo/HAM Operator Quiz/HAM Operator Quiz/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz/FileSync.swift b/Speedo/HAM Operator Quiz/HAM Operator Quiz/FileSync.swift deleted file mode 100644 index df943e7..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz/FileSync.swift +++ /dev/null @@ -1,47 +0,0 @@ -import Foundation - -struct FileSync { - static let baseURL = "https://raw.githubusercontent.com/russolsen/ham_radio_question_pool/master" - static let testPaths = [ - "technician": "technician-2022-2026/technician.json", - "general": "general-2023-2027/general.json", - "extra": "extra-2024-2028/extra.json" - ] - - static func urlForTest(_ test: String) -> URL? { - guard let path = testPaths[test.lowercased()] else { return nil } - return URL(string: "\(baseURL)/\(path)") - } - - static func localFileURL(test: String) -> URL? { - FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("\(test).json") - } - - static func downloadAndSave(test: String, completion: @escaping (Result) -> Void) { - guard - let url = urlForTest(test), - let localURL = localFileURL(test: test) - else { - completion(.failure(NSError(domain: "Invalid URL or file path", code: 0))) - return - } - - let task = URLSession.shared.dataTask(with: url) { data, response, error in - if let error = error { - completion(.failure(error)) - return - } - guard let data = data else { - completion(.failure(NSError(domain: "No data", code: 0))) - return - } - do { - try data.write(to: localURL) - completion(.success(())) - } catch { - completion(.failure(error)) - } - } - task.resume() - } -} diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz/HAM_Operator_Quiz.entitlements b/Speedo/HAM Operator Quiz/HAM Operator Quiz/HAM_Operator_Quiz.entitlements deleted file mode 100644 index 625af03..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz/HAM_Operator_Quiz.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - com.apple.security.network.client - - - diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz/HamOperatorQuizApp.swift b/Speedo/HAM Operator Quiz/HAM Operator Quiz/HamOperatorQuizApp.swift deleted file mode 100644 index 52b09bc..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz/HamOperatorQuizApp.swift +++ /dev/null @@ -1,13 +0,0 @@ -import SwiftUI - -@main -struct HamOperatorQuizApp: App { - var body: some Scene { - WindowGroup { - NavigationStack { - TestSelectionView() - } - .frame(minWidth: 300, minHeight: 200) - } - } -} diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz/QuizView.swift b/Speedo/HAM Operator Quiz/HAM Operator Quiz/QuizView.swift deleted file mode 100644 index c0f1977..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz/QuizView.swift +++ /dev/null @@ -1,252 +0,0 @@ -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 autoAdvanceWorkItem: DispatchWorkItem? = nil - - var body: some View { - VStack(alignment: .leading, spacing: 20) { - if loadError { - Text("Could not load questions for \(testName).") - .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() - scheduleAutoNext() - }) { - Text(answer) - .padding(10) - .frame(maxWidth: .infinity, alignment: .leading) - .background( - Capsule() - .fill(bubbleColor(for: answer)) - ) - .foregroundColor(.white) - } - .buttonStyle(PlainButtonStyle()) - .disabled(selectedAnswer != nil) // Disable after selection - } - - Text(feedbackText) - .foregroundColor(feedbackColor) - - HStack { - Button("Next") { - cancelAutoNext() - nextQuestion() - } - .disabled(selectedAnswer == nil) - - Button("Quit") { - cancelAutoNext() - showFinalResultsAndExit() - } - } - } 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)%)") - - Button("Quit") { - NSApp.terminate(nil) - } - } - } - } - .padding() - .frame(minWidth: 600, minHeight: 400) - .onAppear(perform: syncAndLoadQuestions) - .navigationTitle(testName + " Exam") - } - - 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 { - // Selected wrong answer -> red bubble - return .red - } else if answer == correctAnswer && selected != correctAnswer { - // Highlight correct answer bubble green if selected answer is wrong - return .green - } - } - return imessageBlue - } - - func showFinalResultsAndExit() { - let percentage = currentIndex > 0 ? Int((Double(score) / Double(currentIndex + 1)) * 100) : 0 - feedbackText = "You answered \(score) out of \(currentIndex + 1) correctly. (\(percentage)% correct)" - feedbackColor = .primary - DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { - NSApp.terminate(nil) - } - } - - func scheduleAutoNext() { - cancelAutoNext() // Cancel any existing auto advance - autoAdvanceWorkItem = DispatchWorkItem { - if selectedAnswer != nil && currentIndex < questions.count - 1 { - nextQuestion() - } - } - if let workItem = autoAdvanceWorkItem { - DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: workItem) - } - } - - func cancelAutoNext() { - autoAdvanceWorkItem?.cancel() - autoAdvanceWorkItem = nil - } - - 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() { - cancelAutoNext() - currentIndex += 1 - selectedAnswer = nil - feedbackText = "" - } - - func getDocumentsDirectory() -> URL? { - FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first - } -} diff --git a/Speedo/HAM Operator Quiz/HAM Operator Quiz/TestSelectionView.swift b/Speedo/HAM Operator Quiz/HAM Operator Quiz/TestSelectionView.swift deleted file mode 100644 index a6af407..0000000 --- a/Speedo/HAM Operator Quiz/HAM Operator Quiz/TestSelectionView.swift +++ /dev/null @@ -1,25 +0,0 @@ -import SwiftUI - -struct TestSelectionView: View { - let testOptions = ["Technician Class", "General Class", "Extra Class"] - - var body: some View { - VStack { - List(testOptions, id: \.self) { test in - NavigationLink(value: test) { - Text(test) - } - } - .navigationTitle("Select Test") - .navigationDestination(for: String.self) { selectedTest in - QuizView(testName: selectedTest.replacingOccurrences(of: " Class", with: "")) - } - .frame(minWidth: 400, minHeight: 300) - - Button("Quit") { - NSApp.terminate(nil) - } - .padding(.bottom, 10) - } - } -}