Introduce real payment collection and wallet screens to replace home menu placeholders, wire APIs through RootView, and support bank card OSS uploads plus QR code saving to the photo library. Co-authored-by: Cursor <cursoragent@cursor.com>
145 lines
5.3 KiB
Swift
145 lines
5.3 KiB
Swift
//
|
||
// 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 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 authSessionCoordinator: AuthSessionCoordinator
|
||
@State private var sessionBootstrapper: SessionBootstrapper
|
||
|
||
/// 初始化网络客户端和业务 API,确保它们共享同一个 token provider。
|
||
init() {
|
||
let apiClient = APIClient()
|
||
let tokenStore = SessionTokenStore()
|
||
let snapshotStore = AccountSnapshotStore()
|
||
_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))
|
||
_authSessionCoordinator = State(
|
||
initialValue: AuthSessionCoordinator(
|
||
tokenStore: tokenStore,
|
||
snapshotStore: snapshotStore,
|
||
preferencesStore: AppPreferencesStore()
|
||
)
|
||
)
|
||
_sessionBootstrapper = State(
|
||
initialValue: SessionBootstrapper(
|
||
tokenStore: tokenStore,
|
||
snapshotStore: snapshotStore
|
||
)
|
||
)
|
||
}
|
||
|
||
var body: some View {
|
||
rootContent
|
||
.globalToastOverlay()
|
||
.environment(appSession)
|
||
.environment(accountContext)
|
||
.environment(permissionContext)
|
||
.environment(scenicSpotContext)
|
||
.environment(appRouter)
|
||
.environment(toastCenter)
|
||
.environment(apiClient)
|
||
.environment(authAPI)
|
||
.environment(profileAPI)
|
||
.environment(uploadAPI)
|
||
.environment(ossUploadService)
|
||
.environment(accountContextAPI)
|
||
.environment(ordersAPI)
|
||
.environment(statisticsAPI)
|
||
.environment(paymentAPI)
|
||
.environment(walletAPI)
|
||
.environment(authSessionCoordinator)
|
||
.task {
|
||
apiClient.bindAuthTokenProvider { appSession.token }
|
||
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:
|
||
ProgressView()
|
||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||
.background(Color(.systemBackground))
|
||
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()
|
||
}
|