Reply-бар: Telegram-parity стилизация, alignment, preview-текст и cross-platform аудит
This commit is contained in:
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user