优化冷启动流程:Launch Screen 对齐 Splash,仅阻塞 rolePermissions。

移除 SplashCoordinator,bootstrap 期间用 SplashView 覆盖避免空白闪屏,其余账号数据改为后台补充刷新。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-30 09:56:49 +08:00
parent d2fe5d71e4
commit 258c438f9a
15 changed files with 545 additions and 223 deletions

View File

@ -17,7 +17,7 @@ protocol UserProfileServing {
@MainActor
///
struct AccountContextLoader {
///
///
func refresh(
accountContext: AccountContext,
permissionContext: PermissionContext,
@ -28,17 +28,66 @@ struct AccountContextLoader {
cachedCurrentScenicId: Int? = nil,
cachedCurrentStoreId: Int? = nil
) async throws {
async let userData = profileAPI.userInfo()
async let roleData = accountContextAPI.rolePermissions()
let (userInfo, rolePermissions) = try await (userData, roleData)
try await restorePermissions(
permissionContext: permissionContext,
accountContext: accountContext,
accountContextAPI: accountContextAPI,
cachedCurrentRoleCode: cachedCurrentRoleCode,
cachedLegacyRoleId: cachedLegacyRoleId,
cachedCurrentScenicId: cachedCurrentScenicId,
cachedCurrentStoreId: cachedCurrentStoreId
)
try await refreshSupplementalContext(
accountContext: accountContext,
permissionContext: permissionContext,
profileAPI: profileAPI,
accountContextAPI: accountContextAPI,
cachedCurrentScenicId: cachedCurrentScenicId,
cachedCurrentStoreId: cachedCurrentStoreId
)
}
///
func restorePermissions(
permissionContext: PermissionContext,
accountContext: AccountContext,
accountContextAPI: AccountContextServing,
cachedCurrentRoleCode: String? = nil,
cachedLegacyRoleId: Int? = nil,
cachedCurrentScenicId: Int? = nil,
cachedCurrentStoreId: Int? = nil
) async throws {
let rolePermissions = try await accountContextAPI.rolePermissions()
permissionContext.replaceRolePermissions(
rolePermissions,
currentRoleCode: cachedCurrentRoleCode,
cachedLegacyRoleId: cachedLegacyRoleId
)
let scenicResponse = await loadScenicList(accountContextAPI: accountContextAPI, rolePermissions: rolePermissions)
let roleScenicScopes = permissionContext.currentRoleScenicScopes()
guard accountContext.scenicScopes.isEmpty, !roleScenicScopes.isEmpty else { return }
accountContext.replaceScopes(
scenic: roleScenicScopes,
stores: accountContext.storeScopes,
currentScenicId: cachedCurrentScenicId,
currentStoreId: cachedCurrentStoreId
)
}
///
func refreshSupplementalContext(
accountContext: AccountContext,
permissionContext: PermissionContext,
profileAPI: UserProfileServing,
accountContextAPI: AccountContextServing,
cachedCurrentScenicId: Int? = nil,
cachedCurrentStoreId: Int? = nil
) async throws {
let userInfo = try await profileAPI.userInfo()
let scenicResponse = await loadScenicList(
accountContextAPI: accountContextAPI,
rolePermissions: permissionContext.rolePermissions
)
let storesResponse = await loadStores(accountContextAPI: accountContextAPI)
let profile = makeProfile(from: userInfo, fallback: accountContext.profile)
let scenicScopes = scopedScenics(
@ -53,8 +102,8 @@ struct AccountContextLoader {
accountContext.replaceScopes(
scenic: scenicScopes,
stores: storeScopes,
currentScenicId: cachedCurrentScenicId,
currentStoreId: cachedCurrentStoreId
currentScenicId: cachedCurrentScenicId ?? accountContext.currentScenic?.id,
currentStoreId: cachedCurrentStoreId ?? accountContext.currentStore?.id
)
}

View File

@ -32,7 +32,7 @@ final class SessionBootstrapper {
)
}
///
/// rolePermissions
func restore(
appSession: AppSession,
accountContext: AccountContext,
@ -51,32 +51,38 @@ final class SessionBootstrapper {
appSession.beginRestoring(token: token)
let cachedSnapshot = restoreCachedSnapshot(to: accountContext)
let loader = AccountContextLoader()
do {
try await AccountContextLoader().refresh(
accountContext: accountContext,
try await loader.restorePermissions(
permissionContext: permissionContext,
profileAPI: profileAPI,
accountContext: accountContext,
accountContextAPI: accountContextAPI,
cachedCurrentRoleCode: cachedSnapshot?.currentRoleCode,
cachedLegacyRoleId: cachedSnapshot?.currentRoleId,
cachedCurrentScenicId: cachedSnapshot?.currentScenicId,
cachedCurrentStoreId: cachedSnapshot?.currentStoreId
)
saveLatestSnapshot(from: accountContext, permissionContext: permissionContext)
appSession.markLoggedIn(token: token)
refreshSupplementalContextInBackground(
loader: loader,
appSession: appSession,
accountContext: accountContext,
permissionContext: permissionContext,
profileAPI: profileAPI,
accountContextAPI: accountContextAPI,
toastCenter: toastCenter,
cachedSnapshot: cachedSnapshot
)
} catch {
if APIError.isAuthenticationExpired(error) {
try? tokenStore.clear()
snapshotStore.clear()
accountContext.reset()
permissionContext.reset()
appSession.logout()
toastCenter.show("登录状态已失效,请重新登录")
} else {
appSession.markLoggedIn(token: token)
toastCenter.show("网络异常,已使用本地登录状态")
}
handleBlockingRestoreFailure(
error,
token: token,
appSession: appSession,
accountContext: accountContext,
permissionContext: permissionContext,
toastCenter: toastCenter
)
}
}
@ -93,6 +99,97 @@ final class SessionBootstrapper {
return snapshot
}
///
private func refreshSupplementalContextInBackground(
loader: AccountContextLoader,
appSession: AppSession,
accountContext: AccountContext,
permissionContext: PermissionContext,
profileAPI: UserProfileServing,
accountContextAPI: AccountContextServing,
toastCenter: ToastCenter,
cachedSnapshot: AccountSnapshot?
) {
Task {
do {
try await loader.refreshSupplementalContext(
accountContext: accountContext,
permissionContext: permissionContext,
profileAPI: profileAPI,
accountContextAPI: accountContextAPI,
cachedCurrentScenicId: cachedSnapshot?.currentScenicId,
cachedCurrentStoreId: cachedSnapshot?.currentStoreId
)
saveLatestSnapshot(from: accountContext, permissionContext: permissionContext)
} catch {
handleSupplementalRefreshFailure(
error,
appSession: appSession,
accountContext: accountContext,
permissionContext: permissionContext,
toastCenter: toastCenter
)
}
}
}
///
private func handleBlockingRestoreFailure(
_ error: Error,
token: String,
appSession: AppSession,
accountContext: AccountContext,
permissionContext: PermissionContext,
toastCenter: ToastCenter
) {
if APIError.isAuthenticationExpired(error) {
clearPersistedSession(
appSession: appSession,
accountContext: accountContext,
permissionContext: permissionContext
)
toastCenter.show("登录状态已失效,请重新登录")
return
}
appSession.markLoggedIn(token: token)
toastCenter.show("网络异常,已使用本地登录状态")
}
///
private func handleSupplementalRefreshFailure(
_ error: Error,
appSession: AppSession,
accountContext: AccountContext,
permissionContext: PermissionContext,
toastCenter: ToastCenter
) {
if APIError.isAuthenticationExpired(error) {
clearPersistedSession(
appSession: appSession,
accountContext: accountContext,
permissionContext: permissionContext
)
toastCenter.show("登录状态已失效,请重新登录")
return
}
toastCenter.show("网络异常,已使用本地登录状态")
}
/// token
private func clearPersistedSession(
appSession: AppSession,
accountContext: AccountContext,
permissionContext: PermissionContext
) {
try? tokenStore.clear()
snapshotStore.clear()
accountContext.reset()
permissionContext.reset()
appSession.logout()
}
///
private func saveLatestSnapshot(from accountContext: AccountContext, permissionContext: PermissionContext) {
let existing = snapshotStore.load()
@ -109,5 +206,4 @@ final class SessionBootstrapper {
)
)
}
}