Files
suixinkan_ios_new/suixinkan/App/State/AccountContextLoader.swift
汉秋 258c438f9a 优化冷启动流程:Launch Screen 对齐 Splash,仅阻塞 rolePermissions。
移除 SplashCoordinator,bootstrap 期间用 SplashView 覆盖避免空白闪屏,其余账号数据改为后台补充刷新。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 09:56:49 +08:00

170 lines
6.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// AccountContextLoader.swift
// suixinkan
//
// Created by Codex on 2026/6/22.
//
import Foundation
@MainActor
/// 便
protocol UserProfileServing {
///
func userInfo() async throws -> UserInfoResponse
}
@MainActor
///
struct AccountContextLoader {
///
func refresh(
accountContext: AccountContext,
permissionContext: PermissionContext,
profileAPI: UserProfileServing,
accountContextAPI: AccountContextServing,
cachedCurrentRoleCode: String? = nil,
cachedLegacyRoleId: Int? = nil,
cachedCurrentScenicId: Int? = nil,
cachedCurrentStoreId: Int? = nil
) async throws {
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 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(
permissionContext: permissionContext,
scenicResponse: scenicResponse
)
let storeScopes = storesResponse?.list.map { store in
BusinessScope(id: store.id, name: store.name, kind: .store, parentScenicId: store.scenicId)
} ?? []
accountContext.replaceProfile(profile)
accountContext.replaceScopes(
scenic: scenicScopes,
stores: storeScopes,
currentScenicId: cachedCurrentScenicId ?? accountContext.currentScenic?.id,
currentStoreId: cachedCurrentStoreId ?? accountContext.currentStore?.id
)
}
///
private func loadScenicList(
accountContextAPI: AccountContextServing,
rolePermissions: [RolePermissionResponse]
) async -> ScenicListAllResponse {
do {
return try await accountContextAPI.scenicListAll()
} catch {
let fallback = uniqueScenics(from: rolePermissions)
return ScenicListAllResponse(total: fallback.count, list: fallback)
}
}
/// nil
private func loadStores(accountContextAPI: AccountContextServing) async -> ListPayload<StoreItem>? {
try? await accountContextAPI.storeAll()
}
/// 访使
private func scopedScenics(
permissionContext: PermissionContext,
scenicResponse: ScenicListAllResponse
) -> [BusinessScope] {
let roleScenics = permissionContext.currentRoleScenicScopes()
if !roleScenics.isEmpty {
return roleScenics
}
return scenicResponse.list.map {
BusinessScope(id: $0.id, name: $0.name, kind: .scenic)
}
}
///
private func uniqueScenics(from rolePermissions: [RolePermissionResponse]) -> [ScenicListItem] {
var seen = Set<Int>()
var result: [ScenicListItem] = []
for permission in rolePermissions {
for scenic in permission.scenic where seen.insert(scenic.id).inserted {
result.append(ScenicListItem(id: scenic.id, name: scenic.name))
}
}
return result
}
///
private func makeProfile(from userInfo: UserInfoResponse, fallback: AccountProfile?) -> AccountProfile {
AccountProfile(
userId: fallback?.userId ?? "",
displayName: nonEmpty(userInfo.nickname) ?? nonEmpty(userInfo.realName) ?? fallback?.displayName ?? "未设置昵称",
phone: nonEmpty(userInfo.phone) ?? fallback?.phone,
avatarURL: nonEmpty(userInfo.avatar) ?? fallback?.avatarURL
)
}
///
private func nonEmpty(_ value: String?) -> String? {
let text = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return text.isEmpty ? nil : text
}
}