feat(ios): polish onboarding and native sign-in flow (#15217)

## Summary
- Reworks the iOS onboarding, native sign-in, and paywall flow so
first-install and cold-start entry stay native, adaptive, and aligned
with the current login/subscription gates.
- Removes onboarding swipe paging, refines onboarding/paywall layout
behavior, and keeps AI/paywall entry behavior consistent for logged-in
and subscribed users.
- Adds the new mobile all-docs empty states with localized copy and
dialog entry points, and closes the remaining review follow-ups by
removing the onboarding plan artifact and dropping the iOS AI
subscription bypass.

## Test plan
- Built the iOS app for simulator with `xcodebuild -workspace
App.xcworkspace -scheme App -configuration Debug -sdk iphonesimulator
-destination 'generic/platform=iOS Simulator' ARCHS=arm64
ONLY_ACTIVE_ARCH=YES CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO
build` during the native onboarding/sign-in flow work.
- Let the repo pre-commit hooks run on the latest follow-up commit
(`prettier` + `eslint --fix`).
- Checked diagnostics for the edited TS/TSX files after the review
follow-up changes.
- Manually iterated on onboarding/native sign-in/paywall UI states in
simulator during implementation.

---------

Co-authored-by: DarkSky <darksky2048@gmail.com>
This commit is contained in:
keepClamDown
2026-08-21 08:12:27 +08:00
committed by GitHub
parent c57004ea2c
commit 73e2d10973
130 changed files with 6478 additions and 2268 deletions
@@ -0,0 +1,69 @@
import Foundation
public final class AuthAccessTokenCache: @unchecked Sendable {
public static let shared = AuthAccessTokenCache()
private struct Entry {
let token: String
let expiresAt: Date
}
private let lock = NSLock()
private var entries: [String: Entry] = [:]
private init() {}
public func set(_ token: String, expiresAt: Date, for endpoint: String) {
guard let key = Self.canonicalOrigin(endpoint) else { return }
lock.lock()
entries[key] = Entry(token: token, expiresAt: expiresAt)
lock.unlock()
}
public func token(for endpoint: URL, minimumValidity: TimeInterval = 0) -> String? {
token(for: endpoint, matching: nil, minimumValidity: minimumValidity)
}
public func token(
for endpoint: URL,
matching origin: URL?,
minimumValidity: TimeInterval = 0
) -> String? {
guard let key = Self.canonicalOrigin(endpoint) else { return nil }
if let origin, Self.canonicalOrigin(origin) != key { return nil }
lock.lock()
defer { lock.unlock() }
guard let entry = entries[key], entry.expiresAt.timeIntervalSinceNow > minimumValidity else {
return nil
}
return entry.token
}
public func remove(for endpoint: String) {
guard let key = Self.canonicalOrigin(endpoint) else { return }
lock.lock()
entries[key] = nil
lock.unlock()
}
private static func canonicalOrigin(_ endpoint: String) -> String? {
guard let url = URL(string: endpoint) else { return nil }
return canonicalOrigin(url)
}
private static func canonicalOrigin(_ url: URL) -> String? {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let scheme = components.scheme?.lowercased(),
let host = components.host?.lowercased()
else {
return nil
}
let defaultPort = scheme == "https" ? 443 : (scheme == "http" ? 80 : nil)
if let port = components.port, port != defaultPort {
return "\(scheme)://\(host):\(port)"
}
return "\(scheme)://\(host)"
}
}