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
25 changes: 18 additions & 7 deletions Flipcash/Core/Screens/Main/Home/ActivityAvatar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import FlipcashCore
/// activity (tips/sends), the token image for token activity (deposits, buys),
/// two overlapping coins for a conversion, or a monogram fallback. A peer avatar
/// shows a face rather than a token, so it carries the transacted token as a coin
/// badge (Figma 8966:1910, 9717:14215).
/// badge (Figma 8966:1910, 9717:14215) unless the caller turns it off.
///
/// Shared by the activity row and the transaction details header at different
/// sizes, so the details screen opens on exactly the avatar that was tapped. Every
Expand All @@ -22,6 +22,11 @@ struct ActivityAvatar: View {
let resolution: ActivityResolution
var size: CGFloat = 40

/// Whether a peer avatar carries its token badge. The details header names the
/// token in full under the amount, so it turns the badge off rather than
/// saying the same thing twice at two sizes.
var showsTokenBadge: Bool = true

@Environment(SessionContainer.self) private var sessionContainer
private var session: Session { sessionContainer.session }

Expand All @@ -41,25 +46,31 @@ struct ActivityAvatar: View {
.frame(width: size, height: size)
.clipShape(Circle())
.overlay(alignment: .bottomTrailing) {
if Self.showsTokenBadge(for: activity) {
if drawsTokenBadge {
tokenBadge.offset(x: badgeOverhang, y: badgeOverhang)
}
}
// Reserves the badge's overhang so it doesn't eat into the gap
// before whatever sits beside the avatar.
.padding(.trailing, Self.showsTokenBadge(for: activity) ? badgeOverhang : 0)
.padding(.trailing, drawsTokenBadge ? badgeOverhang : 0)
}
}

/// Whether the avatar carries a token badge: only a peer activity, whose
/// avatar is the counterparty rather than the token itself.
/// Whether this avatar draws a badge: one the activity calls for, that the
/// caller hasn't turned off.
private var drawsTokenBadge: Bool {
showsTokenBadge && Self.showsTokenBadge(for: activity)
}

/// Whether the activity's avatar calls for a token badge: only a peer
/// activity, whose avatar is the counterparty rather than the token itself.
static func showsTokenBadge(for activity: Activity) -> Bool {
activity.swapMetadata == nil && activity.counterparty != nil
}

/// The token the payment moved in, as a coin badge over the counterparty's
/// avatar (Figma 9717:14140) — a peer activity shows *who*, so this badge is
/// the only place the token reads.
/// avatar (Figma 9717:14140) — a peer row shows *who*, so in a row this badge
/// is the only place the token reads.
@ViewBuilder private var tokenBadge: some View {
tokenCoin(
url: resolution.imageURL(for: activity.exchangedFiat.mint, fallback: resolution.entryMint, session: session),
Expand Down
48 changes: 33 additions & 15 deletions Flipcash/Core/Screens/Main/Home/TransactionDetailsScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ struct TransactionDetailsScreen: View {
/// gives the header.
private static let avatarSize: CGFloat = 80

/// The gap between the cards, per Figma node 9708:118142 — tighter than the
/// gap that separates the header from them.
private static let cardSpacing: CGFloat = 8

private var details: TransactionDetails {
TransactionDetails(
activity: activity,
Expand All @@ -57,21 +61,27 @@ struct TransactionDetailsScreen: View {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 16) {
header
receiptCard
idCard

if let userID = activity.counterparty?.userID, details.canViewInChat {
Button("View in Chat") {
// Pushed onto the stack this screen is already on,
// not routed to the Chat tab: a cross-stack jump
// swaps the tab out from under the transition, so
// the bar and the conversation list both show
// before the chat lands. Pushed, the chat arrives
// from the entry it belongs to and back returns
// here.
router.push(.tipConversationForUser(userID))

// The cards are one block, at the tighter gap Figma sets
// between them; the header sits apart from that block, so
// its gap is the outer stack's rather than this one's.
VStack(spacing: Self.cardSpacing) {
receiptCard
idCard

if let userID = activity.counterparty?.userID, details.canViewInChat {
Button("View in Chat") {
// Pushed onto the stack this screen is already
// on, not routed to the Chat tab: a cross-stack
// jump swaps the tab out from under the
// transition, so the bar and the conversation
// list both show before the chat lands. Pushed,
// the chat arrives from the entry it belongs to
// and back returns here.
router.push(.tipConversationForUser(userID))
}
.buttonStyle(.filled05)
}
.buttonStyle(.filled05)
}
}
.padding(.horizontal, 20)
Expand Down Expand Up @@ -103,7 +113,15 @@ struct TransactionDetailsScreen: View {
private var header: some View {
VStack(spacing: 24) {
VStack(spacing: 16) {
ActivityAvatar(activity: activity, resolution: resolution, size: Self.avatarSize)
// No token badge here, unlike the row: the line under the
// amount already names the token, and Figma draws the header
// avatar plain.
ActivityAvatar(
activity: activity,
resolution: resolution,
size: Self.avatarSize,
showsTokenBadge: false
)

Text(details.title)
.font(.appTextLarge)
Expand Down
Loading