add bubble
This commit is contained in:
parent
90f75aba6f
commit
2e98a3e4fb
@ -251,6 +251,8 @@ struct PrivateChatView: View {
|
||||
private func messageBubble(for message: MessageItem, isCurrentUser: Bool) -> some View {
|
||||
let timeText = timestamp(for: message)
|
||||
|
||||
let bubbleColor = isCurrentUser ? Color.accentColor : Color(.secondarySystemBackground)
|
||||
|
||||
return VStack(alignment: isCurrentUser ? .trailing : .leading, spacing: 4) {
|
||||
Text(contentText(for: message))
|
||||
.font(.body)
|
||||
@ -265,8 +267,10 @@ struct PrivateChatView: View {
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 12)
|
||||
.background(isCurrentUser ? Color.accentColor : Color(.secondarySystemBackground))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
||||
.background(
|
||||
MessageBubbleShape()
|
||||
.fill(bubbleColor)
|
||||
)
|
||||
.frame(maxWidth: messageBubbleMaxWidth, alignment: isCurrentUser ? .trailing : .leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
@ -765,6 +769,109 @@ private var headerPlaceholderAvatar: some View {
|
||||
}
|
||||
|
||||
|
||||
/// Decorative bubble with two horns on top and two legs on bottom to mimic cartoon-style speech clouds.
|
||||
private struct MessageBubbleShape: Shape {
|
||||
var cornerRadius: CGFloat = 18
|
||||
var hornHeight: CGFloat = 10
|
||||
var hornWidth: CGFloat = 16
|
||||
var hornSpacing: CGFloat = 12
|
||||
var legHeight: CGFloat = 6
|
||||
var legWidth: CGFloat = 18
|
||||
var legSpacing: CGFloat = 14
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path = Path()
|
||||
guard rect.width > 0, rect.height > 0 else { return path }
|
||||
|
||||
let radius = min(cornerRadius, min(rect.width, rect.height) / 2)
|
||||
let innerTop = rect.minY + hornHeight
|
||||
let innerBottom = rect.maxY - legHeight
|
||||
let left = rect.minX
|
||||
let right = rect.maxX
|
||||
|
||||
let horizontalSpan = max(0, rect.width - 2 * radius)
|
||||
|
||||
let hornsEnabled = horizontalSpan > (2 * hornWidth + 2 * hornSpacing)
|
||||
let legsEnabled = horizontalSpan > (2 * legWidth + 2 * legSpacing)
|
||||
|
||||
let effectiveHornWidth = hornsEnabled ? min(hornWidth, horizontalSpan / 4) : 0
|
||||
let effectiveLegWidth = legsEnabled ? min(legWidth, horizontalSpan / 4) : 0
|
||||
|
||||
let effectiveHornSpacing = hornsEnabled ? min(hornSpacing, (horizontalSpan - effectiveHornWidth * 2) / 2) : 0
|
||||
let effectiveLegSpacing = legsEnabled ? min(legSpacing, (horizontalSpan - effectiveLegWidth * 2) / 2) : 0
|
||||
|
||||
let leftHornStart = left + radius + effectiveHornSpacing
|
||||
let rightHornStart = right - radius - effectiveHornSpacing - effectiveHornWidth
|
||||
|
||||
let leftLegStart = left + radius + effectiveLegSpacing
|
||||
let rightLegStart = right - radius - effectiveLegSpacing - effectiveLegWidth
|
||||
|
||||
path.move(to: CGPoint(x: left + radius, y: innerTop))
|
||||
path.addQuadCurve(to: CGPoint(x: left, y: innerTop + radius), control: CGPoint(x: left, y: innerTop))
|
||||
path.addLine(to: CGPoint(x: left, y: innerBottom - radius))
|
||||
path.addQuadCurve(to: CGPoint(x: left + radius, y: innerBottom), control: CGPoint(x: left, y: innerBottom))
|
||||
|
||||
if legsEnabled {
|
||||
path.addLine(to: CGPoint(x: leftLegStart, y: innerBottom))
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: leftLegStart + effectiveLegWidth / 2, y: innerBottom + legHeight),
|
||||
control: CGPoint(x: leftLegStart + effectiveLegWidth * 0.15, y: innerBottom + legHeight)
|
||||
)
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: leftLegStart + effectiveLegWidth, y: innerBottom),
|
||||
control: CGPoint(x: leftLegStart + effectiveLegWidth * 0.85, y: innerBottom + legHeight)
|
||||
)
|
||||
|
||||
path.addLine(to: CGPoint(x: rightLegStart, y: innerBottom))
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: rightLegStart + effectiveLegWidth / 2, y: innerBottom + legHeight),
|
||||
control: CGPoint(x: rightLegStart + effectiveLegWidth * 0.15, y: innerBottom + legHeight)
|
||||
)
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: rightLegStart + effectiveLegWidth, y: innerBottom),
|
||||
control: CGPoint(x: rightLegStart + effectiveLegWidth * 0.85, y: innerBottom + legHeight)
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: CGPoint(x: right - radius, y: innerBottom))
|
||||
path.addQuadCurve(to: CGPoint(x: right, y: innerBottom - radius), control: CGPoint(x: right, y: innerBottom))
|
||||
path.addLine(to: CGPoint(x: right, y: innerTop + radius))
|
||||
path.addQuadCurve(to: CGPoint(x: right - radius, y: innerTop), control: CGPoint(x: right, y: innerTop))
|
||||
|
||||
if hornsEnabled {
|
||||
let hornOutset = effectiveHornWidth * 0.45
|
||||
let hornTipY = rect.minY - hornHeight * 0.35
|
||||
|
||||
// Right horn leans outward to the right
|
||||
path.addLine(to: CGPoint(x: rightHornStart + effectiveHornWidth, y: innerTop))
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: rightHornStart + effectiveHornWidth + hornOutset, y: hornTipY),
|
||||
control: CGPoint(x: rightHornStart + effectiveHornWidth + hornOutset * 0.65, y: innerTop - hornHeight * 0.35)
|
||||
)
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: rightHornStart, y: innerTop),
|
||||
control: CGPoint(x: rightHornStart + hornOutset * 0.25, y: innerTop - hornHeight)
|
||||
)
|
||||
|
||||
// Move across the top between horns and draw the left horn leaning outward
|
||||
path.addLine(to: CGPoint(x: leftHornStart + effectiveHornWidth, y: innerTop))
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: leftHornStart - hornOutset, y: hornTipY),
|
||||
control: CGPoint(x: leftHornStart + effectiveHornWidth - hornOutset * 0.65, y: innerTop - hornHeight * 0.35)
|
||||
)
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: leftHornStart, y: innerTop),
|
||||
control: CGPoint(x: leftHornStart - hornOutset * 0.25, y: innerTop - hornHeight)
|
||||
)
|
||||
}
|
||||
|
||||
path.addLine(to: CGPoint(x: left + radius, y: innerTop))
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if canImport(UIKit)
|
||||
private struct LegacyMultilineTextView: UIViewRepresentable {
|
||||
@Binding var text: String
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user