Фича: reply-to-reply + подсветка сообщения при навигации по реплаю

This commit is contained in:
2026-03-31 03:07:38 +05:00
parent 6b55baacd8
commit 876e541006
2 changed files with 52 additions and 4 deletions

View File

@@ -138,6 +138,9 @@ final class NativeMessageCell: UICollectionViewCell {
private let forwardAvatarView = UIView()
private let forwardNameLabel = UILabel()
// Highlight overlay (scroll-to-message flash)
private let highlightOverlay = UIView()
// Swipe-to-reply
private let replyCircleView = UIView()
private let replyIconView = UIImageView()
@@ -404,6 +407,13 @@ final class NativeMessageCell: UICollectionViewCell {
forwardNameLabel.textColor = .white
bubbleView.addSubview(forwardNameLabel)
// Highlight overlay on top of all bubble content
highlightOverlay.backgroundColor = UIColor.white.withAlphaComponent(0.12)
highlightOverlay.isUserInteractionEnabled = false
highlightOverlay.alpha = 0
highlightOverlay.layer.cornerCurve = .continuous
bubbleView.addSubview(highlightOverlay)
// Swipe reply icon circle + Telegram-exact arrow (same vector as SwiftUI SwipeToReplyModifier)
replyCircleView.backgroundColor = UIColor.white.withAlphaComponent(0.12)
replyCircleView.layer.cornerRadius = 17 // 34pt / 2
@@ -749,6 +759,10 @@ final class NativeMessageCell: UICollectionViewCell {
outgoing: layout.isOutgoing, mergeType: layout.mergeType
)
// Highlight overlay matches bubble bounds with inner corner radius
highlightOverlay.frame = bubbleView.bounds
highlightOverlay.layer.cornerRadius = 16
// Vector shadow path (approximate shape, used only for shadow)
bubbleLayer.frame = bubbleView.bounds
let shapeRect = imageFrame
@@ -1074,9 +1088,8 @@ final class NativeMessageCell: UICollectionViewCell {
// MARK: - Swipe to Reply
@objc private func handleSwipe(_ gesture: UIPanGestureRecognizer) {
// Desktop parity: system accounts + ATTACHMENTS_NOT_ALLOWED_TO_REPLY = [AVATAR, MESSAGES]
if isSavedMessages || isSystemAccount { return }
let isReplyBlocked = message?.attachments.contains(where: { $0.type == .avatar || $0.type == .messages }) ?? false
let isReplyBlocked = message?.attachments.contains(where: { $0.type == .avatar }) ?? false
if isReplyBlocked { return }
let translation = gesture.translation(in: contentView)
@@ -1304,6 +1317,21 @@ final class NativeMessageCell: UICollectionViewCell {
}
}
// MARK: - Highlight (scroll-to-message flash)
func showHighlight() {
highlightOverlay.alpha = 0
UIView.animate(withDuration: 0.2) {
self.highlightOverlay.alpha = 1
}
}
func hideHighlight() {
UIView.animate(withDuration: 0.4) {
self.highlightOverlay.alpha = 0
}
}
@objc private func replyQuoteTapped() {
guard let replyMessageId, let actions else { return }
actions.onScrollToMessage(replyMessageId)
@@ -2052,6 +2080,7 @@ final class NativeMessageCell: UICollectionViewCell {
resetPhotoTiles()
replyContainer.isHidden = true
replyMessageId = nil
highlightOverlay.alpha = 0
fileContainer.isHidden = true
callArrowView.isHidden = true
callBackButton.isHidden = true

View File

@@ -1028,6 +1028,20 @@ final class NativeMessageListController: UIViewController {
collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: animated)
}
/// Telegram-style highlight flash on a message cell.
func animateHighlight(messageId: String?) {
// Hide any previously highlighted cell
for cell in collectionView.visibleCells {
(cell as? NativeMessageCell)?.hideHighlight()
}
guard let messageId,
let snapshot = dataSource?.snapshot(),
let itemIndex = snapshot.indexOfItem(messageId) else { return }
let indexPath = IndexPath(item: itemIndex, section: 0)
guard let cell = collectionView.cellForItem(at: indexPath) as? NativeMessageCell else { return }
cell.showHighlight()
}
/// Reconfigure visible cells without rebuilding the snapshot.
func reconfigureVisibleCells() {
var snapshot = dataSource.snapshot()
@@ -1373,9 +1387,9 @@ struct NativeMessageListView: UIViewControllerRepresentable {
controller.config.maxBubbleWidth = maxBubbleWidth
configChanged = true
}
if controller.config.highlightedMessageId != highlightedMessageId {
let highlightChanged = controller.config.highlightedMessageId != highlightedMessageId
if highlightChanged {
controller.config.highlightedMessageId = highlightedMessageId
configChanged = true
}
if controller.config.firstUnreadMessageId != firstUnreadMessageId {
controller.config.firstUnreadMessageId = firstUnreadMessageId
@@ -1435,6 +1449,11 @@ struct NativeMessageListView: UIViewControllerRepresentable {
}
}
// Highlight animation (Telegram-style flash on scroll-to-message)
if highlightChanged {
controller.animateHighlight(messageId: highlightedMessageId)
}
// Empty state (iOS < 26 UIKit-managed for keyboard animation parity)
if let info = emptyChatInfo {
controller.updateEmptyState(isEmpty: messages.isEmpty, info: info)