diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/MessageList.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/MessageList.kt index ada715fae6..34a1796938 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/MessageList.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/MessageList.kt @@ -65,7 +65,7 @@ import com.flipcash.shared.chat.ui.ChatAnimations import com.flipcash.shared.chat.ui.GroupInviteCardDefaults import com.flipcash.shared.chat.ui.GroupInviteLinkCard import com.flipcash.shared.chat.ui.QuickReactionStripPlacement -import com.flipcash.shared.chat.ui.transcriptCardWidth +import com.flipcash.shared.chat.ui.transcriptBubbleMaxWidth import com.getcode.theme.CodeTheme import com.getcode.ui.utils.rememberKeyboardController import com.getcode.util.vibration.LocalVibrator @@ -478,7 +478,7 @@ internal fun MessageList( ) { // The width a link card gets in a DM transcript, so this reads as the // same card sent there (iOS sizes both the same way). - val cardWidth = transcriptCardWidth( + val cardWidth = transcriptBubbleMaxWidth( rowWidth = maxWidth, rowInset = CodeTheme.dimens.inset, ) diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/MessageRow.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/MessageRow.kt index 47a49d4eed..78d731440d 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/MessageRow.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/MessageRow.kt @@ -68,6 +68,7 @@ import com.flipcash.shared.chat.reactions.ReactionStrip import com.flipcash.shared.chat.ui.ContentBubble import com.flipcash.shared.chat.ui.QuickReactionStripPopup import com.flipcash.shared.chat.ui.ReactionPillRow +import com.flipcash.shared.chat.ui.bubbleMaxWidth import com.flipcash.shared.chat.ui.bubblePositionOf import com.flipcash.shared.chat.ui.rendersBare import com.flipcash.shared.common.ui.ContactAvatar @@ -399,9 +400,9 @@ internal fun MessageRow( ) } } - // Same width as the bubble above it (decision 2) — matched here - // against the same fraction MessageBubble sizes a text/reply/deleted - // bubble to, since the row doesn't expose its resolved width outward. + // Same width as the bubble above it (decision 2) — sized by the same + // rule MessageBubble caps every bubble with, since the row doesn't + // expose its resolved width outward. // Kept on screen while selecting, as iOS keeps them under its // backdrop, but inert like every other target on the row. // @@ -433,7 +434,13 @@ internal fun MessageRow( }, modifier = Modifier .align(if (item.isFromSelf) Alignment.TopEnd else Alignment.TopStart) - .width(maxWidth * BUBBLE_ROW_WIDTH_FRACTION), + .width( + bubbleMaxWidth( + content = item.content, + rowWidth = maxWidth, + rowInset = CodeTheme.dimens.inset, + ), + ), alignEnd = item.isFromSelf, animateInitialPills = !pillsAtFirstComposition, ) @@ -554,10 +561,6 @@ private fun senderNameInset(showsGutter: Boolean): Dp = CodeTheme.dimens.grid.x1 } -// Matches MessageBubble's own BUBBLE_MAX_WIDTH_FRACTION for a text/reply/deleted bubble — the row -// doesn't expose its resolved width outward, so the pill row underneath it re-derives the same -// fraction of the shared row width instead. -private const val BUBBLE_ROW_WIDTH_FRACTION = 0.78f private val STRIP_BAR_GAP = 8.dp private val AFFORDANCE_SIZE = 32.dp diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/SwipeToReply.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/SwipeToReply.kt index 37f941fac2..381acda0a8 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/SwipeToReply.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/SwipeToReply.kt @@ -139,7 +139,7 @@ internal fun rememberSwipeToReply( * where the bubble is aligned to the far side. * * [width] is that one avatar column's width, reused as the outgoing strip so both read the same. It - * cannot eat into an outgoing bubble: a bubble stops at 0.78 of the row, which leaves far more than + * cannot eat into an outgoing bubble: a bubble stops at 0.78 of the transcript, which leaves more than * this to its leading side at every width the app runs at. The one content that fills the row is a * system notice, and those carry no capabilities at all, so no drag on one was ever going to reply. */ diff --git a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt index 75b11d1e18..d0ab027db5 100644 --- a/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt +++ b/apps/flipcash/shared/chat-ui/src/main/kotlin/com/flipcash/shared/chat/ui/MessageBubble.kt @@ -83,19 +83,32 @@ enum class BubblePosition { Solo, First, Middle, Last } private const val BUBBLE_MAX_WIDTH_FRACTION = 0.78f +private const val CASH_CARD_WIDTH_FRACTION = 0.64f + /** - * How wide a link card is drawn in a transcript whose rows are inset by [rowInset] on each side. + * The widest a bubble or link card grows in a transcript whose rows are inset by [rowInset] on each + * side. * - * The same fraction as a text bubble, but of the transcript's full width rather than of the inset - * row, which is how iOS sizes its link cards and its group's head card - * (`ChatViewController.maxBubbleWidthFraction`). Taken of the inset row, the cards came out about - * 30dp narrower than iOS's on the same screen. + * A fraction of the transcript's full width rather than of the inset row, which is how iOS sizes + * its bubbles, link cards, and group head card (`ChatViewController.maxBubbleWidthFraction`). Taken + * of the inset row, they came out about 30dp narrower than iOS's on the same screen. */ -fun transcriptCardWidth(rowWidth: Dp, rowInset: Dp): Dp = +fun transcriptBubbleMaxWidth(rowWidth: Dp, rowInset: Dp): Dp = (rowWidth + rowInset * 2) * BUBBLE_MAX_WIDTH_FRACTION +/** + * The widest a bubble carrying [content] grows in a row [rowWidth] wide, inset by [rowInset] on each + * side. A system notice spans the row, and a cash card keeps its own narrower fraction of the inset + * row (iOS's `ChatViewController.cashCardWidthFraction` matches it); everything else takes + * [transcriptBubbleMaxWidth]. + */ +fun bubbleMaxWidth(content: MessageContent, rowWidth: Dp, rowInset: Dp): Dp = when (content) { + is MessageContent.System -> rowWidth + is MessageContent.Cash -> rowWidth * CASH_CARD_WIDTH_FRACTION + else -> transcriptBubbleMaxWidth(rowWidth = rowWidth, rowInset = rowInset) +} + private val EDITED_MARKER_GAP = 6.dp -private const val CASH_BUBBLE_MAX_WIDTH_FRACTION = 0.64f /** * @param onLongClick what a long-press on this bubble reports, or `null` where the row behind it @@ -118,18 +131,11 @@ fun ContentBubble( val actionHandler = LocalChatActionHandler.current val jumbo = remember(item) { item.rendersBareEmoji() } BoxWithConstraints(modifier = Modifier.fillMaxWidth()) { - val isCardRow = item.linkCard != null && item.part == MessagePart.Card - val bubbleMaxWidth = if (isCardRow) { - transcriptCardWidth(rowWidth = maxWidth, rowInset = CodeTheme.dimens.inset) - } else when (item.content) { - is MessageContent.Text -> maxWidth * BUBBLE_MAX_WIDTH_FRACTION - is MessageContent.Cash -> maxWidth * CASH_BUBBLE_MAX_WIDTH_FRACTION - is MessageContent.Deleted -> maxWidth * BUBBLE_MAX_WIDTH_FRACTION - is MessageContent.Media -> maxWidth * CASH_BUBBLE_MAX_WIDTH_FRACTION - is MessageContent.Reply -> maxWidth * BUBBLE_MAX_WIDTH_FRACTION - is MessageContent.System -> maxWidth - is MessageContent.Encrypted -> maxWidth * BUBBLE_MAX_WIDTH_FRACTION - } + val bubbleMaxWidth = bubbleMaxWidth( + content = item.content, + rowWidth = maxWidth, + rowInset = CodeTheme.dimens.inset, + ) // A split message's citation goes on its first row and its marker on its last, so the // pieces read as one message stacked rather than as three.