Чат: сузил bubble и улучшил пропорции media-сообщений
This commit is contained in:
@@ -211,9 +211,21 @@ extension MessageCellLayout {
|
|||||||
// Tiny floor just to prevent zero-width collapse.
|
// Tiny floor just to prevent zero-width collapse.
|
||||||
// Telegram does NOT force a large minW — short messages get tight bubbles.
|
// Telegram does NOT force a large minW — short messages get tight bubbles.
|
||||||
let minW: CGFloat = 40
|
let minW: CGFloat = 40
|
||||||
|
let mediaWidthFraction: CGFloat
|
||||||
|
let mediaAbsoluteCap: CGFloat
|
||||||
|
if config.imageCount == 1 {
|
||||||
|
mediaWidthFraction = 0.64
|
||||||
|
mediaAbsoluteCap = 288
|
||||||
|
} else if config.imageCount == 2 {
|
||||||
|
mediaWidthFraction = 0.67
|
||||||
|
mediaAbsoluteCap = 300
|
||||||
|
} else {
|
||||||
|
mediaWidthFraction = 0.7
|
||||||
|
mediaAbsoluteCap = 312
|
||||||
|
}
|
||||||
let mediaBubbleMaxWidth = min(
|
let mediaBubbleMaxWidth = min(
|
||||||
effectiveMaxBubbleWidth,
|
effectiveMaxBubbleWidth,
|
||||||
max(220, UIScreen.main.bounds.width * 0.78)
|
min(mediaAbsoluteCap, max(200, UIScreen.main.bounds.width * mediaWidthFraction))
|
||||||
)
|
)
|
||||||
|
|
||||||
var bubbleW: CGFloat
|
var bubbleW: CGFloat
|
||||||
@@ -390,14 +402,14 @@ extension MessageCellLayout {
|
|||||||
/// Photo collage height — same formulas as C++ MessageLayout & PhotoCollageView.swift.
|
/// Photo collage height — same formulas as C++ MessageLayout & PhotoCollageView.swift.
|
||||||
private static func collageHeight(count: Int, width: CGFloat) -> CGFloat {
|
private static func collageHeight(count: Int, width: CGFloat) -> CGFloat {
|
||||||
guard count > 0 else { return 0 }
|
guard count > 0 else { return 0 }
|
||||||
if count == 1 { return min(width * 0.75, 320) }
|
if count == 1 { return max(180, min(width * 0.93, 340)) }
|
||||||
if count == 2 {
|
if count == 2 {
|
||||||
let cellW = (width - 2) / 2
|
let cellW = (width - 2) / 2
|
||||||
return min(cellW * 1.2, 320)
|
return min(cellW * 1.28, 330)
|
||||||
}
|
}
|
||||||
if count == 3 {
|
if count == 3 {
|
||||||
let leftW = width * 0.66
|
let leftW = width * 0.66
|
||||||
return min(leftW * 1.1, 320)
|
return min(leftW * 1.16, 330)
|
||||||
}
|
}
|
||||||
if count == 4 {
|
if count == 4 {
|
||||||
let cellW = (width - 2) / 2
|
let cellW = (width - 2) / 2
|
||||||
|
|||||||
@@ -129,9 +129,9 @@ struct ChatDetailView: View {
|
|||||||
private var maxBubbleWidth: CGFloat {
|
private var maxBubbleWidth: CGFloat {
|
||||||
let w = UIScreen.main.bounds.width
|
let w = UIScreen.main.bounds.width
|
||||||
if w <= 500 {
|
if w <= 500 {
|
||||||
return max(240, min(w * 0.78, w - 86))
|
return max(224, min(w * 0.72, w - 104))
|
||||||
}
|
}
|
||||||
return min(w * 0.72, 520)
|
return min(w * 0.66, 460)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Visual chat content: messages list + gradient overlays + background.
|
/// Visual chat content: messages list + gradient overlays + background.
|
||||||
|
|||||||
@@ -163,7 +163,10 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
private var photoTileImageViews: [UIImageView] = []
|
private var photoTileImageViews: [UIImageView] = []
|
||||||
private var photoTilePlaceholderViews: [UIView] = []
|
private var photoTilePlaceholderViews: [UIView] = []
|
||||||
private var photoTileActivityIndicators: [UIActivityIndicatorView] = []
|
private var photoTileActivityIndicators: [UIActivityIndicatorView] = []
|
||||||
|
private var photoTileErrorViews: [UIImageView] = []
|
||||||
private var photoTileButtons: [UIButton] = []
|
private var photoTileButtons: [UIButton] = []
|
||||||
|
private let photoUploadingOverlayView = UIView()
|
||||||
|
private let photoUploadingIndicator = UIActivityIndicatorView(style: .medium)
|
||||||
private let photoOverflowOverlayView = UIView()
|
private let photoOverflowOverlayView = UIView()
|
||||||
private let photoOverflowLabel = UILabel()
|
private let photoOverflowLabel = UILabel()
|
||||||
|
|
||||||
@@ -195,6 +198,7 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
private var photoLoadTasks: [String: Task<Void, Never>] = [:]
|
private var photoLoadTasks: [String: Task<Void, Never>] = [:]
|
||||||
private var photoDownloadTasks: [String: Task<Void, Never>] = [:]
|
private var photoDownloadTasks: [String: Task<Void, Never>] = [:]
|
||||||
private var downloadingAttachmentIds: Set<String> = []
|
private var downloadingAttachmentIds: Set<String> = []
|
||||||
|
private var failedAttachmentIds: Set<String> = []
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
@@ -230,8 +234,9 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
bubbleView.addSubview(textLabel)
|
bubbleView.addSubview(textLabel)
|
||||||
|
|
||||||
// Timestamp
|
// Timestamp
|
||||||
statusBackgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.32)
|
statusBackgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.3)
|
||||||
statusBackgroundView.layer.cornerRadius = 6
|
statusBackgroundView.layer.cornerRadius = 7
|
||||||
|
statusBackgroundView.layer.cornerCurve = .continuous
|
||||||
statusBackgroundView.isHidden = true
|
statusBackgroundView.isHidden = true
|
||||||
bubbleView.addSubview(statusBackgroundView)
|
bubbleView.addSubview(statusBackgroundView)
|
||||||
|
|
||||||
@@ -281,6 +286,11 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
indicator.isHidden = true
|
indicator.isHidden = true
|
||||||
photoContainer.addSubview(indicator)
|
photoContainer.addSubview(indicator)
|
||||||
|
|
||||||
|
let errorView = UIImageView(image: Self.errorIcon)
|
||||||
|
errorView.contentMode = .center
|
||||||
|
errorView.isHidden = true
|
||||||
|
photoContainer.addSubview(errorView)
|
||||||
|
|
||||||
let button = UIButton(type: .custom)
|
let button = UIButton(type: .custom)
|
||||||
button.tag = index
|
button.tag = index
|
||||||
button.isHidden = true
|
button.isHidden = true
|
||||||
@@ -290,9 +300,20 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
photoTileImageViews.append(imageView)
|
photoTileImageViews.append(imageView)
|
||||||
photoTilePlaceholderViews.append(placeholderView)
|
photoTilePlaceholderViews.append(placeholderView)
|
||||||
photoTileActivityIndicators.append(indicator)
|
photoTileActivityIndicators.append(indicator)
|
||||||
|
photoTileErrorViews.append(errorView)
|
||||||
photoTileButtons.append(button)
|
photoTileButtons.append(button)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
photoUploadingOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.22)
|
||||||
|
photoUploadingOverlayView.isHidden = true
|
||||||
|
photoUploadingOverlayView.isUserInteractionEnabled = false
|
||||||
|
photoContainer.addSubview(photoUploadingOverlayView)
|
||||||
|
|
||||||
|
photoUploadingIndicator.color = .white
|
||||||
|
photoUploadingIndicator.hidesWhenStopped = true
|
||||||
|
photoUploadingIndicator.isHidden = true
|
||||||
|
photoContainer.addSubview(photoUploadingIndicator)
|
||||||
|
|
||||||
photoOverflowOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.45)
|
photoOverflowOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.45)
|
||||||
photoOverflowOverlayView.layer.cornerCurve = .continuous
|
photoOverflowOverlayView.layer.cornerCurve = .continuous
|
||||||
photoOverflowOverlayView.layer.cornerRadius = 0
|
photoOverflowOverlayView.layer.cornerRadius = 0
|
||||||
@@ -516,11 +537,23 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
bubbleLayer.shadowPath = bubbleLayer.path
|
bubbleLayer.shadowPath = bubbleLayer.path
|
||||||
bubbleOutlineLayer.frame = bubbleView.bounds
|
bubbleOutlineLayer.frame = bubbleView.bounds
|
||||||
bubbleOutlineLayer.path = bubbleLayer.path
|
bubbleOutlineLayer.path = bubbleLayer.path
|
||||||
if layout.hasTail {
|
let hasPhotoContent = layout.hasPhoto
|
||||||
|
if hasPhotoContent {
|
||||||
|
bubbleLayer.shadowOpacity = 0.04
|
||||||
|
bubbleLayer.shadowRadius = 0.4
|
||||||
|
bubbleLayer.shadowOffset = CGSize(width: 0, height: 0.2)
|
||||||
|
bubbleOutlineLayer.strokeColor = UIColor.clear.cgColor
|
||||||
|
} else if layout.hasTail {
|
||||||
// Tail path is appended as a second subpath; stroking it produces
|
// Tail path is appended as a second subpath; stroking it produces
|
||||||
// a visible seam at the junction. Keep fill-only for tailed bubbles.
|
// a visible seam at the junction. Keep fill-only for tailed bubbles.
|
||||||
|
bubbleLayer.shadowOpacity = 0.12
|
||||||
|
bubbleLayer.shadowRadius = 0.6
|
||||||
|
bubbleLayer.shadowOffset = CGSize(width: 0, height: 0.4)
|
||||||
bubbleOutlineLayer.strokeColor = UIColor.clear.cgColor
|
bubbleOutlineLayer.strokeColor = UIColor.clear.cgColor
|
||||||
} else {
|
} else {
|
||||||
|
bubbleLayer.shadowOpacity = 0.12
|
||||||
|
bubbleLayer.shadowRadius = 0.6
|
||||||
|
bubbleLayer.shadowOffset = CGSize(width: 0, height: 0.4)
|
||||||
bubbleOutlineLayer.strokeColor = UIColor.black.withAlphaComponent(
|
bubbleOutlineLayer.strokeColor = UIColor.black.withAlphaComponent(
|
||||||
layout.isOutgoing ? 0.16 : 0.22
|
layout.isOutgoing ? 0.16 : 0.22
|
||||||
).cgColor
|
).cgColor
|
||||||
@@ -732,6 +765,7 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
task.cancel()
|
task.cancel()
|
||||||
photoDownloadTasks.removeValue(forKey: attachmentId)
|
photoDownloadTasks.removeValue(forKey: attachmentId)
|
||||||
downloadingAttachmentIds.remove(attachmentId)
|
downloadingAttachmentIds.remove(attachmentId)
|
||||||
|
failedAttachmentIds.remove(attachmentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
for index in 0..<photoTileImageViews.count {
|
for index in 0..<photoTileImageViews.count {
|
||||||
@@ -739,6 +773,7 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
let imageView = photoTileImageViews[index]
|
let imageView = photoTileImageViews[index]
|
||||||
let placeholderView = photoTilePlaceholderViews[index]
|
let placeholderView = photoTilePlaceholderViews[index]
|
||||||
let indicator = photoTileActivityIndicators[index]
|
let indicator = photoTileActivityIndicators[index]
|
||||||
|
let errorView = photoTileErrorViews[index]
|
||||||
let button = photoTileButtons[index]
|
let button = photoTileButtons[index]
|
||||||
|
|
||||||
button.isHidden = !isActiveTile
|
button.isHidden = !isActiveTile
|
||||||
@@ -749,29 +784,42 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
placeholderView.isHidden = true
|
placeholderView.isHidden = true
|
||||||
indicator.stopAnimating()
|
indicator.stopAnimating()
|
||||||
indicator.isHidden = true
|
indicator.isHidden = true
|
||||||
|
errorView.isHidden = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let attachment = photoAttachments[index]
|
let attachment = photoAttachments[index]
|
||||||
if let cached = AttachmentCache.shared.loadImage(forAttachmentId: attachment.id) {
|
if let cached = AttachmentCache.shared.loadImage(forAttachmentId: attachment.id) {
|
||||||
|
failedAttachmentIds.remove(attachment.id)
|
||||||
setPhotoTileImage(cached, at: index, animated: false)
|
setPhotoTileImage(cached, at: index, animated: false)
|
||||||
placeholderView.isHidden = true
|
placeholderView.isHidden = true
|
||||||
indicator.stopAnimating()
|
indicator.stopAnimating()
|
||||||
indicator.isHidden = true
|
indicator.isHidden = true
|
||||||
|
errorView.isHidden = true
|
||||||
} else {
|
} else {
|
||||||
setPhotoTileImage(Self.blurHashImage(from: attachment.preview), at: index, animated: false)
|
setPhotoTileImage(Self.blurHashImage(from: attachment.preview), at: index, animated: false)
|
||||||
placeholderView.isHidden = imageView.image != nil
|
placeholderView.isHidden = imageView.image != nil
|
||||||
if downloadingAttachmentIds.contains(attachment.id) {
|
let hasFailed = failedAttachmentIds.contains(attachment.id)
|
||||||
|
if hasFailed {
|
||||||
|
indicator.stopAnimating()
|
||||||
|
indicator.isHidden = true
|
||||||
|
errorView.isHidden = false
|
||||||
|
} else if downloadingAttachmentIds.contains(attachment.id) {
|
||||||
indicator.startAnimating()
|
indicator.startAnimating()
|
||||||
indicator.isHidden = false
|
indicator.isHidden = false
|
||||||
|
errorView.isHidden = true
|
||||||
} else {
|
} else {
|
||||||
indicator.stopAnimating()
|
indicator.stopAnimating()
|
||||||
indicator.isHidden = true
|
indicator.isHidden = true
|
||||||
|
errorView.isHidden = true
|
||||||
}
|
}
|
||||||
startPhotoLoadTask(attachment: attachment)
|
startPhotoLoadTask(attachment: attachment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layoutPhotoOverflowOverlay()
|
layoutPhotoOverflowOverlay()
|
||||||
|
updatePhotoUploadingOverlay(
|
||||||
|
isVisible: layout.isOutgoing && message.deliveryStatus == .waiting
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func layoutPhotoTiles() {
|
private func layoutPhotoTiles() {
|
||||||
@@ -783,7 +831,19 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
photoTilePlaceholderViews[index].frame = frame
|
photoTilePlaceholderViews[index].frame = frame
|
||||||
photoTileButtons[index].frame = frame
|
photoTileButtons[index].frame = frame
|
||||||
photoTileActivityIndicators[index].center = CGPoint(x: frame.midX, y: frame.midY)
|
photoTileActivityIndicators[index].center = CGPoint(x: frame.midX, y: frame.midY)
|
||||||
|
photoTileErrorViews[index].frame = CGRect(
|
||||||
|
x: frame.midX - 10, y: frame.midY - 10,
|
||||||
|
width: 20, height: 20
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
photoUploadingOverlayView.frame = photoContainer.bounds
|
||||||
|
photoUploadingIndicator.center = CGPoint(
|
||||||
|
x: photoContainer.bounds.midX,
|
||||||
|
y: photoContainer.bounds.midY
|
||||||
|
)
|
||||||
|
photoContainer.bringSubviewToFront(photoUploadingOverlayView)
|
||||||
|
photoContainer.bringSubviewToFront(photoUploadingIndicator)
|
||||||
|
photoContainer.bringSubviewToFront(photoOverflowOverlayView)
|
||||||
layoutPhotoOverflowOverlay(frames: frames)
|
layoutPhotoOverflowOverlay(frames: frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -795,14 +855,21 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
|
|
||||||
let inset: CGFloat = 2
|
let inset: CGFloat = 2
|
||||||
let r: CGFloat = max(16 - inset, 0)
|
let r: CGFloat = max(16 - inset, 0)
|
||||||
let s: CGFloat = max(8 - inset, 0)
|
let s: CGFloat = max(5 - inset, 0)
|
||||||
|
let tailJoin: CGFloat = max(10 - inset, 0)
|
||||||
let rect = photoContainer.bounds
|
let rect = photoContainer.bounds
|
||||||
let (tl, tr, bl, br): (CGFloat, CGFloat, CGFloat, CGFloat) = {
|
let (tl, tr, bl, br): (CGFloat, CGFloat, CGFloat, CGFloat) = {
|
||||||
switch layout.position {
|
switch layout.position {
|
||||||
case .single: return (r, r, r, r)
|
case .single:
|
||||||
|
return layout.isOutgoing
|
||||||
|
? (r, r, r, tailJoin)
|
||||||
|
: (r, r, tailJoin, r)
|
||||||
case .top: return layout.isOutgoing ? (r, r, r, s) : (r, r, s, r)
|
case .top: return layout.isOutgoing ? (r, r, r, s) : (r, r, s, r)
|
||||||
case .mid: return layout.isOutgoing ? (r, s, r, s) : (s, r, s, r)
|
case .mid: return layout.isOutgoing ? (r, s, r, s) : (s, r, s, r)
|
||||||
case .bottom: return layout.isOutgoing ? (r, s, r, r) : (s, r, r, r)
|
case .bottom:
|
||||||
|
return layout.isOutgoing
|
||||||
|
? (r, s, r, tailJoin)
|
||||||
|
: (s, r, tailJoin, r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -950,6 +1017,17 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
photoOverflowLabel.frame = photoOverflowOverlayView.bounds
|
photoOverflowLabel.frame = photoOverflowOverlayView.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updatePhotoUploadingOverlay(isVisible: Bool) {
|
||||||
|
photoUploadingOverlayView.isHidden = !isVisible
|
||||||
|
if isVisible {
|
||||||
|
photoUploadingIndicator.isHidden = false
|
||||||
|
photoUploadingIndicator.startAnimating()
|
||||||
|
} else {
|
||||||
|
photoUploadingIndicator.stopAnimating()
|
||||||
|
photoUploadingIndicator.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func tileIndex(for attachmentId: String) -> Int? {
|
private func tileIndex(for attachmentId: String) -> Int? {
|
||||||
photoAttachments.firstIndex(where: { $0.id == attachmentId })
|
photoAttachments.firstIndex(where: { $0.id == attachmentId })
|
||||||
}
|
}
|
||||||
@@ -972,10 +1050,12 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
let loaded else {
|
let loaded else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.failedAttachmentIds.remove(attachmentId)
|
||||||
self.setPhotoTileImage(loaded, at: tileIndex, animated: true)
|
self.setPhotoTileImage(loaded, at: tileIndex, animated: true)
|
||||||
self.photoTilePlaceholderViews[tileIndex].isHidden = true
|
self.photoTilePlaceholderViews[tileIndex].isHidden = true
|
||||||
self.photoTileActivityIndicators[tileIndex].stopAnimating()
|
self.photoTileActivityIndicators[tileIndex].stopAnimating()
|
||||||
self.photoTileActivityIndicators[tileIndex].isHidden = true
|
self.photoTileActivityIndicators[tileIndex].isHidden = true
|
||||||
|
self.photoTileErrorViews[tileIndex].isHidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -986,14 +1066,22 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
guard !tag.isEmpty,
|
guard !tag.isEmpty,
|
||||||
let storedPassword = message.attachmentPassword,
|
let storedPassword = message.attachmentPassword,
|
||||||
!storedPassword.isEmpty else {
|
!storedPassword.isEmpty else {
|
||||||
|
failedAttachmentIds.insert(attachment.id)
|
||||||
|
if let tileIndex = tileIndex(for: attachment.id), tileIndex < photoTileErrorViews.count {
|
||||||
|
photoTileActivityIndicators[tileIndex].stopAnimating()
|
||||||
|
photoTileActivityIndicators[tileIndex].isHidden = true
|
||||||
|
photoTileErrorViews[tileIndex].isHidden = false
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let attachmentId = attachment.id
|
let attachmentId = attachment.id
|
||||||
|
failedAttachmentIds.remove(attachmentId)
|
||||||
downloadingAttachmentIds.insert(attachmentId)
|
downloadingAttachmentIds.insert(attachmentId)
|
||||||
if let tileIndex = tileIndex(for: attachmentId), tileIndex < photoTileActivityIndicators.count {
|
if let tileIndex = tileIndex(for: attachmentId), tileIndex < photoTileActivityIndicators.count {
|
||||||
photoTileActivityIndicators[tileIndex].startAnimating()
|
photoTileActivityIndicators[tileIndex].startAnimating()
|
||||||
photoTileActivityIndicators[tileIndex].isHidden = false
|
photoTileActivityIndicators[tileIndex].isHidden = false
|
||||||
|
photoTileErrorViews[tileIndex].isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
photoDownloadTasks[attachmentId] = Task { [weak self] in
|
photoDownloadTasks[attachmentId] = Task { [weak self] in
|
||||||
@@ -1011,9 +1099,14 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let image {
|
if let image {
|
||||||
|
self.failedAttachmentIds.remove(attachmentId)
|
||||||
AttachmentCache.shared.saveImage(image, forAttachmentId: attachmentId)
|
AttachmentCache.shared.saveImage(image, forAttachmentId: attachmentId)
|
||||||
self.setPhotoTileImage(image, at: tileIndex, animated: true)
|
self.setPhotoTileImage(image, at: tileIndex, animated: true)
|
||||||
self.photoTilePlaceholderViews[tileIndex].isHidden = true
|
self.photoTilePlaceholderViews[tileIndex].isHidden = true
|
||||||
|
self.photoTileErrorViews[tileIndex].isHidden = true
|
||||||
|
} else {
|
||||||
|
self.failedAttachmentIds.insert(attachmentId)
|
||||||
|
self.photoTileErrorViews[tileIndex].isHidden = false
|
||||||
}
|
}
|
||||||
self.photoTileActivityIndicators[tileIndex].stopAnimating()
|
self.photoTileActivityIndicators[tileIndex].stopAnimating()
|
||||||
self.photoTileActivityIndicators[tileIndex].isHidden = true
|
self.photoTileActivityIndicators[tileIndex].isHidden = true
|
||||||
@@ -1023,12 +1116,14 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
self.photoDownloadTasks.removeValue(forKey: attachmentId)
|
self.photoDownloadTasks.removeValue(forKey: attachmentId)
|
||||||
self.downloadingAttachmentIds.remove(attachmentId)
|
self.downloadingAttachmentIds.remove(attachmentId)
|
||||||
|
self.failedAttachmentIds.insert(attachmentId)
|
||||||
guard let tileIndex = self.tileIndex(for: attachmentId),
|
guard let tileIndex = self.tileIndex(for: attachmentId),
|
||||||
tileIndex < self.photoTileActivityIndicators.count else {
|
tileIndex < self.photoTileActivityIndicators.count else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.photoTileActivityIndicators[tileIndex].stopAnimating()
|
self.photoTileActivityIndicators[tileIndex].stopAnimating()
|
||||||
self.photoTileActivityIndicators[tileIndex].isHidden = true
|
self.photoTileActivityIndicators[tileIndex].isHidden = true
|
||||||
|
self.photoTileErrorViews[tileIndex].isHidden = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1046,7 +1141,9 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
}
|
}
|
||||||
photoDownloadTasks.removeAll()
|
photoDownloadTasks.removeAll()
|
||||||
downloadingAttachmentIds.removeAll()
|
downloadingAttachmentIds.removeAll()
|
||||||
|
failedAttachmentIds.removeAll()
|
||||||
photoContainer.layer.mask = nil
|
photoContainer.layer.mask = nil
|
||||||
|
updatePhotoUploadingOverlay(isVisible: false)
|
||||||
photoOverflowOverlayView.isHidden = true
|
photoOverflowOverlayView.isHidden = true
|
||||||
photoOverflowLabel.isHidden = true
|
photoOverflowLabel.isHidden = true
|
||||||
photoOverflowLabel.text = nil
|
photoOverflowLabel.text = nil
|
||||||
@@ -1057,6 +1154,7 @@ final class NativeMessageCell: UICollectionViewCell, UIContextMenuInteractionDel
|
|||||||
photoTilePlaceholderViews[index].isHidden = true
|
photoTilePlaceholderViews[index].isHidden = true
|
||||||
photoTileActivityIndicators[index].stopAnimating()
|
photoTileActivityIndicators[index].stopAnimating()
|
||||||
photoTileActivityIndicators[index].isHidden = true
|
photoTileActivityIndicators[index].isHidden = true
|
||||||
|
photoTileErrorViews[index].isHidden = true
|
||||||
photoTileButtons[index].isHidden = true
|
photoTileButtons[index].isHidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1271,7 +1369,7 @@ extension NativeMessageCell: UIGestureRecognizerDelegate {
|
|||||||
final class BubblePathCache {
|
final class BubblePathCache {
|
||||||
static let shared = BubblePathCache()
|
static let shared = BubblePathCache()
|
||||||
|
|
||||||
private let pathVersion = 7
|
private let pathVersion = 8
|
||||||
private var cache: [String: CGPath] = [:]
|
private var cache: [String: CGPath] = [:]
|
||||||
|
|
||||||
func path(
|
func path(
|
||||||
@@ -1296,7 +1394,7 @@ final class BubblePathCache {
|
|||||||
private func makeBubblePath(
|
private func makeBubblePath(
|
||||||
in rect: CGRect, position: BubblePosition, isOutgoing: Bool, hasTail: Bool
|
in rect: CGRect, position: BubblePosition, isOutgoing: Bool, hasTail: Bool
|
||||||
) -> CGPath {
|
) -> CGPath {
|
||||||
let r: CGFloat = 16, s: CGFloat = 8, tailW: CGFloat = 6
|
let r: CGFloat = 16, s: CGFloat = 5, tailW: CGFloat = 6
|
||||||
|
|
||||||
// Body rect
|
// Body rect
|
||||||
let bodyRect: CGRect
|
let bodyRect: CGRect
|
||||||
|
|||||||
Reference in New Issue
Block a user