Reply-бар: Telegram-parity стилизация, alignment, preview-текст и cross-platform аудит

This commit is contained in:
2026-03-29 19:39:08 +05:00
parent 469f182155
commit 44a74ad327
11 changed files with 541 additions and 70 deletions

View File

@@ -1181,6 +1181,8 @@ private extension ChatDetailView {
}
if message.attachments.contains(where: { $0.type == .avatar }) { return "Avatar" }
if message.attachments.contains(where: { $0.type == .messages }) { return "Forwarded message" }
// Android/Desktop parity: show "Call" for call attachments
if message.attachments.contains(where: { $0.type == .call }) { return "Call" }
let trimmed = message.text.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty { return message.text }
if !message.attachments.isEmpty { return "Attachment" }
@@ -1206,6 +1208,8 @@ private extension ChatDetailView {
}
if message.attachments.contains(where: { $0.type == .avatar }) { return "Avatar" }
if message.attachments.contains(where: { $0.type == .messages }) { return "Forwarded message" }
// Android/Desktop parity: show "Call" for call attachments
if message.attachments.contains(where: { $0.type == .call }) { return "Call" }
// No known attachment type fall back to text
let trimmed = message.text.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmed.isEmpty { return message.text }
@@ -1217,17 +1221,17 @@ private extension ChatDetailView {
#endif
HStack(spacing: 0) {
RoundedRectangle(cornerRadius: 1.5)
RoundedRectangle(cornerRadius: 1.0)
.fill(RosettaColors.figmaBlue)
.frame(width: 3, height: 36)
.frame(width: 2, height: 36)
VStack(alignment: .leading, spacing: 2) {
HStack(spacing: 0) {
Text("Reply to ")
.font(.system(size: 14, weight: .medium))
.foregroundStyle(RosettaColors.Adaptive.textSecondary)
.foregroundStyle(.white)
Text(senderName)
.font(.system(size: 14, weight: .semibold))
.font(.system(size: 14, weight: .medium))
.foregroundStyle(RosettaColors.figmaBlue)
}
.lineLimit(1)
@@ -1247,15 +1251,15 @@ private extension ChatDetailView {
}
} label: {
Image(systemName: "xmark")
.font(.system(size: 14, weight: .medium))
.font(.system(size: 12, weight: .medium))
.foregroundStyle(RosettaColors.Adaptive.textSecondary)
.frame(width: 30, height: 30)
.frame(width: 44, height: 44)
}
}
.padding(.leading, 6)
.padding(.trailing, 4)
.padding(.top, 6)
.padding(.bottom, 4)
.padding(.leading, 8) // align blue line with text cursor (6pt textView padding + 2pt inset)
.padding(.trailing, 0)
.padding(.top, 8)
.padding(.bottom, 2) // tight gap to text input below
.transition(.move(edge: .bottom).combined(with: .opacity))
}

View File

@@ -125,15 +125,15 @@ final class ComposerView: UIView, UITextViewDelegate {
replyBar.isHidden = true
replyBlueBar.backgroundColor = UIColor(red: 0, green: 0x8B/255.0, blue: 1.0, alpha: 1)
replyBlueBar.layer.cornerRadius = 1.5
replyBlueBar.layer.cornerRadius = 1.0
replyBar.addSubview(replyBlueBar)
replyTitleLabel.font = .systemFont(ofSize: 14, weight: .medium)
replyTitleLabel.textColor = UIColor(white: 1, alpha: 0.6)
replyTitleLabel.textColor = .white
replyTitleLabel.text = "Reply to "
replyBar.addSubview(replyTitleLabel)
replySenderLabel.font = .systemFont(ofSize: 14, weight: .semibold)
replySenderLabel.font = .systemFont(ofSize: 14, weight: .medium)
replySenderLabel.textColor = UIColor(red: 0, green: 0x8B/255.0, blue: 1.0, alpha: 1)
replyBar.addSubview(replySenderLabel)
@@ -142,7 +142,7 @@ final class ComposerView: UIView, UITextViewDelegate {
replyPreviewLabel.lineBreakMode = .byTruncatingTail
replyBar.addSubview(replyPreviewLabel)
let xImage = UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 14, weight: .medium))
let xImage = UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .medium))
replyCancelButton.setImage(xImage, for: .normal)
replyCancelButton.tintColor = UIColor(white: 1, alpha: 0.6)
replyCancelButton.addTarget(self, action: #selector(replyCancelTapped), for: .touchUpInside)
@@ -281,8 +281,8 @@ final class ComposerView: UIView, UITextViewDelegate {
let w = bounds.width
guard w > 0 else { return }
// Reply bar height
let replyH: CGFloat = isReplyVisible ? 46 : 0 // 6 top + 36 bar + 4 bottom
// Reply bar height (8 top + 36 bar + 2 bottom = tight gap to text)
let replyH: CGFloat = isReplyVisible ? 46 : 0
// Text row height = textViewHeight (clamped)
let textRowH = textViewHeight
@@ -366,21 +366,22 @@ final class ComposerView: UIView, UITextViewDelegate {
private func layoutReplyBar(width: CGFloat, height: CGFloat) {
guard height > 0 else { return }
let barX: CGFloat = 0
let barY: CGFloat = 6
replyBlueBar.frame = CGRect(x: barX, y: barY, width: 3, height: 36)
// barX=5 aligns blue line with text cursor: replyX(6)+5 = 11 = textX(9)+inset(2)
let barX: CGFloat = 5
let barY: CGFloat = 8
replyBlueBar.frame = CGRect(x: barX, y: barY, width: 2, height: 36)
let labelX: CGFloat = 3 + 8
let labelX: CGFloat = barX + 2 + 8 // blue line + spacing
let titleSize = replyTitleLabel.sizeThatFits(CGSize(width: 200, height: 20))
replyTitleLabel.frame = CGRect(x: labelX, y: barY + 2, width: titleSize.width, height: 17)
let senderX = labelX + titleSize.width
let senderW = width - senderX - 34
let senderW = width - senderX - 44
replySenderLabel.frame = CGRect(x: senderX, y: barY + 2, width: max(0, senderW), height: 17)
replyPreviewLabel.frame = CGRect(x: labelX, y: barY + 19, width: width - labelX - 34, height: 17)
replyPreviewLabel.frame = CGRect(x: labelX, y: barY + 19, width: width - labelX - 44, height: 17)
replyCancelButton.frame = CGRect(x: width - 30, y: barY, width: 30, height: 36)
replyCancelButton.frame = CGRect(x: width - 44, y: (height - 44) / 2, width: 44, height: 44)
}
// MARK: - Icon Helpers

View File

@@ -592,6 +592,8 @@ struct MessageCellView: View, Equatable {
if reply.attachments.contains(where: { $0.type == AttachmentType.messages.rawValue }) { return "Forwarded message" }
if reply.attachments.contains(where: { $0.type == AttachmentType.file.rawValue }) { return "File" }
if reply.attachments.contains(where: { $0.type == AttachmentType.avatar.rawValue }) { return "Avatar" }
// Android/Desktop parity: show "Call" for call attachments in reply quote
if reply.attachments.contains(where: { $0.type == AttachmentType.call.rawValue }) { return "Call" }
return "Attachment"
}()
let accentColor = outgoing ? Color.white.opacity(0.5) : RosettaColors.figmaBlue