基于 Lottie 在关键流程中新增全局 Loading 遮罩
引入带引用计数的 GlobalLoadingCenter,通过 RootView 及主要登录/订单/个人中心页面接入,并添加 Loading 动画资源与单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -18,6 +18,7 @@ struct AccountSwitchView: View {
|
||||
@Environment(AccountContextAPI.self) private var accountContextAPI
|
||||
@Environment(AuthSessionCoordinator.self) private var authSessionCoordinator
|
||||
@Environment(ToastCenter.self) private var toastCenter
|
||||
@Environment(\.globalLoading) private var globalLoading
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var viewModel = AccountSwitchViewModel()
|
||||
@ -71,15 +72,9 @@ struct AccountSwitchView: View {
|
||||
Button {
|
||||
Task { await confirmSelection() }
|
||||
} label: {
|
||||
HStack(spacing: 8) {
|
||||
if viewModel.switching {
|
||||
ProgressView()
|
||||
.tint(.white)
|
||||
}
|
||||
Text("确认切换")
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
Text("确认切换")
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
.background(canConfirm ? AppDesign.primary : Color(hex: 0xC9CED6), in: RoundedRectangle(cornerRadius: 12))
|
||||
@ -187,7 +182,9 @@ struct AccountSwitchView: View {
|
||||
/// 拉取账号列表。
|
||||
private func loadAccounts(force: Bool = false) async {
|
||||
do {
|
||||
try await viewModel.load(api: profileAPI, force: force, currentAccountId: currentAccountId)
|
||||
try await globalLoading.withOptionalLoading(!force && viewModel.accounts.isEmpty, message: "加载中...") {
|
||||
try await viewModel.load(api: profileAPI, force: force, currentAccountId: currentAccountId)
|
||||
}
|
||||
} catch is CancellationError {
|
||||
return
|
||||
} catch {
|
||||
@ -204,18 +201,20 @@ struct AccountSwitchView: View {
|
||||
}
|
||||
|
||||
do {
|
||||
let response = try await viewModel.switchAccount(account, api: authAPI)
|
||||
let username = nonEmpty(accountContext.profile?.phone) ?? nonEmpty(account.phone) ?? ""
|
||||
try await authSessionCoordinator.completeLogin(
|
||||
with: response,
|
||||
username: username,
|
||||
privacyAgreementAccepted: authSessionCoordinator.loginPreferences().privacyAgreementAccepted,
|
||||
appSession: appSession,
|
||||
accountContext: accountContext,
|
||||
permissionContext: permissionContext,
|
||||
profileAPI: profileAPI,
|
||||
accountContextAPI: accountContextAPI
|
||||
)
|
||||
try await globalLoading.withLoading(message: "账号切换中...") {
|
||||
let response = try await viewModel.switchAccount(account, api: authAPI)
|
||||
let username = nonEmpty(accountContext.profile?.phone) ?? nonEmpty(account.phone) ?? ""
|
||||
try await authSessionCoordinator.completeLogin(
|
||||
with: response,
|
||||
username: username,
|
||||
privacyAgreementAccepted: authSessionCoordinator.loginPreferences().privacyAgreementAccepted,
|
||||
appSession: appSession,
|
||||
accountContext: accountContext,
|
||||
permissionContext: permissionContext,
|
||||
profileAPI: profileAPI,
|
||||
accountContextAPI: accountContextAPI
|
||||
)
|
||||
}
|
||||
appRouter.reset()
|
||||
toastCenter.show("账号已切换")
|
||||
dismiss()
|
||||
|
||||
@ -21,6 +21,7 @@ struct ProfileView: View {
|
||||
@Environment(OSSUploadService.self) private var ossUploadService
|
||||
@Environment(ToastCenter.self) private var toastCenter
|
||||
@Environment(AuthSessionCoordinator.self) private var authSessionCoordinator
|
||||
@Environment(\.globalLoading) private var globalLoading
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
@State private var viewModel = ProfileViewModel()
|
||||
@ -73,14 +74,6 @@ struct ProfileView: View {
|
||||
}
|
||||
Button("取消", role: .cancel) {}
|
||||
}
|
||||
.overlay {
|
||||
if viewModel.isLoading && viewModel.userInfo == nil {
|
||||
ProgressView()
|
||||
.controlSize(.large)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color.white.opacity(0.35))
|
||||
}
|
||||
}
|
||||
.onChange(of: viewModel.isEditingProfile) { _, isEditing in
|
||||
guard isEditing else { return }
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
@ -512,7 +505,9 @@ struct ProfileView: View {
|
||||
/// 重新拉取个人资料,并同步更新全局账号资料。
|
||||
private func reloadProfile(showToast: Bool) async {
|
||||
do {
|
||||
try await viewModel.reload(api: profileAPI)
|
||||
try await globalLoading.withOptionalLoading(!showToast && viewModel.userInfo == nil, message: "加载中...") {
|
||||
try await viewModel.reload(api: profileAPI)
|
||||
}
|
||||
if let userInfo = viewModel.userInfo {
|
||||
authSessionCoordinator.refreshCachedProfile(from: userInfo, accountContext: accountContext)
|
||||
} else {
|
||||
@ -531,11 +526,13 @@ struct ProfileView: View {
|
||||
/// 保存昵称编辑内容,并在成功后刷新全局账号展示。
|
||||
private func saveProfileEdits() async {
|
||||
do {
|
||||
try await viewModel.saveProfile(
|
||||
api: profileAPI,
|
||||
uploader: ossUploadService,
|
||||
scenicId: accountContext.currentScenic?.id ?? 0
|
||||
)
|
||||
try await globalLoading.withLoading(message: "保存中...") {
|
||||
try await viewModel.saveProfile(
|
||||
api: profileAPI,
|
||||
uploader: ossUploadService,
|
||||
scenicId: accountContext.currentScenic?.id ?? 0
|
||||
)
|
||||
}
|
||||
if let userInfo = viewModel.userInfo {
|
||||
authSessionCoordinator.refreshCachedProfile(from: userInfo, accountContext: accountContext)
|
||||
} else {
|
||||
|
||||
@ -15,6 +15,7 @@ struct RealNameAuthView: View {
|
||||
@Environment(ProfileAPI.self) private var profileAPI
|
||||
@Environment(OSSUploadService.self) private var ossUploadService
|
||||
@Environment(ToastCenter.self) private var toastCenter
|
||||
@Environment(\.globalLoading) private var globalLoading
|
||||
@State private var viewModel = RealNameAuthViewModel()
|
||||
@State private var pickedFrontItem: PhotosPickerItem?
|
||||
@State private var pickedBackItem: PhotosPickerItem?
|
||||
@ -37,14 +38,6 @@ struct RealNameAuthView: View {
|
||||
.task {
|
||||
await loadInfo()
|
||||
}
|
||||
.overlay {
|
||||
if viewModel.loading && viewModel.info == nil {
|
||||
ProgressView()
|
||||
.controlSize(.large)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color.white.opacity(0.35))
|
||||
}
|
||||
}
|
||||
.onChange(of: pickedFrontItem) { _, item in
|
||||
Task { await prepareIdentityImage(from: item, side: .front) }
|
||||
}
|
||||
@ -380,7 +373,9 @@ struct RealNameAuthView: View {
|
||||
/// 拉取实名认证信息。
|
||||
private func loadInfo() async {
|
||||
do {
|
||||
try await viewModel.load(api: profileAPI)
|
||||
try await globalLoading.withOptionalLoading(viewModel.info == nil, message: "加载中...") {
|
||||
try await viewModel.load(api: profileAPI)
|
||||
}
|
||||
} catch is CancellationError {
|
||||
return
|
||||
} catch {
|
||||
@ -400,11 +395,13 @@ struct RealNameAuthView: View {
|
||||
/// 提交实名资料。
|
||||
private func submit() async {
|
||||
do {
|
||||
try await viewModel.submit(
|
||||
api: profileAPI,
|
||||
uploader: ossUploadService,
|
||||
scenicId: accountContext.currentScenic?.id ?? 0
|
||||
)
|
||||
try await globalLoading.withLoading(message: "提交中...") {
|
||||
try await viewModel.submit(
|
||||
api: profileAPI,
|
||||
uploader: ossUploadService,
|
||||
scenicId: accountContext.currentScenic?.id ?? 0
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
viewModel.statusMessage = error.localizedDescription
|
||||
toastCenter.show(error.localizedDescription)
|
||||
|
||||
Reference in New Issue
Block a user