taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

commit cd52ab1e95e9998632fd30a8215f3439074ea68f
parent d25e0bc71c6a8c53af084a74fd94cd83a180032a
Author: Marc Stibane <marc@taler.net>
Date:   Mon, 22 Jun 2026 09:55:27 +0200

Subscriptions

Diffstat:
MTalerWallet1/Controllers/Controller.swift | 20+++++++++++++++++++-
MTalerWallet1/Model/Model+Balances.swift | 45+++++++++++++++++++++++++++++++++++++++++----
MTalerWallet1/Views/Balances/DiscountPasses.swift | 2+-
3 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/TalerWallet1/Controllers/Controller.swift b/TalerWallet1/Controllers/Controller.swift @@ -125,7 +125,8 @@ class Controller: ObservableObject { @Published var haveProdBalance: Bool = false @Published var balances: [Balance] = [] - @Published var discounts: [Discount] = [] + @Published var discounts: [TalerToken] = [] + @Published var subscriptions: [TalerToken] = [] @Published var defaultExchanges: [DefaultExchange] = [] @Published var scannedURLs: [ScannedURL] = [] @@ -307,6 +308,7 @@ class Controller: ObservableObject { exchanges = [] balances = [] discounts = [] + subscriptions = [] defaultExchanges = [] // printFonts() // checkInternetConnection() @@ -358,6 +360,22 @@ class Controller: ObservableObject { } return nil } + // MARK: - + @MainActor + @discardableResult + func loadSubscriptions(_ stack: CallStack,_ model: WalletModel) async -> Int? { + if let response = try? await model.listSubscriptions(stack.push()) { + let reloaded = response.subscriptions + if reloaded != subscriptions { + self.logger.log("••Got new passes, will redraw") + subscriptions = reloaded // redraw + } else { + self.logger.log("••Same passes, no redraw") + } + return reloaded.count + } + return nil + } // MARK: - func exchange(for baseUrl: String) -> Exchange? { for exchange in exchanges { diff --git a/TalerWallet1/Model/Model+Balances.swift b/TalerWallet1/Model/Model+Balances.swift @@ -83,7 +83,7 @@ fileprivate struct Balances: WalletBackendFormattedRequest { typealias Response = BalancesResponse } // MARK: - -struct Discount: Identifiable, Codable, Hashable, Sendable { +struct TalerToken: Identifiable, Codable, Hashable, Sendable { // Hash of token family info. var tokenFamilyHash: String // Hash of token issue public key. @@ -103,7 +103,7 @@ struct Discount: Identifiable, Codable, Hashable, Sendable { // End time of the token's validity period. var validityEnd: Timestamp // Number of tokens available to use. - var tokensAvailable: Int + var tokensAvailable: Int? // Only for Discount var id: String { return tokenFamilyHash @@ -111,7 +111,7 @@ struct Discount: Identifiable, Codable, Hashable, Sendable { } // MARK: - struct DiscountsResponse: Decodable, Hashable, Sendable { - let discounts: [Discount] + let discounts: [TalerToken] } /// A request to get the balances held in the wallet. @@ -135,6 +135,31 @@ struct DeleteDiscount: WalletBackendFormattedRequest { } } // MARK: - +struct SubscriptionsResponse: Decodable, Hashable, Sendable { + let subscriptions: [TalerToken] +} + +/// A request to get the balances held in the wallet. +fileprivate struct ListSubscriptions: WalletBackendFormattedRequest { + func operation() -> String { "listSubscriptions" } + func args() -> Args { Args() } + + struct Args: Encodable {} // no arguments needed + + typealias Response = SubscriptionsResponse +} +/// A request to delete a subscription by ID. +struct DeleteSubscription: WalletBackendFormattedRequest { + struct Response: Decodable {} // no result - getting no error back means success + func operation() -> String { "deleteSubscription" } + func args() -> Args { Args(tokenFamilyHash: tokenFamilyHash) } + + var tokenFamilyHash: String + struct Args: Encodable { + var tokenFamilyHash: String + } +} +// MARK: - extension WalletModel { /// fetch Balances from Wallet-Core. No networking involved nonisolated func getBalances(_ stack: CallStack, viewHandles: Bool = false) async throws -> BalancesResponse { @@ -148,10 +173,22 @@ extension WalletModel { let response = try await sendRequest(request, viewHandles: viewHandles) return response } - /// fetch Discounts from Wallet-Core. No networking involved + /// delete a discount. No networking involved nonisolated func deleteDiscount(tokenFamilyHash: String, viewHandles: Bool = false) async throws { let request = DeleteDiscount(tokenFamilyHash: tokenFamilyHash) logger.notice("deleteDiscount: \(tokenFamilyHash, privacy: .private(mask: .hash))") let _ = try await sendRequest(request, viewHandles: viewHandles) } + /// fetch Subscriptions from Wallet-Core. No networking involved + nonisolated func listSubscriptions(_ stack: CallStack, viewHandles: Bool = false) async throws -> SubscriptionsResponse { + let request = ListSubscriptions() + let response = try await sendRequest(request, viewHandles: viewHandles) + return response + } + /// delete a subscription. No networking involved + nonisolated func deleteSubscription(tokenFamilyHash: String, viewHandles: Bool = false) async throws { + let request = DeleteSubscription(tokenFamilyHash: tokenFamilyHash) + logger.notice("deleteSubscription: \(tokenFamilyHash, privacy: .private(mask: .hash))") + let _ = try await sendRequest(request, viewHandles: viewHandles) + } } diff --git a/TalerWallet1/Views/Balances/DiscountPasses.swift b/TalerWallet1/Views/Balances/DiscountPasses.swift @@ -27,7 +27,7 @@ struct DiscountsPassesList: View { if isDiscount { await controller.loadDiscounts(stack.push("refreshing discounts"), model) } else { -// await controller.loadPasses(stack.push("refreshing passes"), model) + await controller.loadSubscriptions(stack.push("refreshing passes"), model) } }