98b7600797
对标安卓正式版、对接 app-server(8770)的 SwiftUI 客户端。iOS 无障碍受限、无比价核心,做自成一体:美团 CPS 导购 + 福利(签到/任务/金币)+ 查看(比价记录/省钱战绩)。 功能完整 M0-M5:脚手架、短信登录(JWT+Keychain+401 自动刷新)、首页券 feed 导购、比价记录+省钱战绩、福利、我的(资料/反馈/设置/注销)。 单 target、0 三方依赖、XcodeGen 生成工程;Debug/Release 双 plist 分流 ATS。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
1.7 KiB
Swift
57 lines
1.7 KiB
Swift
import SwiftUI
|
|
|
|
/// 登录态真相源(UI 订阅)。token 实际存 Keychain,这里持内存镜像 + user 资料。
|
|
@MainActor
|
|
@Observable
|
|
final class SessionStore {
|
|
var user: UserDTO?
|
|
private(set) var isLoggedIn: Bool
|
|
@ObservationIgnored private var authExpireObserver: NSObjectProtocol?
|
|
|
|
init() {
|
|
// 启动即判断:Keychain 有 access token 视为已登录(资料由 refreshProfile 拉)
|
|
isLoggedIn = KeychainStore.get(.accessToken) != nil
|
|
authExpireObserver = NotificationCenter.default.addObserver(
|
|
forName: .authDidExpire, object: nil, queue: .main
|
|
) { [weak self] _ in
|
|
MainActor.assumeIsolated { self?.clearLocal() }
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
if let authExpireObserver {
|
|
NotificationCenter.default.removeObserver(authExpireObserver)
|
|
}
|
|
}
|
|
|
|
/// 登录成功:存 token + user。
|
|
func loginSucceeded(_ resp: TokenWithUserDTO) async {
|
|
await AuthTokens.shared.setTokens(access: resp.accessToken, refresh: resp.refreshToken)
|
|
user = resp.user
|
|
isLoggedIn = true
|
|
}
|
|
|
|
/// 资料更新后(改昵称/头像)同步内存镜像。
|
|
func updateUser(_ user: UserDTO) {
|
|
self.user = user
|
|
}
|
|
|
|
/// 拉最新资料(进入「我的」时刷新)。
|
|
func refreshProfile() async {
|
|
guard isLoggedIn else { return }
|
|
if let u = try? await AuthService().me() { user = u }
|
|
}
|
|
|
|
/// 主动退出登录。
|
|
func logout() async {
|
|
_ = try? await AuthService().logout() // 占位接口,失败也无所谓
|
|
await AuthTokens.shared.clear()
|
|
clearLocal()
|
|
}
|
|
|
|
private func clearLocal() {
|
|
user = nil
|
|
isLoggedIn = false
|
|
}
|
|
}
|