Files
suixinkan_ios_new/suixinkan/App/RootView.swift
汉秋 abcac9bfdf 新增打卡点与位置上报模块,并接入首页路由
将打卡点管理与位置上报从占位页迁移为完整 MVVM 流程,包含前台定位支持与单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 11:08:41 +08:00

167 lines
6.5 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 {
@State private var appSession = AppSession()
@State private var accountContext = AccountContext()
@State private var permissionContext = PermissionContext()
@State private var scenicSpotContext = ScenicSpotContext()
@State private var appRouter = AppRouter()
@State 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 scenicPermissionAPI: ScenicPermissionAPI
@State private var taskAPI: TaskAPI
@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))
_scenicPermissionAPI = State(initialValue: ScenicPermissionAPI(client: apiClient))
_taskAPI = State(initialValue: TaskAPI(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)
.environment(appSession)
.environment(accountContext)
.environment(permissionContext)
.environment(scenicSpotContext)
.environment(appRouter)
.environment(toastCenter)
.environment(\.globalLoading, globalLoading)
.environment(snapshotStore)
.environment(apiClient)
.environment(authAPI)
.environment(profileAPI)
.environment(uploadAPI)
.environment(ossUploadService)
.environment(accountContextAPI)
.environment(ordersAPI)
.environment(statisticsAPI)
.environment(paymentAPI)
.environment(walletAPI)
.environment(scenicPermissionAPI)
.environment(taskAPI)
.environment(assetsAPI)
.environment(punchPointAPI)
.environment(locationReportAPI)
.environment(authSessionCoordinator)
.task {
apiClient.bindAuthTokenProvider { appSession.token }
await globalLoading.withLoading {
await sessionBootstrapper.restore(
appSession: appSession,
accountContext: accountContext,
permissionContext: permissionContext,
profileAPI: profileAPI,
accountContextAPI: accountContextAPI,
toastCenter: toastCenter
)
}
}
.task(id: scenicSpotTaskID) {
guard appSession.isLoggedIn else {
scenicSpotContext.reset()
return
}
await scenicSpotContext.reload(
scenicId: accountContext.currentScenic?.id,
api: accountContextAPI
)
}
.onChange(of: appSession.phase) { _, phase in
guard phase == .loggedOut else { return }
accountContext.reset()
permissionContext.reset()
scenicSpotContext.reset()
appRouter.reset()
toastCenter.dismiss()
}
}
@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()
}