From b37c71720b8c43e75e7b28602f5a087bf4825e4b Mon Sep 17 00:00:00 2001 From: wt Date: Mon, 14 Sep 2026 18:05:13 +0700 Subject: [PATCH] added bubble tails --- .../UI/Components/ChatBubbleTailShape.swift | 84 +++++++++++++++++++ .../Shared/UI/Components/MessageBubble.swift | 3 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 Sources/Shared/UI/Components/ChatBubbleTailShape.swift diff --git a/Sources/Shared/UI/Components/ChatBubbleTailShape.swift b/Sources/Shared/UI/Components/ChatBubbleTailShape.swift new file mode 100644 index 0000000..28b1a77 --- /dev/null +++ b/Sources/Shared/UI/Components/ChatBubbleTailShape.swift @@ -0,0 +1,84 @@ +import SwiftUI + +struct ChatBubbleTailShape: Shape { + let isOutgoing: Bool + func path(in rect: CGRect) -> Path { + let radius: CGFloat = 18 + let tailWidth: CGFloat = 8 + let tailHeight: CGFloat = 7 + var path = Path() + if isOutgoing { + // Top-left + path.move(to: CGPoint(x: rect.minX + radius, y: rect.minY)) + // Top edge + path.addLine(to: CGPoint(x: rect.maxX - radius, y: rect.minY)) + // Top-right corner + path.addQuadCurve( + to: CGPoint(x: rect.maxX, y: rect.minY + radius), + control: CGPoint(x: rect.maxX, y: rect.minY)) + // Right edge + path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - radius)) + // Bottom-right corner + path.addQuadCurve( + to: CGPoint(x: rect.maxX - radius, y: rect.maxY), + control: CGPoint(x: rect.maxX, y: rect.maxY)) + // Bottom edge before tail + path.addLine(to: CGPoint(x: rect.maxX - tailWidth, y: rect.maxY)) + // Tail + path.addQuadCurve( + to: CGPoint(x: rect.maxX - 2, y: rect.maxY + tailHeight), + control: CGPoint(x: rect.maxX - tailWidth, y: rect.maxY + 2)) + path.addQuadCurve( + to: CGPoint(x: rect.maxX - 6, y: rect.maxY - 2), + control: CGPoint(x: rect.maxX - 4, y: rect.maxY + 1)) + // Bottom-left + path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.maxY)) + path.addQuadCurve( + to: CGPoint(x: rect.minX, y: rect.maxY - radius), + control: CGPoint(x: rect.minX, y: rect.maxY)) + // Left edge + path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + radius)) + // Top-left corner + path.addQuadCurve( + to: CGPoint(x: rect.minX + radius, y: rect.minY), + control: CGPoint(x: rect.minX, y: rect.minY)) + } else { + // Incoming — mirror of outgoing + // Top-right + path.move(to: CGPoint(x: rect.maxX - radius, y: rect.minY)) + // Top edge + path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.minY)) + // Top-left corner + path.addQuadCurve( + to: CGPoint(x: rect.minX, y: rect.minY + radius), + control: CGPoint(x: rect.minX, y: rect.minY)) + // Left edge + path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY - radius)) + // Bottom-left corner + path.addQuadCurve( + to: CGPoint(x: rect.minX + radius, y: rect.maxY), + control: CGPoint(x: rect.minX, y: rect.maxY)) + // Bottom edge before tail + path.addLine(to: CGPoint(x: rect.minX + tailWidth, y: rect.maxY)) + // Tail + path.addQuadCurve( + to: CGPoint(x: rect.minX + 2, y: rect.maxY + tailHeight), + control: CGPoint(x: rect.minX + tailWidth, y: rect.maxY + 2)) + path.addQuadCurve( + to: CGPoint(x: rect.minX + 6, y: rect.maxY - 2), + control: CGPoint(x: rect.minX + 4, y: rect.maxY + 1)) + // Bottom-right + path.addLine(to: CGPoint(x: rect.maxX - radius, y: rect.maxY)) + path.addQuadCurve( + to: CGPoint(x: rect.maxX, y: rect.maxY - radius), + control: CGPoint(x: rect.maxX, y: rect.maxY)) + // Right edge + path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + radius)) + // Top-right corner + path.addQuadCurve( + to: CGPoint(x: rect.maxX - radius, y: rect.minY), + control: CGPoint(x: rect.maxX, y: rect.minY)) + } + return path + } +} diff --git a/Sources/Shared/UI/Components/MessageBubble.swift b/Sources/Shared/UI/Components/MessageBubble.swift index c7d7d80..262403f 100644 --- a/Sources/Shared/UI/Components/MessageBubble.swift +++ b/Sources/Shared/UI/Components/MessageBubble.swift @@ -782,7 +782,8 @@ struct MessageBubble: View { } private var messageBubbleShape: RoundedRectangle { - RoundedRectangle(cornerRadius: 18, style: .continuous) + // RoundedRectangle(cornerRadius: 18, style: .continuous) + ChatBubbleTailShape(isOutgoing: message.direction == .outgoing) } private var deliveryIcon: String {