From 3d1b930edec03180704bd5e32fe4485ba5975796 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Fri, 27 Oct 2023 16:24:40 +1100 Subject: [PATCH] feat: update disappearing messages ui for visible messages --- .../DisappearingMessageTimerView.swift | 21 ++--- .../Message Cells/VisibleMessageCell.swift | 90 ++++++++++--------- .../Database/Models/RecipientState.swift | 2 +- 3 files changed, 59 insertions(+), 54 deletions(-) diff --git a/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift b/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift index db9b3f73c..60a583317 100644 --- a/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift +++ b/Session/Conversations/Message Cells/Content Views/DisappearingMessageTimerView.swift @@ -4,20 +4,15 @@ import UIKit import SessionSnodeKit class DisappearingMessageTimerView: UIView { - private var initialDurationSeconds: Int32 = 0 - private var expirationTimestampMs: Int64 = 0 + private var initialDurationSeconds: Double = 0 + private var expirationTimestampMs: Double = 0 // MARK: - Animation private var animationTimer: Timer? private var progress: Int = 12 // 0 == about to expire, 12 == just started countdown. // MARK: - UI - private var iconImageView: UIImageView = { - let result: UIImageView = UIImageView() - result.set(.width, to: 9) - result.set(.height, to: 9) - return result - }() + private var iconImageView = UIImageView() // MARK: - Lifecycle @@ -25,7 +20,7 @@ class DisappearingMessageTimerView: UIView { super.init(frame: CGRect.zero) self.addSubview(iconImageView) - iconImageView.pin(to: self) + iconImageView.pin(to: self, withInset: 1) } override init(frame: CGRect) { @@ -36,7 +31,7 @@ class DisappearingMessageTimerView: UIView { preconditionFailure("Use init(viewItem:textColor:) instead.") } - public func configure(expirationTimestampMs: Int64, initialDurationSeconds: Int32) { + public func configure(expirationTimestampMs: Double, initialDurationSeconds: Double) { self.expirationTimestampMs = expirationTimestampMs self.initialDurationSeconds = initialDurationSeconds @@ -50,9 +45,9 @@ class DisappearingMessageTimerView: UIView { return } - let timestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs() - let secondsLeft: Double = max(Double(self.expirationTimestampMs - timestampMs) / 1000, 0) - let progressRatio: Double = self.initialDurationSeconds > 0 ? secondsLeft / Double(self.initialDurationSeconds) : 0 + let timestampMs: Double = Double(SnodeAPI.currentOffsetTimestampMs()) + let secondsLeft: Double = max((self.expirationTimestampMs - timestampMs) / 1000, 0) + let progressRatio: Double = self.initialDurationSeconds > 0 ? secondsLeft / self.initialDurationSeconds : 0 self.progress = Int(round(min(progressRatio, 1) * 12)) self.updateIcon() diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 6103c9804..269614a56 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -36,8 +36,10 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { private lazy var underBubbleStackViewOutgoingTrailingConstraint: NSLayoutConstraint = underBubbleStackView.pin(.trailing, to: .trailing, of: snContentView) private lazy var underBubbleStackViewNoHeightConstraint: NSLayoutConstraint = underBubbleStackView.set(.height, to: 0) - private lazy var timerViewOutgoingMessageConstraint = timerView.pin(.leading, to: .leading, of: self, withInset: VisibleMessageCell.contactThreadHSpacing) - private lazy var timerViewIncomingMessageConstraint = timerView.pin(.trailing, to: .trailing, of: self, withInset: -VisibleMessageCell.contactThreadHSpacing) + private lazy var timerViewOutgoingMessageConstraint = timerView.pin(.trailing, to: .trailing, of: messageStatusContainerView) + private lazy var timerViewIncomingMessageConstraint = timerView.pin(.leading, to: .leading, of: messageStatusContainerView) + private lazy var messageStatusLabelOutgoingMessageConstraint = messageStatusLabel.pin(.trailing, to: .leading, of: timerView, withInset: -2) + private lazy var messageStatusLabelIncomingMessageConstraint = messageStatusLabel.pin(.leading, to: .trailing, of: timerView, withInset: 2) private lazy var panGestureRecognizer: UIPanGestureRecognizer = { let result = UIPanGestureRecognizer(target: self, action: #selector(handlePan)) @@ -118,7 +120,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { return result }() - private lazy var timerView: OWSMessageTimerView = OWSMessageTimerView() + private lazy var timerView = DisappearingMessageTimerView() lazy var underBubbleStackView: UIStackView = { let result = UIStackView(arrangedSubviews: []) @@ -215,11 +217,6 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { bubbleBackgroundView.addSubview(bubbleView) bubbleView.pin(to: bubbleBackgroundView) - // Timer view - addSubview(timerView) - timerView.center(.vertical, in: snContentView) - timerViewOutgoingMessageConstraint.isActive = true - // Reply button addSubview(replyButton) replyButton.addSubview(replyIconImageView) @@ -242,6 +239,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { messageStatusContainerView.addSubview(messageStatusLabel) messageStatusContainerView.addSubview(messageStatusImageView) + messageStatusContainerView.addSubview(timerView) reactionContainerView.widthAnchor .constraint(lessThanOrEqualTo: underBubbleStackView.widthAnchor) @@ -251,9 +249,12 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { messageStatusImageView.pin(.trailing, to: .trailing, of: messageStatusContainerView) messageStatusImageView.set(.width, to: VisibleMessageCell.messageStatusImageViewSize) messageStatusImageView.set(.height, to: VisibleMessageCell.messageStatusImageViewSize) + timerView.pin(.top, to: .top, of: messageStatusContainerView) + timerView.pin(.bottom, to: .bottom, of: messageStatusContainerView) + timerView.set(.width, to: VisibleMessageCell.messageStatusImageViewSize) + timerView.set(.height, to: VisibleMessageCell.messageStatusImageViewSize) messageStatusLabel.center(.vertical, in: messageStatusContainerView) - messageStatusLabel.pin(.leading, to: .leading, of: messageStatusContainerView) - messageStatusLabel.pin(.trailing, to: .leading, of: messageStatusImageView, withInset: -2) +// messageStatusLabel.pin(.leading, to: .leading, of: messageStatusContainerView) messageStatusLabelPaddingView.pin(.leading, to: .leading, of: messageStatusContainerView) messageStatusLabelPaddingView.pin(.trailing, to: .trailing, of: messageStatusContainerView) } @@ -359,31 +360,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { let authorLabelAvailableSpace = CGSize(width: authorLabelAvailableWidth, height: .greatestFiniteMagnitude) let authorLabelSize = authorLabel.sizeThatFits(authorLabelAvailableSpace) authorLabelHeightConstraint.constant = (cellViewModel.senderName != nil ? authorLabelSize.height : 0) - - // Timer - if - let expiresStartedAtMs: Double = cellViewModel.expiresStartedAtMs, - let expiresInSeconds: TimeInterval = cellViewModel.expiresInSeconds - { - let expirationTimestampMs: Double = (expiresStartedAtMs + (expiresInSeconds * 1000)) - - timerView.configure( - withExpirationTimestamp: UInt64(floor(expirationTimestampMs)), - initialDurationSeconds: UInt32(floor(expiresInSeconds)) - ) - timerView.themeTintColor = .textPrimary - timerView.isHidden = false - } - else { - timerView.isHidden = true - } - - timerViewOutgoingMessageConstraint.isActive = (cellViewModel.variant == .standardOutgoing) - timerViewIncomingMessageConstraint.isActive = ( - cellViewModel.variant == .standardIncoming || - cellViewModel.variant == .standardIncomingDeleted - ) - + // Swipe to reply if ContextMenuVC.viewModelCanReply(cellViewModel) { addGestureRecognizer(panGestureRecognizer) @@ -424,11 +401,13 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { messageStatusLabel.accessibilityIdentifier = "Message sent status: \(statusText ?? "invalid")" messageStatusImageView.themeTintColor = tintColor messageStatusContainerView.isHidden = ( - cellViewModel.variant != .standardOutgoing || - cellViewModel.variant == .infoCall || - ( - cellViewModel.state == .sent && - !cellViewModel.isLastOutgoing + (cellViewModel.expiresInSeconds ?? 0) == 0 && ( + cellViewModel.variant != .standardOutgoing || + cellViewModel.variant == .infoCall || + ( + cellViewModel.state == .sent && + !cellViewModel.isLastOutgoing + ) ) ) messageStatusLabelPaddingView.isHidden = ( @@ -436,6 +415,37 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { cellViewModel.isLast ) + // Timer + if + let expiresStartedAtMs: Double = cellViewModel.expiresStartedAtMs, + let expiresInSeconds: TimeInterval = cellViewModel.expiresInSeconds + { + let expirationTimestampMs: Double = (expiresStartedAtMs + (expiresInSeconds * 1000)) + + timerView.configure( + expirationTimestampMs: expirationTimestampMs, + initialDurationSeconds: expiresInSeconds + ) + timerView.themeTintColor = tintColor + timerView.isHidden = false + messageStatusImageView.isHidden = true + } + else { + timerView.isHidden = true + messageStatusImageView.isHidden = false + } + + timerViewOutgoingMessageConstraint.isActive = (cellViewModel.variant == .standardOutgoing) + timerViewIncomingMessageConstraint.isActive = ( + cellViewModel.variant == .standardIncoming || + cellViewModel.variant == .standardIncomingDeleted + ) + messageStatusLabelOutgoingMessageConstraint.isActive = (cellViewModel.variant == .standardOutgoing) + messageStatusLabelIncomingMessageConstraint.isActive = ( + cellViewModel.variant == .standardIncoming || + cellViewModel.variant == .standardIncomingDeleted + ) + // Set the height of the underBubbleStackView to 0 if it has no content (need to do this // otherwise it can randomly stretch) underBubbleStackViewNoHeightConstraint.isActive = underBubbleStackView.arrangedSubviews diff --git a/SessionMessagingKit/Database/Models/RecipientState.swift b/SessionMessagingKit/Database/Models/RecipientState.swift index 74a9cba22..ab9a5ac21 100644 --- a/SessionMessagingKit/Database/Models/RecipientState.swift +++ b/SessionMessagingKit/Database/Models/RecipientState.swift @@ -71,7 +71,7 @@ public struct RecipientState: Codable, Equatable, FetchableRecord, PersistableRe } public func statusIconInfo(variant: Interaction.Variant, hasAtLeastOneReadReceipt: Bool) -> (image: UIImage?, text: String?, themeTintColor: ThemeValue) { - guard variant == .standardOutgoing else { return (nil, nil, .textPrimary) } + guard variant == .standardOutgoing else { return (nil, "MESSAGE_DELIVERY_STATUS_READ".localized(), .messageBubble_deliveryStatus) } switch (self, hasAtLeastOneReadReceipt) { case (.sending, _):