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
37 changes: 24 additions & 13 deletions FlipcashUI/Sources/FlipcashUI/Chat/ChatCashCardCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ import UIKit
import FlipcashCore
import Kingfisher

/// A recycled cell for a cash payment row: a fixed-size card (shared `BubbleBackgroundView` chrome)
/// with the token name + optional coin icon in the top-left and a centered "You sent / You received"
/// caption above the currency flag + amount, hosted in a `ChatColumnCell`. Dumb — `configure(with:)`
/// is the only input; the strings, flag name, and icon URL all arrive on the `ChatMessage`.
/// A recycled cell for a cash payment row: a card (shared `BubbleBackgroundView` chrome) with the
/// token name + optional coin icon in the top-left and a centered "You sent / You received" caption
/// above the currency flag + amount, hosted in a `ChatColumnCell`. The card's width is supplied by
/// the owner, as `ChatMessageCell`/`ChatLinkMessageCell` take theirs, so it tracks the transcript
/// width instead of a fixed footprint. `configure(with:maxWidth:)` is the only input; the strings,
/// flag name, and icon URL all arrive on the `ChatMessage`.
public final class ChatCashCardCell: ChatColumnCell {

public static let reuseIdentifier = "ChatCashCardCell"

/// The card's fixed footprint.
static let cardSize = CGSize(width: 232, height: 170)
/// The card's fixed height; width now tracks `maxWidth`.
static let cardHeight: CGFloat = 170

private let card = BubbleBackgroundView()
private let reactionRow = ReactionPillRowView()
Expand All @@ -28,6 +30,8 @@ public final class ChatCashCardCell: ChatColumnCell {
private let flag = UIImageView()
private let captionLabel = UILabel()
private let amountLabel = UILabel()
private var cardWidthConstraint: NSLayoutConstraint!
private var reactionRowWidthConstraint: NSLayoutConstraint!

/// Dim the card while pressed so the tap-to-open-currency-info affordance reads as a button.
/// Driven by the collection view's selection machinery (`shouldHighlightItemAt`), not a gesture.
Expand Down Expand Up @@ -83,12 +87,15 @@ public final class ChatCashCardCell: ChatColumnCell {
reactionRow.onAdd = { [weak self] in self?.onReactionAdd?() }

// Below required so the card height yields to the cell's self-sizing height instead of fighting it.
let cardHeight = card.heightAnchor.constraint(equalToConstant: Self.cardSize.height)
let cardHeight = card.heightAnchor.constraint(equalToConstant: Self.cardHeight)
cardHeight.priority = UILayoutPriority(999)

cardWidthConstraint = card.widthAnchor.constraint(equalToConstant: 232)
reactionRowWidthConstraint = reactionRow.widthAnchor.constraint(equalToConstant: 232)

NSLayoutConstraint.activate([
card.widthAnchor.constraint(equalToConstant: Self.cardSize.width),
reactionRow.widthAnchor.constraint(equalToConstant: Self.cardSize.width),
cardWidthConstraint,
reactionRowWidthConstraint,
cardHeight,

tokenRow.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 11),
Expand All @@ -104,7 +111,7 @@ public final class ChatCashCardCell: ChatColumnCell {
@available(*, unavailable)
public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }

/// Scopes the row's touch target to the fixed-width card. The cell spans the full transcript
/// Scopes the row's touch target to the card, not the cell. The cell spans the full transcript
/// width, so without this the selection machinery opens currency info from a tap in the dead
/// space beside the card — the same reason the retry gesture on text rows hugs the bubble.
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
Expand Down Expand Up @@ -136,8 +143,12 @@ public final class ChatCashCardCell: ChatColumnCell {
reactionRow.prepareForReuse()
}

public func configure(with message: ChatMessage, authorImageData: Data? = nil) {
/// - Parameter maxWidth: the card's width, in points. The owner derives it from the collection
/// view's width: 0.64 of the row between the column insets, matching Android's cash card.
public func configure(with message: ChatMessage, maxWidth: CGFloat, authorImageData: Data? = nil) {
guard case .cash(let cash) = message.content else { return }
cardWidthConstraint.constant = maxWidth
reactionRowWidthConstraint.constant = maxWidth
tokenLabel.text = cash.token
captionLabel.text = ChatCashContent.caption(isFromSelf: message.sender == .me, isTip: cash.isTip)
amountLabel.text = cash.amount
Expand All @@ -158,7 +169,7 @@ public final class ChatCashCardCell: ChatColumnCell {
),
identity: message.id
)
reactionRow.layoutWidth = Self.cardSize.width
reactionRow.layoutWidth = maxWidth
reactionRow.hugsTrailingEdge = message.sender == .me
reactionRow.configure(pills: message.reactions, canReact: message.canReact)
updateColumn(for: message, authorImageData: authorImageData)
Expand Down Expand Up @@ -215,7 +226,7 @@ private final class ChatCashCardCellPreviewController: UICollectionViewControlle

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ChatCashCardCell.reuseIdentifier, for: indexPath) as! ChatCashCardCell
cell.configure(with: messages[indexPath.item])
cell.configure(with: messages[indexPath.item], maxWidth: (collectionView.bounds.width - 24) * 0.64)
return cell
}
}
Expand Down
8 changes: 7 additions & 1 deletion FlipcashUI/Sources/FlipcashUI/Chat/ChatViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ public final class ChatViewController: UICollectionViewController {
/// The widest a bubble may grow, as a share of the collection view's width.
private static let maxBubbleWidthFraction: CGFloat = 0.78

/// A cash card's width as a fraction of the row between the column insets — narrower than a
/// bubble, and taken of the inset row rather than the full width. Matches Android's
/// `CASH_CARD_WIDTH_FRACTION` so the card reads the same size on both.
private static let cashCardWidthFraction: CGFloat = 0.64

/// Avatar bytes for the transcript's authors and typists, keyed by user id. Empty in a DM, where
/// no row is attributed. The owner fills it as pictures download; rows already on screen pick the
/// new bytes up without a diff, since nothing about the row itself changed.
Expand Down Expand Up @@ -509,7 +514,8 @@ public final class ChatViewController: UICollectionViewController {
cell.onReactionLongPress = { [weak self] emoji in self?.onReactionLongPress?(message.messageID, emoji) }
cell.onReactionAdd = { [weak self] in self?.onReactionAdd?(message.messageID) }
case let cell as ChatCashCardCell:
cell.configure(with: message, authorImageData: authorImageData)
let cardWidth = (available - ChatColumnCell.rowInset * 2) * Self.cashCardWidthFraction
cell.configure(with: message, maxWidth: cardWidth, authorImageData: authorImageData)
cell.onReactionTap = { [weak self] emoji in self?.onReactionTap?(message.messageID, emoji) }
cell.onReactionLongPress = { [weak self] emoji in self?.onReactionLongPress?(message.messageID, emoji) }
cell.onReactionAdd = { [weak self] in self?.onReactionAdd?(message.messageID) }
Expand Down
3 changes: 2 additions & 1 deletion NotificationContent/NotificationTranscriptView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ private struct NotificationTextBubble: View {
}
}

/// A payment "cash card" matching `ChatCashCardCell` (232×170): a token row top-left, a centered
/// A payment "cash card" styled like `ChatCashCardCell`, fixed at 232×170 here (there is no
/// transcript width to size off in a notification banner): a token row top-left, a centered
/// "You sent / You received" caption over the currency flag + amount, and the same bubble chrome.
private struct NotificationCashCard: View {

Expand Down
Loading