Files
suixinkan_ios_new/suixinkan/App/RootView.swift
汉秋 703078352c 从 iOS 17 Observation 迁移至 iOS 16 兼容的 Combine 架构
将最低部署版本降至 iOS 16,以 ObservableObject 替换 @Observable,新增导航与 UI 兼容层,并补充登录冒烟 UI 测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 10:16:35 +08:00

232 lines
10 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.

//
// RootView.swift
// suixinkan
//
// Created by Codex on 2026/6/18.
//
import SwiftUI
/// App
struct RootView: View {
@Environment(\.scenePhase) private var scenePhase
@StateObject private var appSession = AppSession()
@StateObject private var accountContext = AccountContext()
@StateObject private var permissionContext = PermissionContext()
@StateObject private var scenicSpotContext = ScenicSpotContext()
@StateObject private var appRouter = AppRouter()
@StateObject private var toastCenter = ToastCenter()
@State private var globalLoading = GlobalLoadingCenter()
@State private var snapshotStore: AccountSnapshotStore
@State private var apiClient: APIClient
@State private var authAPI: AuthAPI
@State private var profileAPI: ProfileAPI
@State private var uploadAPI: UploadAPI
@State private var ossUploadService: OSSUploadService
@State private var accountContextAPI: AccountContextAPI
@State private var ordersAPI: OrdersAPI
@State private var statisticsAPI: StatisticsAPI
@State private var paymentAPI: PaymentAPI
@State private var walletAPI: WalletAPI
@State private var pushAPI: PushAPI
@State private var scenicPermissionAPI: ScenicPermissionAPI
@State private var scenicSettlementAPI: ScenicSettlementAPI
@State private var messageCenterAPI: MessageCenterAPI
@State private var scenicQueueAPI: ScenicQueueAPI
@StateObject private var scenicQueueRuntime = ScenicQueueRuntime()
@State private var liveAPI: LiveAPI
@State private var operatingAreaAPI: OperatingAreaAPI
@State private var pilotCertificationAPI: PilotCertificationAPI
@State private var taskAPI: TaskAPI
@State private var projectAPI: ProjectAPI
@State private var scheduleAPI: ScheduleAPI
@State private var inviteAPI: InviteAPI
@State private var assetsAPI: AssetsAPI
@State private var punchPointAPI: PunchPointAPI
@State private var locationReportAPI: LocationReportAPI
@State private var authSessionCoordinator: AuthSessionCoordinator
@State private var sessionBootstrapper: SessionBootstrapper
/// API token provider
init() {
let apiClient = APIClient()
let tokenStore = SessionTokenStore()
let snapshotStore = AccountSnapshotStore()
_snapshotStore = State(initialValue: snapshotStore)
_apiClient = State(initialValue: apiClient)
_authAPI = State(initialValue: AuthAPI(client: apiClient))
_profileAPI = State(initialValue: ProfileAPI(client: apiClient))
let uploadAPI = UploadAPI(client: apiClient)
_uploadAPI = State(initialValue: uploadAPI)
_ossUploadService = State(initialValue: OSSUploadService(configService: uploadAPI))
_accountContextAPI = State(initialValue: AccountContextAPI(client: apiClient))
_ordersAPI = State(initialValue: OrdersAPI(client: apiClient))
_statisticsAPI = State(initialValue: StatisticsAPI(client: apiClient))
_paymentAPI = State(initialValue: PaymentAPI(client: apiClient))
_walletAPI = State(initialValue: WalletAPI(client: apiClient))
_pushAPI = State(initialValue: PushAPI(client: apiClient))
_scenicPermissionAPI = State(initialValue: ScenicPermissionAPI(client: apiClient))
_scenicSettlementAPI = State(initialValue: ScenicSettlementAPI(client: apiClient))
_messageCenterAPI = State(initialValue: MessageCenterAPI(client: apiClient))
_scenicQueueAPI = State(initialValue: ScenicQueueAPI(client: apiClient))
_liveAPI = State(initialValue: LiveAPI(client: apiClient))
_operatingAreaAPI = State(initialValue: OperatingAreaAPI(client: apiClient))
_pilotCertificationAPI = State(initialValue: PilotCertificationAPI(client: apiClient))
_taskAPI = State(initialValue: TaskAPI(client: apiClient))
_projectAPI = State(initialValue: ProjectAPI(client: apiClient))
_scheduleAPI = State(initialValue: ScheduleAPI(client: apiClient))
_inviteAPI = State(initialValue: InviteAPI(client: apiClient))
_assetsAPI = State(initialValue: AssetsAPI(client: apiClient))
_punchPointAPI = State(initialValue: PunchPointAPI(client: apiClient))
_locationReportAPI = State(initialValue: LocationReportAPI(client: apiClient))
_authSessionCoordinator = State(
initialValue: AuthSessionCoordinator(
tokenStore: tokenStore,
snapshotStore: snapshotStore,
preferencesStore: AppPreferencesStore()
)
)
_sessionBootstrapper = State(
initialValue: SessionBootstrapper(
tokenStore: tokenStore,
snapshotStore: snapshotStore
)
)
}
var body: some View {
rootContent
.globalToastOverlay()
.globalLoadingOverlay(loadingCenter: globalLoading)
.environmentObject(appSession)
.environmentObject(accountContext)
.environmentObject(permissionContext)
.environmentObject(scenicSpotContext)
.environmentObject(appRouter)
.environmentObject(toastCenter)
.environment(\.globalLoading, globalLoading)
.environment(\.accountSnapshotStore, snapshotStore)
.environment(\.apiClient, apiClient)
.environment(\.authAPI, authAPI)
.environment(\.profileAPI, profileAPI)
.environment(\.uploadAPI, uploadAPI)
.environment(\.ossUploadService, ossUploadService)
.environment(\.accountContextAPI, accountContextAPI)
.environment(\.ordersAPI, ordersAPI)
.environment(\.statisticsAPI, statisticsAPI)
.environment(\.paymentAPI, paymentAPI)
.environment(\.walletAPI, walletAPI)
.environment(\.pushAPI, pushAPI)
.environment(\.scenicPermissionAPI, scenicPermissionAPI)
.environment(\.scenicSettlementAPI, scenicSettlementAPI)
.environment(\.messageCenterAPI, messageCenterAPI)
.environment(\.scenicQueueAPI, scenicQueueAPI)
.environmentObject(scenicQueueRuntime)
.environment(\.liveAPI, liveAPI)
.environment(\.operatingAreaAPI, operatingAreaAPI)
.environment(\.pilotCertificationAPI, pilotCertificationAPI)
.environment(\.taskAPI, taskAPI)
.environment(\.projectAPI, projectAPI)
.environment(\.scheduleAPI, scheduleAPI)
.environment(\.inviteAPI, inviteAPI)
.environment(\.assetsAPI, assetsAPI)
.environment(\.punchPointAPI, punchPointAPI)
.environment(\.locationReportAPI, locationReportAPI)
.environment(\.authSessionCoordinator, authSessionCoordinator)
.task {
apiClient.bindAuthTokenProvider { appSession.token }
PushNotificationManager.shared.configure(api: pushAPI, session: appSession, router: appRouter)
await globalLoading.withLoading {
await sessionBootstrapper.restore(
appSession: appSession,
accountContext: accountContext,
permissionContext: permissionContext,
profileAPI: profileAPI,
accountContextAPI: accountContextAPI,
toastCenter: toastCenter
)
}
if appSession.isLoggedIn {
PushNotificationManager.shared.requestAuthorizationAndRegister()
}
}
.task(id: scenicSpotTaskID) {
guard appSession.isLoggedIn else {
scenicSpotContext.reset()
return
}
await scenicSpotContext.reload(
scenicId: accountContext.currentScenic?.id,
api: accountContextAPI
)
}
.onChange(of: scenePhase) { newPhase in
if appSession.isLoggedIn {
scenicQueueRuntime.update(
api: scenicQueueAPI,
userId: accountContext.profile?.userId,
scenicId: accountContext.currentScenic?.id,
scenePhase: newPhase
)
} else {
scenicQueueRuntime.stop()
}
}
.onChange(of: accountContext.currentScenic?.id) { _ in
if appSession.isLoggedIn {
scenicQueueRuntime.update(
api: scenicQueueAPI,
userId: accountContext.profile?.userId,
scenicId: accountContext.currentScenic?.id,
scenePhase: scenePhase
)
}
}
.onChange(of: appSession.phase) { phase in
switch phase {
case .loggedIn:
PushNotificationManager.shared.requestAuthorizationAndRegister()
case .loggedOut:
accountContext.reset()
permissionContext.reset()
scenicSpotContext.reset()
appRouter.reset()
toastCenter.dismiss()
case .restoring:
break
}
}
}
@ViewBuilder
private var rootContent: some View {
switch appSession.phase {
case .loggedOut:
LoginView()
case .restoring:
Color(.systemBackground)
.frame(maxWidth: .infinity, maxHeight: .infinity)
case .loggedIn:
MainTabsView()
}
}
///
private var scenicSpotTaskID: String {
let phaseText: String
switch appSession.phase {
case .loggedOut:
phaseText = "loggedOut"
case .restoring:
phaseText = "restoring"
case .loggedIn:
phaseText = "loggedIn"
}
return "\(phaseText)-\(accountContext.currentScenic?.id ?? 0)"
}
}
#Preview {
RootView()
}