feat: add WeChat sharing and invite flow

This commit is contained in:
2026-07-07 16:53:39 +08:00
parent f1937e23d4
commit a80c181bd7
160 changed files with 7557 additions and 849 deletions

View File

@ -0,0 +1,218 @@
//
// InviteViewModels.swift
// suixinkan
//
import Foundation
import UIKit
/// ViewModel
final class PhotographerInviteViewModel {
private(set) var isLoading = false
private(set) var inviteCode = ""
private(set) var inviteUrl = ""
private(set) var rules: [String] = []
private(set) var qrImage: UIImage?
private(set) var errorMessage: String?
var onStateChange: (() -> Void)?
///
func reload(api: any InviteServing) async {
isLoading = true
errorMessage = nil
notifyStateChange()
do {
let response = try await api.inviteInfo()
inviteCode = response.inviteCode
inviteUrl = response.inviteUrl
rules = response.description
qrImage = QRCodeGenerator.generateStyledQRCode(
content: response.inviteUrl,
pixelSize: 560,
backgroundColor: .white,
cornerRadius: 0
)
} catch {
clear()
errorMessage = error.localizedDescription
}
isLoading = false
notifyStateChange()
}
///
func inviteCodeForCopy() -> String {
inviteCode
}
///
func inviteUrlForCopy() -> String {
inviteUrl
}
private func clear() {
inviteCode = ""
inviteUrl = ""
rules = []
qrImage = nil
}
private func notifyStateChange() {
onStateChange?()
}
}
/// ViewModel
final class InviteRecordViewModel {
private(set) var selectedTab: InviteRecordTab = .invite
private(set) var totalRewardText = "0.00"
private(set) var withdrawableText = "0.00"
private(set) var inviteRows: [InviteDisplayRow] = []
private(set) var rewardRows: [WalletTransactionItem] = []
private(set) var isInviteRefreshing = false
private(set) var isRewardRefreshing = false
private(set) var canLoadMoreInvite = false
private(set) var canLoadMoreReward = false
private(set) var errorMessage: String?
var onStateChange: (() -> Void)?
private var invitePage = 1
private let invitePageSize = 20
private var rewardPage = 1
private let rewardPageSize = 10
private var isLoadingInvite = false
private var isLoadingReward = false
///
func loadInitial(inviteAPI: any InviteServing, walletAPI: any WalletServing) async {
await refreshInvite(inviteAPI: inviteAPI, walletAPI: walletAPI)
}
///
func selectTab(_ tab: InviteRecordTab, inviteAPI: any InviteServing, walletAPI: any WalletServing) async {
guard tab != selectedTab else { return }
selectedTab = tab
notifyStateChange()
if tab == .reward, rewardRows.isEmpty {
await refreshReward(walletAPI: walletAPI)
}
}
///
func refreshInvite(inviteAPI: any InviteServing, walletAPI: any WalletServing) async {
await loadSummary(walletAPI: walletAPI)
await loadInviteRows(inviteAPI: inviteAPI, refresh: true)
}
///
func loadMoreInvite(inviteAPI: any InviteServing) async {
guard canLoadMoreInvite, !isInviteRefreshing else { return }
await loadInviteRows(inviteAPI: inviteAPI, refresh: false)
}
///
func refreshReward(walletAPI: any WalletServing) async {
await loadSummary(walletAPI: walletAPI)
await loadRewardRows(walletAPI: walletAPI, refresh: true)
}
///
func loadMoreReward(walletAPI: any WalletServing) async {
guard canLoadMoreReward, !isRewardRefreshing else { return }
await loadRewardRows(walletAPI: walletAPI, refresh: false)
}
private func loadSummary(walletAPI: any WalletServing) async {
do {
let summary = try await walletAPI.walletSummary(type: 2)
totalRewardText = summary.amountTotal.isEmpty ? "0.00" : summary.amountTotal
withdrawableText = summary.amountWithdrawable.isEmpty ? "0.00" : summary.amountWithdrawable
} catch {
errorMessage = error.localizedDescription
}
notifyStateChange()
}
private func loadInviteRows(inviteAPI: any InviteServing, refresh: Bool) async {
if isLoadingInvite && !refresh { return }
if refresh { invitePage = 1 }
isLoadingInvite = true
isInviteRefreshing = refresh
errorMessage = nil
notifyStateChange()
do {
let users = try await inviteAPI.inviteUserList(page: invitePage, pageSize: invitePageSize)
let mapped = users.map { user in
InviteDisplayRow(
id: "invite_\(user.id)",
title: user.realName.isEmpty ? "**\(user.phone.suffix(4))" : user.realName,
phone: user.phone,
avatar: user.avatar,
time: user.createdAt,
inviteLevel: user.inviteLevel
)
}
inviteRows = refresh ? mapped : inviteRows + mapped
invitePage += 1
canLoadMoreInvite = users.count >= invitePageSize
} catch {
if refresh {
inviteRows = []
canLoadMoreInvite = false
}
errorMessage = error.localizedDescription
}
isLoadingInvite = false
isInviteRefreshing = false
notifyStateChange()
}
private func loadRewardRows(walletAPI: any WalletServing, refresh: Bool) async {
if isLoadingReward && !refresh { return }
if refresh { rewardPage = 1 }
isLoadingReward = true
isRewardRefreshing = refresh
errorMessage = nil
notifyStateChange()
do {
let response = try await walletAPI.walletTransactionList(
startDate: "2025-01-01",
endDate: Self.dayFormatter.string(from: Date()),
page: rewardPage,
pageSize: rewardPageSize,
type: 2
)
rewardRows = refresh ? response.list : rewardRows + response.list
rewardPage += 1
canLoadMoreReward = response.list.count >= rewardPageSize && rewardRows.count < response.total
} catch {
if refresh {
rewardRows = []
canLoadMoreReward = false
}
errorMessage = error.localizedDescription
}
isLoadingReward = false
isRewardRefreshing = false
notifyStateChange()
}
private func notifyStateChange() {
onStateChange?()
}
private static let dayFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "zh_CN")
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
}