Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Flipcash/Utilities/Events.swift
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ extension Analytics {
case .denied: "Denied"
case .notFound: "NotFound"
case .rulesNotSatisfied: "RulesNotSatisfied"
case .banned: "Banned"
case .ok, .unknown, .transportFailure, .cancelled, .rejected: "Network"
}
case let error as ErrorLeaveChat:
Expand Down
2 changes: 1 addition & 1 deletion FlipcashAPI/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ enum ContractPackage: String, CaseIterable {
var version: Version {
switch self {
case .ocp: return "0.9.0"
case .flipcash2: return "0.20.0"
case .flipcash2: return "0.21.0"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,52 @@ extension FlipClient {
}
}

// MARK: - Moderation -

/// Silences `userID` in `conversationID` until `silence` lapses (or forever). Creator only.
public func silenceUser(owner: KeyPair, conversationID: ConversationID, userID: UserID, silence: ConversationSilenceState) async throws {
try await withCheckedThrowingContinuation { c in
chatService.silenceUser(owner: owner, conversationID: conversationID, userID: userID, silence: silence) { c.resume(with: $0) }
}
}

/// Lifts a silence on `userID` in `conversationID`. Creator only.
public func unsilenceUser(owner: KeyPair, conversationID: ConversationID, userID: UserID) async throws {
try await withCheckedThrowingContinuation { c in
chatService.unsilenceUser(owner: owner, conversationID: conversationID, userID: userID) { c.resume(with: $0) }
}
}

/// Bans `userID` from `conversationID`. Creator only.
public func banUser(owner: KeyPair, conversationID: ConversationID, userID: UserID) async throws {
try await withCheckedThrowingContinuation { c in
chatService.banUser(owner: owner, conversationID: conversationID, userID: userID) { c.resume(with: $0) }
}
}

/// Lifts a ban on `userID` in `conversationID`. Creator only.
public func unbanUser(owner: KeyPair, conversationID: ConversationID, userID: UserID) async throws {
try await withCheckedThrowingContinuation { c in
chatService.unbanUser(owner: owner, conversationID: conversationID, userID: userID) { c.resume(with: $0) }
}
}

/// Every user silenced or banned in `conversationID`, paged to exhaustion. Creator only.
public func getRestrictedUsers(owner: KeyPair, conversationID: ConversationID) async throws -> [ConversationRestrictedUser] {
var all: [ConversationRestrictedUser] = []
var pagingToken: Data?

while true {
let page: ChatService.RestrictedUsersPage = try await withCheckedThrowingContinuation { c in
chatService.getRestrictedUsers(owner: owner, conversationID: conversationID, pagingToken: pagingToken) { c.resume(with: $0) }
}
all.append(contentsOf: page.users)
guard page.hasMore else { break }
pagingToken = page.pagingToken
}
return all
}

/// Page the group chat feed to exhaustion against a single pinned snapshot. Unlike a DM feed, the
/// caller's own membership can change mid-read (a join or a leave lands as a `rosterChanged`
/// stream event, not a feed mutation) — the same live-stream caveat as `getDmChatFeed` applies.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// FlipClient+GroupPaymentRequest.swift
// FlipcashCore
//
// Copyright © 2026 Code Inc. All rights reserved.
//

import Foundation
import FlipcashAPI

extension FlipClient {

/// Creates a split in the group chat `chatID`. Reuse `idempotencyKey` across retries.
public func createPaymentRequest(owner: KeyPair, idempotencyKey: Data, chatID: ConversationID, split: GroupPaymentRequest.Split) async throws -> GroupPaymentRequest {
try await withCheckedThrowingContinuation { c in
paymentRequestService.createPaymentRequest(owner: owner, idempotencyKey: idempotencyKey, chatID: chatID, split: split) { c.resume(with: $0) }
}
}

/// Fetches one request. `paymentIntentID` is the intent id a caller holding an unpaid share must
/// use for the payment, and `nil` for anyone else.
public func getPaymentRequest(owner: KeyPair, id: PaymentRequestID) async throws -> (request: GroupPaymentRequest, paymentIntentID: Data?) {
let fetched: PaymentRequestService.Fetched = try await withCheckedThrowingContinuation { c in
paymentRequestService.getPaymentRequest(owner: owner, id: id) { c.resume(with: $0) }
}
return (fetched.request, fetched.paymentIntentID)
}

/// Every request in `conversationID` for `section`, paged to exhaustion.
public func getChatPaymentRequests(owner: KeyPair, conversationID: ConversationID, section: GroupPaymentRequest.Section) async throws -> [GroupPaymentRequest] {
var all: [GroupPaymentRequest] = []
var pagingToken: Data?

while true {
let page: PaymentRequestService.ChatPage = try await withCheckedThrowingContinuation { c in
paymentRequestService.getChatPaymentRequests(owner: owner, conversationID: conversationID, section: section, pagingToken: pagingToken) { c.resume(with: $0) }
}
all.append(contentsOf: page.requests)
guard page.hasMore else { break }
pagingToken = page.pagingToken
}
return all
}

/// Closes an open request. Creator only.
public func closePaymentRequest(owner: KeyPair, id: PaymentRequestID) async throws -> GroupPaymentRequest {
try await withCheckedThrowingContinuation { c in
paymentRequestService.closePaymentRequest(owner: owner, id: id) { c.resume(with: $0) }
}
}

/// Marks `userID`'s share paid out of band. Creator only.
public func markPaid(owner: KeyPair, id: PaymentRequestID, userID: UserID) async throws -> GroupPaymentRequest {
try await withCheckedThrowingContinuation { c in
paymentRequestService.markPaid(owner: owner, id: id, userID: userID) { c.resume(with: $0) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class FlipClient: ObservableObject {
internal let reportingService: ReportingService
internal let pollService: PollService
internal let communityService: CommunityService
internal let paymentRequestService: PaymentRequestService

/// The single per-user event stream. Started on login, stopped on logout.
public let eventStreamer: EventStreamer
Expand Down Expand Up @@ -91,6 +92,7 @@ public class FlipClient: ObservableObject {
self.reportingService = ReportingService(client: client)
self.pollService = PollService(client: client)
self.communityService = CommunityService(client: client)
self.paymentRequestService = PaymentRequestService(client: client)
self.eventStreamer = EventStreamer(service: EventStreamingService(client: client))
}

Expand Down
Loading
Loading