From 8eccd4e08cadf1c7f7f9531b3bcbe4028b10b368 Mon Sep 17 00:00:00 2001 From: tkgstrator <29420801+tkgstrator@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:19:56 +0900 Subject: [PATCH] fix(ui): restore help sheet and update QuantumLeap --- .github/workflows/ios.yml | 2 +- Interceptor.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 50 +++++++++++++--- Interceptor/Views/SettingsView.swift | 18 +++++- InterceptorUITests/InterceptorUITests.swift | 59 ++++++++++++++++--- README.md | 7 ++- 6 files changed, 115 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 5759153..8eb8526 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -33,7 +33,7 @@ jobs: uses: actions/checkout@v4 with: repository: qtmleap/Mudmouth - ref: 5c97774f645023b22366db5abe86797915da2d33 + ref: 3c1468aaaea5140835982bc8e38c9d3bfc49d2a0 path: Mudmouth persist-credentials: false diff --git a/Interceptor.xcodeproj/project.pbxproj b/Interceptor.xcodeproj/project.pbxproj index 934b804..9c56804 100644 --- a/Interceptor.xcodeproj/project.pbxproj +++ b/Interceptor.xcodeproj/project.pbxproj @@ -849,7 +849,7 @@ repositoryURL = "https://github.com/qtmleap/QuantumLeap.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.0.4; + minimumVersion = 1.0.0; }; }; 10FBBF2B2E522C5E00CC57B0 /* XCRemoteSwiftPackageReference "Runestone" */ = { diff --git a/Interceptor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Interceptor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2704aa3..bcb93d0 100644 --- a/Interceptor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Interceptor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "22d5619e2fdce386caa392fc1fb4a202417795060d3e5576bcf78e0f21bb0a08", + "originHash" : "f4e5590032e044c3cfb0c8e46ff7eae47a4c6ba5d8309c80e9550ff571aab2b2", "pins" : [ { "identity" : "bettersafariview", @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cybozu/LicenseList.git", "state" : { - "revision" : "56f1b5724138e4d9f60453b555edc80e43805503", - "version" : "2.2.0" + "revision" : "ee1e897acb2c8f3061fca4434cb702e8ddb7a72c", + "version" : "2.5.0" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/qtmleap/QuantumLeap.git", "state" : { - "revision" : "30dac2509deea3d935bb70fcc3f1730a24248641", - "version" : "0.0.7" + "revision" : "3bd5905bebf514133bb4430e4451567f948807f1", + "version" : "1.0.0" } }, { @@ -73,6 +73,15 @@ "version" : "0.5.2" } }, + { + "identity" : "siren", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ArtSabintsev/Siren.git", + "state" : { + "revision" : "d43c63eff4114f9de0bdaa1f4e786029b0513362", + "version" : "7.0.3" + } + }, { "identity" : "sourcekitten", "kind" : "remoteSourceControl", @@ -118,6 +127,15 @@ "version" : "1.12.0" } }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks.git", + "state" : { + "revision" : "82440fa0a8b1c381a6d1e8e0fc7bbba53ec63204", + "version" : "1.1.1" + } + }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", @@ -127,6 +145,15 @@ "version" : "1.2.1" } }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "5fa253428866f2360c3754e88537f700ed2656b5", + "version" : "1.4.1" + } + }, { "identity" : "swift-crypto", "kind" : "remoteSourceControl", @@ -136,6 +163,15 @@ "version" : "3.15.1" } }, + { + "identity" : "swift-issue-reporting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-issue-reporting", + "state" : { + "revision" : "75b000cea2ca6d7527a57e1b9cc0bdf21a9abe9f", + "version" : "2.1.1" + } + }, { "identity" : "swift-nio", "kind" : "remoteSourceControl", @@ -186,8 +222,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/siteline/swiftui-introspect.git", "state" : { - "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", - "version" : "1.3.0" + "revision" : "a37eca1db7bebc9437ee89508e7b4433ff34adb4", + "version" : "27.0.0" } }, { diff --git a/Interceptor/Views/SettingsView.swift b/Interceptor/Views/SettingsView.swift index 969038c..c4b8227 100644 --- a/Interceptor/Views/SettingsView.swift +++ b/Interceptor/Views/SettingsView.swift @@ -18,6 +18,7 @@ struct SettingsView: View { var body: some View { Form(content: { QuantumLeap.Support() + .environment(\.isFirstLaunch, $showSetup) Section { Button("Set Up Capture") { showSetup = true } .disabled(consentVersion != CaptureAuthorization.version) @@ -33,13 +34,17 @@ struct SettingsView: View { Link("Privacy Policy", destination: URL(string: "https://qleap.jp/term/interceptor_privacy_policy")!) Link("Developers", destination: URL(string: "https://qleap.jp")!) NavigationLink("Licenses") { - LicenseListView().licenseViewStyle(.withRepositoryAnchorLink) + LicenseListView().licenseViewStyle(InlineRepositoryLicenseViewStyle()) .navigationTitle("Licenses") + .navigationBarTitleDisplayMode(.inline) } } QuantumLeap.Version() }) - .fullScreenCover(isPresented: $showSetup) { FirstLaunchView() } + .sheet(isPresented: $showSetup) { + FirstLaunchView() + .presentationDetents([.large]) + } .navigationTitle(Text("TITLE_SETTINGS")) .navigationBarTitleDisplayMode(.inline) } @@ -50,3 +55,12 @@ struct SettingsView: View { .environmentIsFirstLaunch() .environmentObject(Tuberose.default) } + +private struct InlineRepositoryLicenseViewStyle: LicenseViewStyle { + @MainActor + func makeBody(configuration: Configuration) -> some View { + WithRepositoryAnchorLinkLicenseViewStyle() + .makeBody(configuration: configuration) + .navigationBarTitleDisplayMode(.inline) + } +} diff --git a/InterceptorUITests/InterceptorUITests.swift b/InterceptorUITests/InterceptorUITests.swift index 0fd5c6c..4d9b279 100644 --- a/InterceptorUITests/InterceptorUITests.swift +++ b/InterceptorUITests/InterceptorUITests.swift @@ -31,6 +31,30 @@ final class InterceptorUITests: XCTestCase { // Use XCTAssert and related functions to verify your tests produce the correct results. } + @MainActor + func testHowToUseCanCloseAndReopen() throws { + let app = XCUIApplication() + app.launchArguments = ["-AppleLanguages", "(en)", "-AppleLocale", "en_US"] + app.launch() + agreeIfNeeded(app) + tab(app, named: "Settings").tap() + for _ in 0..<2 { + settingsButton(app, named: "How to Use").tap() + XCTAssertTrue(app.buttons["Next"].waitForExistence(timeout: 5)) + XCTAssertTrue(app.buttons["Close"].isHittable) + attachScreenshot(app, named: "How to Use sheet") + app.buttons["Close"].tap() + XCTAssertTrue(app.buttons["Next"].waitForNonExistence(timeout: 5)) + XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 5)) + XCTAssertFalse(app.buttons["Agree"].exists) + } + settingsButton(app, named: "Set Up Capture").tap() + XCTAssertTrue(app.buttons["Next"].waitForExistence(timeout: 5)) + app.buttons["Close"].tap() + XCTAssertTrue(app.buttons["Next"].waitForNonExistence(timeout: 5)) + XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 5)) + } + @MainActor func testCaptureConsentLifecycle() throws { let app = XCUIApplication() @@ -348,15 +372,13 @@ final class InterceptorUITests: XCTestCase { XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 5)) let autoConnect = app.switches["Auto Connect"] XCTAssertTrue(autoConnect.waitForExistence(timeout: 5)) - let initialValue = try XCTUnwrap(autoConnect.value as? String) - // SwiftUI exposes the labeled row as the switch's accessibility frame. - // Tap the trailing control rather than the center of the row's label. - let control = autoConnect.coordinate(withNormalizedOffset: CGVector(dx: 0.95, dy: 0.5)) + let control = try switchControl(app, row: autoConnect) + let initialValue = try XCTUnwrap(control.value as? String) control.tap() - let changed = XCTNSPredicateExpectation(predicate: NSPredicate(format: "value != %@", initialValue), object: autoConnect) + let changed = XCTNSPredicateExpectation(predicate: NSPredicate(format: "value != %@", initialValue), object: control) XCTAssertEqual(XCTWaiter.wait(for: [changed], timeout: 5), .completed) control.tap() - let restored = XCTNSPredicateExpectation(predicate: NSPredicate(format: "value == %@", initialValue), object: autoConnect) + let restored = XCTNSPredicateExpectation(predicate: NSPredicate(format: "value == %@", initialValue), object: control) XCTAssertEqual(XCTWaiter.wait(for: [restored], timeout: 5), .completed) attachScreenshot(app, named: "Settings") @@ -365,16 +387,26 @@ final class InterceptorUITests: XCTestCase { XCTAssertTrue(app.staticTexts["api.lp1.av5ja.srv.nintendo.net"].exists) XCTAssertTrue(app.staticTexts["app.splatoon2.nintendo.net"].exists) attachScreenshot(app, named: "Proxy Hosts") - app.navigationBars.buttons.firstMatch.tap() + returnFromSettingsDetail(app) settingsButton(app, named: "Token List").tap() XCTAssertTrue(app.navigationBars["Token List"].waitForExistence(timeout: 5)) attachScreenshot(app, named: "Token List") - app.navigationBars.buttons.firstMatch.tap() + returnFromSettingsDetail(app) settingsButton(app, named: "Certificate").tap() XCTAssertTrue(app.navigationBars["Certificate"].waitForExistence(timeout: 5)) - app.navigationBars.buttons.firstMatch.tap() + returnFromSettingsDetail(app) + settingsButton(app, named: "Licenses").tap() + XCTAssertTrue(app.navigationBars["Licenses"].waitForExistence(timeout: 5)) + attachScreenshot(app, named: "Licenses detail title") + let license = app.staticTexts["BetterSafariView"].firstMatch + XCTAssertTrue(license.waitForExistence(timeout: 5)) + license.tap() + XCTAssertTrue(app.navigationBars["BetterSafariView"].waitForExistence(timeout: 5)) + attachScreenshot(app, named: "Individual license inline title") + app.navigationBars.buttons["Licenses"].firstMatch.tap() + returnFromSettingsDetail(app) tab(app, named: "Home").tap() app.navigationBars.buttons.firstMatch.tap() @@ -388,6 +420,15 @@ final class InterceptorUITests: XCTestCase { XCTAssertFalse(app.buttons["Next"].exists, "Completed onboarding must remain dismissed after relaunch") } + @MainActor + private func returnFromSettingsDetail(_ app: XCUIApplication) { + // iPad replaces the secondary root while keeping Settings visible in the sidebar. + // iPhone pushes the destination and exposes Settings as its back button. + let back = app.navigationBars.buttons["Settings"].firstMatch + if back.exists { back.tap() } + XCTAssertTrue(app.navigationBars["Settings"].waitForExistence(timeout: 5)) + } + @MainActor private func switchControl(_ app: XCUIApplication, row: XCUIElement) throws -> XCUIElement { // iPadOS 18 exposes the UISwitch beside its labeled row; newer versions nest it. diff --git a/README.md b/README.md index 5f09106..6f0e680 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This is an iOS application that uses a self-signed certificate to obtain an acce ### Requirements - iOS 17 or later -- Xcode with Swift 6.1 or later (required by Mudmouth) +- Xcode 27 with Swift 6.4 or later (required by QuantumLeap 1.0.0) - fastlane The simulator build was verified with Xcode 27.0 on 2026-10-04 using @@ -30,13 +30,14 @@ From the Interceptor directory, clone Mudmouth if it does not already exist: ```bash git clone https://github.com/qtmleap/Mudmouth.git ../Mudmouth -git -C ../Mudmouth checkout 698d5bcb25245a26a3be925c34ded8a4c8a2ff6a +git -C ../Mudmouth checkout 3c1468aaaea5140835982bc8e38c9d3bfc49d2a0 xcodebuild -resolvePackageDependencies -project Interceptor.xcodeproj -scheme Interceptor open Interceptor.xcodeproj ``` The revision above is the Mudmouth revision selected for the local setup on -2026-10-04. Keep the checkout on its development branch when editing Mudmouth; +2026-10-06 and matches CI. It removes the unused Introspect 1.x dependency so +QuantumLeap 1.0.0 can resolve Introspect 27.x. Keep the checkout on its development branch when editing Mudmouth; record any dependency revision changes alongside the application changes. To check simulator compilation without signing: