Migrate from iOS 17 Observation to iOS 16-compatible Combine architecture.
Lower deployment target to iOS 16, replace @Observable with ObservableObject, add navigation and UI compatibility shims, and include login smoke UI tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Observation
|
||||
import Combine
|
||||
|
||||
/// 钱包模块服务协议,定义钱包、提现、银行卡和积分接口能力。
|
||||
@MainActor
|
||||
@ -55,10 +55,9 @@ protocol WalletServing {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
/// 钱包 API,封装个人钱包、提现、银行卡和积分兑换网络请求。
|
||||
final class WalletAPI {
|
||||
@ObservationIgnored private let client: APIClient
|
||||
private let client: APIClient
|
||||
|
||||
/// 初始化钱包 API,并注入共享网络客户端。
|
||||
init(client: APIClient) {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Observation
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
/// 钱包实名认证服务协议,抽象实名认证状态读取能力以便测试替换。
|
||||
@ -34,25 +34,24 @@ enum WalletLedgerTab: String, CaseIterable, Identifiable {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
/// 钱包首页 ViewModel,管理钱包汇总、收益明细、提现记录和提现资格流。
|
||||
final class WalletViewModel {
|
||||
var summary: WalletSummaryResponse?
|
||||
var pointsOverview: PointOverviewResponse?
|
||||
var selectedTab: WalletLedgerTab = .earnings
|
||||
var selectedFilter: WalletDateFilter = .last30
|
||||
var earningsGroups: [WalletEarningDetailGroup] = []
|
||||
var withdrawRecords: [WalletWithdrawRecord] = []
|
||||
var isLoadingSummary = false
|
||||
var isLoadingList = false
|
||||
var isCheckingWithdraw = false
|
||||
var errorMessage: String?
|
||||
final class WalletViewModel: ObservableObject {
|
||||
@Published var summary: WalletSummaryResponse?
|
||||
@Published var pointsOverview: PointOverviewResponse?
|
||||
@Published var selectedTab: WalletLedgerTab = .earnings
|
||||
@Published var selectedFilter: WalletDateFilter = .last30
|
||||
@Published var earningsGroups: [WalletEarningDetailGroup] = []
|
||||
@Published var withdrawRecords: [WalletWithdrawRecord] = []
|
||||
@Published var isLoadingSummary = false
|
||||
@Published var isLoadingList = false
|
||||
@Published var isCheckingWithdraw = false
|
||||
@Published var errorMessage: String?
|
||||
|
||||
private let pageSize = 20
|
||||
private var earningsPage = 1
|
||||
private var earningsTotal = 0
|
||||
private var withdrawPage = 1
|
||||
private var withdrawTotal = 0
|
||||
@Published private var earningsPage = 1
|
||||
@Published private var earningsTotal = 0
|
||||
@Published private var withdrawPage = 1
|
||||
@Published private var withdrawTotal = 0
|
||||
|
||||
/// 钱包可提现金额展示文本。
|
||||
var withdrawableText: String {
|
||||
@ -225,16 +224,15 @@ final class WalletViewModel {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
/// 提现申请 ViewModel,管理提现信息、金额校验、短信验证码和提交。
|
||||
final class WithdrawApplyViewModel {
|
||||
var info: WithdrawInfoResponse?
|
||||
var amountText = ""
|
||||
var smsCode = ""
|
||||
var isLoading = false
|
||||
var isSubmitting = false
|
||||
var smsCountdown = 0
|
||||
var errorMessage: String?
|
||||
final class WithdrawApplyViewModel: ObservableObject {
|
||||
@Published var info: WithdrawInfoResponse?
|
||||
@Published var amountText = ""
|
||||
@Published var smsCode = ""
|
||||
@Published var isLoading = false
|
||||
@Published var isSubmitting = false
|
||||
@Published var smsCountdown = 0
|
||||
@Published var errorMessage: String?
|
||||
|
||||
/// 加载提现申请所需信息。
|
||||
func load(api: WalletServing) async {
|
||||
@ -294,28 +292,27 @@ final class WithdrawApplyViewModel {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
/// 银行卡设置 ViewModel,管理银行卡表单、图片上传、短信验证码和提交。
|
||||
final class WithdrawalSettingsViewModel {
|
||||
var bankCard: WalletBankCardInfo?
|
||||
var banks: [String] = []
|
||||
var areas: [AreaNode] = []
|
||||
var realName = ""
|
||||
var cardNumber = ""
|
||||
var bankName = ""
|
||||
var branchName = ""
|
||||
var provinceCode = ""
|
||||
var cityCode = ""
|
||||
var smsCode = ""
|
||||
var frontImageData: Data?
|
||||
var backImageData: Data?
|
||||
var frontImageURL = ""
|
||||
var backImageURL = ""
|
||||
var uploadProgress = 0
|
||||
var isLoading = false
|
||||
var isSubmitting = false
|
||||
var smsCountdown = 0
|
||||
var errorMessage: String?
|
||||
final class WithdrawalSettingsViewModel: ObservableObject {
|
||||
@Published var bankCard: WalletBankCardInfo?
|
||||
@Published var banks: [String] = []
|
||||
@Published var areas: [AreaNode] = []
|
||||
@Published var realName = ""
|
||||
@Published var cardNumber = ""
|
||||
@Published var bankName = ""
|
||||
@Published var branchName = ""
|
||||
@Published var provinceCode = ""
|
||||
@Published var cityCode = ""
|
||||
@Published var smsCode = ""
|
||||
@Published var frontImageData: Data?
|
||||
@Published var backImageData: Data?
|
||||
@Published var frontImageURL = ""
|
||||
@Published var backImageURL = ""
|
||||
@Published var uploadProgress = 0
|
||||
@Published var isLoading = false
|
||||
@Published var isSubmitting = false
|
||||
@Published var smsCountdown = 0
|
||||
@Published var errorMessage: String?
|
||||
|
||||
/// 加载银行卡设置需要的银行、地区和已提交资料。
|
||||
func load(api: WalletServing) async {
|
||||
@ -440,19 +437,18 @@ final class WithdrawalSettingsViewModel {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
/// 积分兑换 ViewModel,管理积分概览、兑换记录、兑换金额校验和提交。
|
||||
final class PointsRedemptionViewModel {
|
||||
var overview = PointOverviewResponse()
|
||||
var records: [PointWithdrawItem] = []
|
||||
var pointsText = ""
|
||||
var isLoading = false
|
||||
var isSubmitting = false
|
||||
var errorMessage: String?
|
||||
final class PointsRedemptionViewModel: ObservableObject {
|
||||
@Published var overview = PointOverviewResponse()
|
||||
@Published var records: [PointWithdrawItem] = []
|
||||
@Published var pointsText = ""
|
||||
@Published var isLoading = false
|
||||
@Published var isSubmitting = false
|
||||
@Published var errorMessage: String?
|
||||
|
||||
private let pageSize = 20
|
||||
private var page = 1
|
||||
private var total = 0
|
||||
@Published private var page = 1
|
||||
@Published private var total = 0
|
||||
|
||||
/// 是否还能继续加载兑换记录。
|
||||
var canLoadMore: Bool {
|
||||
|
||||
@ -11,12 +11,12 @@ import SwiftUI
|
||||
|
||||
/// 钱包首页,展示钱包汇总、收益明细、提现记录和钱包二级入口。
|
||||
struct WalletView: View {
|
||||
@Environment(AccountContext.self) private var accountContext
|
||||
@Environment(WalletAPI.self) private var walletAPI
|
||||
@Environment(ProfileAPI.self) private var profileAPI
|
||||
@Environment(ToastCenter.self) private var toastCenter
|
||||
@EnvironmentObject private var accountContext: AccountContext
|
||||
@Environment(\.walletAPI) private var walletAPI
|
||||
@Environment(\.profileAPI) private var profileAPI
|
||||
@EnvironmentObject private var toastCenter: ToastCenter
|
||||
|
||||
@State private var viewModel = WalletViewModel()
|
||||
@StateObject private var viewModel = WalletViewModel()
|
||||
@State private var route: WalletRoute?
|
||||
|
||||
var body: some View {
|
||||
@ -33,7 +33,7 @@ struct WalletView: View {
|
||||
.background(Color(hex: 0xF5F7FA))
|
||||
.navigationTitle("我的钱包")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationDestination(item: $route) { route in
|
||||
.appNavigationDestination(item: $route) { route in
|
||||
switch route {
|
||||
case .withdrawApply:
|
||||
WithdrawApplyView()
|
||||
@ -51,7 +51,7 @@ struct WalletView: View {
|
||||
.refreshable {
|
||||
await viewModel.loadInitial(api: walletAPI, staffId: staffId)
|
||||
}
|
||||
.onChange(of: viewModel.errorMessage) { _, message in
|
||||
.onChange(of: viewModel.errorMessage) { message in
|
||||
if let message {
|
||||
toastCenter.show(message)
|
||||
}
|
||||
@ -256,10 +256,10 @@ struct WalletView: View {
|
||||
|
||||
/// 提现申请页,输入金额和短信验证码后提交提现。
|
||||
struct WithdrawApplyView: View {
|
||||
@Environment(WalletAPI.self) private var walletAPI
|
||||
@Environment(ToastCenter.self) private var toastCenter
|
||||
@Environment(\.walletAPI) private var walletAPI
|
||||
@EnvironmentObject private var toastCenter: ToastCenter
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var viewModel = WithdrawApplyViewModel()
|
||||
@StateObject private var viewModel = WithdrawApplyViewModel()
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
@ -301,7 +301,7 @@ struct WithdrawApplyView: View {
|
||||
viewModel.smsCountdown -= 1
|
||||
}
|
||||
}
|
||||
.onChange(of: viewModel.errorMessage) { _, message in
|
||||
.onChange(of: viewModel.errorMessage) { message in
|
||||
if let message {
|
||||
toastCenter.show(message)
|
||||
}
|
||||
@ -311,13 +311,13 @@ struct WithdrawApplyView: View {
|
||||
|
||||
/// 银行卡设置页,使用图片选择和 OSS 上传提交银行卡资料。
|
||||
struct WithdrawalSettingsView: View {
|
||||
@Environment(AccountContext.self) private var accountContext
|
||||
@Environment(WalletAPI.self) private var walletAPI
|
||||
@Environment(OSSUploadService.self) private var ossUploadService
|
||||
@Environment(ToastCenter.self) private var toastCenter
|
||||
@EnvironmentObject private var accountContext: AccountContext
|
||||
@Environment(\.walletAPI) private var walletAPI
|
||||
@Environment(\.ossUploadService) private var ossUploadService
|
||||
@EnvironmentObject private var toastCenter: ToastCenter
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var viewModel = WithdrawalSettingsViewModel()
|
||||
@StateObject private var viewModel = WithdrawalSettingsViewModel()
|
||||
@State private var frontItem: PhotosPickerItem?
|
||||
@State private var backItem: PhotosPickerItem?
|
||||
|
||||
@ -389,10 +389,10 @@ struct WithdrawalSettingsView: View {
|
||||
.navigationTitle("银行卡设置")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.task { await viewModel.load(api: walletAPI) }
|
||||
.onChange(of: frontItem) { _, item in
|
||||
.onChange(of: frontItem) { item in
|
||||
Task { viewModel.frontImageData = try? await item?.loadTransferable(type: Data.self) }
|
||||
}
|
||||
.onChange(of: backItem) { _, item in
|
||||
.onChange(of: backItem) { item in
|
||||
Task { viewModel.backImageData = try? await item?.loadTransferable(type: Data.self) }
|
||||
}
|
||||
.onReceive(Timer.publish(every: 1, on: .main, in: .common).autoconnect()) { _ in
|
||||
@ -400,7 +400,7 @@ struct WithdrawalSettingsView: View {
|
||||
viewModel.smsCountdown -= 1
|
||||
}
|
||||
}
|
||||
.onChange(of: viewModel.errorMessage) { _, message in
|
||||
.onChange(of: viewModel.errorMessage) { message in
|
||||
if let message {
|
||||
toastCenter.show(message)
|
||||
}
|
||||
@ -449,10 +449,10 @@ struct WithdrawalSettingsView: View {
|
||||
|
||||
/// 积分兑换页,展示积分概览、提交兑换申请和兑换记录。
|
||||
struct PointsRedemptionView: View {
|
||||
@Environment(AccountContext.self) private var accountContext
|
||||
@Environment(WalletAPI.self) private var walletAPI
|
||||
@Environment(ToastCenter.self) private var toastCenter
|
||||
@State private var viewModel = PointsRedemptionViewModel()
|
||||
@EnvironmentObject private var accountContext: AccountContext
|
||||
@Environment(\.walletAPI) private var walletAPI
|
||||
@EnvironmentObject private var toastCenter: ToastCenter
|
||||
@StateObject private var viewModel = PointsRedemptionViewModel()
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
@ -517,7 +517,7 @@ struct PointsRedemptionView: View {
|
||||
.navigationTitle("积分兑换")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.task { await viewModel.load(api: walletAPI, staffId: staffId) }
|
||||
.onChange(of: viewModel.errorMessage) { _, message in
|
||||
.onChange(of: viewModel.errorMessage) { message in
|
||||
if let message {
|
||||
toastCenter.show(message)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user