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,67 @@
//
// WalletAPI.swift
// suixinkan
//
import Foundation
@MainActor
///
protocol WalletServing {
/// `type = 2`
func walletSummary(type: Int) async throws -> WalletSummaryResponse
///
func walletTransactionList(
startDate: String,
endDate: String,
page: Int,
pageSize: Int,
type: Int
) async throws -> WalletTransactionListResponse
}
@MainActor
/// API
final class WalletAPI: WalletServing {
private let client: APIClient
/// API
init(client: APIClient) {
self.client = client
}
///
func walletSummary(type: Int = 0) async throws -> WalletSummaryResponse {
try await client.send(
APIRequest(
method: .get,
path: "/api/yf-handset-app/photog/wallet/summary",
queryItems: [URLQueryItem(name: "type", value: String(type))]
)
)
}
///
func walletTransactionList(
startDate: String,
endDate: String,
page: Int = 1,
pageSize: Int = 10,
type: Int = 2
) async throws -> WalletTransactionListResponse {
try await client.send(
APIRequest(
method: .get,
path: "/api/yf-handset-app/photog/wallet/transaction-list",
queryItems: [
URLQueryItem(name: "start_date", value: startDate),
URLQueryItem(name: "end_date", value: endDate),
URLQueryItem(name: "page", value: String(max(page, 1))),
URLQueryItem(name: "page_size", value: String(max(pageSize, 1))),
URLQueryItem(name: "type", value: String(type)),
]
)
)
}
}

View File

@ -0,0 +1,127 @@
//
// WalletModels.swift
// suixinkan
//
import Foundation
///
struct WalletSummaryResponse: Decodable, Equatable {
let amountTotal: String
let amountCurrentBalance: String
let amountWithdrawable: String
enum CodingKeys: String, CodingKey {
case amountTotal = "amount_total"
case amountCurrentBalance = "amount_current_balance"
case amountWithdrawable = "amount_withdrawable"
}
///
init(amountTotal: String = "", amountCurrentBalance: String = "", amountWithdrawable: String = "") {
self.amountTotal = amountTotal
self.amountCurrentBalance = amountCurrentBalance
self.amountWithdrawable = amountWithdrawable
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
amountTotal = try container.decodeWalletLossyString(forKey: .amountTotal)
amountCurrentBalance = try container.decodeWalletLossyString(forKey: .amountCurrentBalance)
amountWithdrawable = try container.decodeWalletLossyString(forKey: .amountWithdrawable)
}
}
///
struct WalletTransactionListResponse: Decodable, Equatable {
let total: Int
let list: [WalletTransactionItem]
///
init(total: Int = 0, list: [WalletTransactionItem] = []) {
self.total = total
self.list = list
}
}
///
struct WalletTransactionItem: Decodable, Hashable {
let id: Int
let amount: String
let transactionType: Int
let transactionTypeLabel: String
let createdAt: String
let orderNumberSuffix: String
let withdrawLabel: String
enum CodingKeys: String, CodingKey {
case id
case amount
case transactionType = "transaction_type"
case transactionTypeLabel = "transaction_type_label"
case createdAt = "created_at"
case orderNumberSuffix = "order_number_suffix"
case withdrawLabel = "withdraw_label"
}
///
init(
id: Int,
amount: String,
transactionType: Int = 0,
transactionTypeLabel: String = "",
createdAt: String,
orderNumberSuffix: String = "",
withdrawLabel: String = ""
) {
self.id = id
self.amount = amount
self.transactionType = transactionType
self.transactionTypeLabel = transactionTypeLabel
self.createdAt = createdAt
self.orderNumberSuffix = orderNumberSuffix
self.withdrawLabel = withdrawLabel
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeWalletLossyInt(forKey: .id) ?? 0
amount = try container.decodeWalletLossyString(forKey: .amount)
transactionType = try container.decodeWalletLossyInt(forKey: .transactionType) ?? 0
transactionTypeLabel = try container.decodeWalletLossyString(forKey: .transactionTypeLabel)
createdAt = try container.decodeWalletLossyString(forKey: .createdAt)
orderNumberSuffix = try container.decodeWalletLossyString(forKey: .orderNumberSuffix)
withdrawLabel = try container.decodeWalletLossyString(forKey: .withdrawLabel)
}
}
private extension KeyedDecodingContainer {
func decodeWalletLossyString(forKey key: Key) throws -> String {
if let value = try? decodeIfPresent(String.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return String(value)
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return String(value)
}
return ""
}
func decodeWalletLossyInt(forKey key: Key) throws -> Int? {
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return Int(value)
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
return Int(text) ?? Double(text).map(Int.init)
}
return nil
}
}