mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-27 17:58:47 +00:00
157 lines
6.2 KiB
Swift
157 lines
6.2 KiB
Swift
//
|
|
// BadgesLedgerView.swift
|
|
// SimpleX (iOS)
|
|
//
|
|
// Created by spaced4ndy on 17.09.2026.
|
|
// Copyright © 2026 SimpleX Chat. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import SimpleXChat
|
|
|
|
struct BadgesLedgerView: View {
|
|
@EnvironmentObject var theme: AppTheme
|
|
@EnvironmentObject var chatModel: ChatModel
|
|
let badgeState: BadgeState
|
|
@State private var entries: [StatementEntry]? = nil
|
|
@State private var expanded: Set<String> = []
|
|
|
|
var body: some View {
|
|
List {
|
|
if let entries {
|
|
Section {
|
|
if entries.isEmpty {
|
|
Text("No entries")
|
|
.foregroundColor(theme.colors.secondary)
|
|
} else {
|
|
ForEach(entries, id: \.entryId) { entry in
|
|
ledgerRow(entry)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Badge ledger")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.modifier(ThemedBackground(grouped: true))
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button { showShareSheet(items: [ledgerShareText()]) } label: {
|
|
Image(systemName: "square.and.arrow.up")
|
|
}
|
|
.disabled(entries?.isEmpty ?? true)
|
|
}
|
|
}
|
|
.onAppear(perform: loadLedger)
|
|
}
|
|
|
|
@ViewBuilder private func ledgerRow(_ entry: StatementEntry) -> some View {
|
|
let isExpanded = expanded.contains(entry.entryId)
|
|
Button {
|
|
withAnimation {
|
|
if isExpanded { expanded.remove(entry.entryId) } else { expanded.insert(entry.entryId) }
|
|
}
|
|
} label: {
|
|
HStack {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(entry.entryType.text)
|
|
Text(dateText(entry.createdAt))
|
|
.font(.caption)
|
|
.foregroundColor(theme.colors.secondary)
|
|
}
|
|
Spacer()
|
|
Text(changeText(entry))
|
|
.foregroundStyle(.secondary)
|
|
Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
|
|
.foregroundColor(theme.colors.secondary)
|
|
}
|
|
}
|
|
.foregroundColor(theme.colors.onBackground)
|
|
if isExpanded {
|
|
ForEach(entryFields(entry), id: \.0) { field in
|
|
infoRow(Text(field.0), field.1).padding(.leading, 24)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func changeText(_ entry: StatementEntry) -> String {
|
|
let n = entry.changeMonths
|
|
let months = abs(n) == 1
|
|
? String.localizedStringWithFormat(NSLocalizedString("%d month", comment: "time interval"), n)
|
|
: String.localizedStringWithFormat(NSLocalizedString("%d months", comment: "time interval"), n)
|
|
return n > 0 ? "+" + months : months
|
|
}
|
|
|
|
private func entryFields(_ entry: StatementEntry) -> [(String, String)] {
|
|
var fields = [
|
|
(NSLocalizedString("Date", comment: "ledger entry field"), dateTimeText(entry.createdAt)),
|
|
(NSLocalizedString("Balance", comment: "ledger entry field"), "\(entry.balanceMonths)"),
|
|
(NSLocalizedString("Balance start", comment: "ledger entry field"), dateTimeText(entry.balanceStartTs)),
|
|
(NSLocalizedString("Anchor", comment: "ledger entry field"), dateTimeText(entry.balanceAnchorTs)),
|
|
(NSLocalizedString("Badge type", comment: "ledger entry field"), entry.balanceBadgeType.text)
|
|
]
|
|
if let pausedSince = entry.wasPausedSince {
|
|
fields.append((NSLocalizedString("Paused since", comment: "ledger entry field"), dateTimeText(pausedSince)))
|
|
}
|
|
fields.append((NSLocalizedString("Entry ID", comment: "ledger entry field"), entry.entryId))
|
|
if let payload = payloadField(entry.entryType) {
|
|
fields.append(payload)
|
|
}
|
|
return fields
|
|
}
|
|
|
|
private func payloadField(_ entryType: StatementEntryType) -> (String, String)? {
|
|
switch entryType {
|
|
case let .credit(credit):
|
|
switch credit {
|
|
case let .payment(invoiceId):
|
|
return invoiceId.map { (NSLocalizedString("Invoice ID", comment: "ledger entry field"), $0) }
|
|
case let .charge(chargeId):
|
|
return (NSLocalizedString("Charge ID", comment: "ledger entry field"), chargeId)
|
|
case let .transferIn(fromPurchaseKey):
|
|
return (NSLocalizedString("From purchase key", comment: "ledger entry field"), fromPurchaseKey)
|
|
case .code, .support, .opening, .unknown:
|
|
return nil
|
|
}
|
|
case let .debit(debit):
|
|
switch debit {
|
|
case let .upgrade(toPurchaseKey), let .transferOut(toPurchaseKey):
|
|
return (NSLocalizedString("To purchase key", comment: "ledger entry field"), toPurchaseKey)
|
|
case .refund, .support, .badge, .lapse, .unknown:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// the JSON as core sent it: English field names and ISO dates, for support
|
|
private func ledgerShareText() -> String {
|
|
let encoder = getJSONEncoder()
|
|
encoder.outputFormatting = .prettyPrinted
|
|
let data = (try? encoder.encode(entries ?? [])) ?? Data()
|
|
return String(decoding: data, as: UTF8.self)
|
|
}
|
|
|
|
private func dateText(_ date: Date) -> String {
|
|
DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .none)
|
|
}
|
|
|
|
private func dateTimeText(_ date: Date) -> String {
|
|
DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .short)
|
|
}
|
|
|
|
private func loadLedger() {
|
|
guard let user = chatModel.currentUser else { return }
|
|
Task {
|
|
do {
|
|
let ledger = try await apiGetBadgeLedger(user.userId, badgeState.badgePurchaseId)
|
|
await MainActor.run { entries = ledger }
|
|
} catch let e {
|
|
logger.error("apiGetBadgeLedger error: \(responseError(e))")
|
|
await MainActor.run {
|
|
showErrorAlert(e, NSLocalizedString("Error", comment: ""))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|