diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift
index b464d3da8a85b57eb76842d2782a4f34e1fa886b..860cc8250b6a3f2a7c448f05ea8ae1ba1391943a 100644
--- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift
@@ -30,6 +30,7 @@ import ActiveLabel
 import SwiftyBeaver
 
 // swiftlint:disable type_body_length
+// swiftlint:disable file_length
 class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
 
     // MARK: Properties
@@ -49,6 +50,7 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
     @IBOutlet weak var acceptButton: UIButton?
     @IBOutlet weak var cancelButton: UIButton!
     @IBOutlet weak var buttonsHeightConstraint: NSLayoutConstraint?
+    @IBOutlet weak var bubbleHeightConstraint: NSLayoutConstraint?
     @IBOutlet weak var bottomCorner: UIView!
     @IBOutlet weak var topCorner: UIView!
     @IBOutlet weak var timeLabel: UILabel!
@@ -77,12 +79,16 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
     private let _deleteMessage = BehaviorRelay<Bool>(value: false)
     var deleteMessage: Observable<Bool> { _deleteMessage.asObservable() }
 
+    private let showTimeTap = BehaviorRelay<Bool>(value: false)
+    var tappedToShowTime: Observable<Bool> { showTimeTap.asObservable() }
+    private var previousBubbleConstraint: CGFloat?
+
     private var longGestureRecognizer: UILongPressGestureRecognizer?
+    private var tapGestureRecognizer: UITapGestureRecognizer?
 
-    // MARK: prepareForReuse
+    // MARK: PrepareForReuse
 
     override func prepareForReuse() {
-        self.prepareForReuseLongGesture()
         self.setCellTimeLabelVisibility(hide: true)
 
         if self.sendingIndicator != nil {
@@ -98,6 +104,20 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
         super.prepareForReuse()
     }
 
+    private func prepareForReuseTapGesture() {
+        self.showTimeTap.accept(false)
+        self.previousBubbleConstraint = nil
+        if let tapGestureRecognizer = tapGestureRecognizer {
+            self.bubble.removeGestureRecognizer(tapGestureRecognizer)
+            self.tapGestureRecognizer = nil
+        }
+
+        if self.bubbleHeightConstraint != nil && self.bubbleHeightConstraint!.relation == .equal {
+            self.bubbleHeightConstraint?.constant = 31
+            changeRelation(constraint: self.bubbleHeightConstraint!, relation: .greaterThanOrEqual)
+        }
+    }
+
     private func prepareForReuseLongGesture() {
         self.messageId = nil
         self.isCopyable = false
@@ -108,6 +128,8 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
         }
     }
 
+    // MARK: Progress
+
     func startProgressMonitor(_ item: MessageViewModel,
                               _ conversationViewModel: ConversationViewModel) {
         if self.outgoingImageProgressUpdater != nil {
@@ -170,17 +192,58 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
         }
     }
 
+    // MARK: Configure
+
+    private func configureTapGesture() {
+        let shownByDefault = !self.timeLabel.isHidden && !showTimeTap.value
+        if !shownByDefault {
+            self.bubble.isUserInteractionEnabled = true
+            self.tapGestureRecognizer = UITapGestureRecognizer()
+            self.tapGestureRecognizer!.rx.event.bind(onNext: { [weak self] _ in self?.onTapGesture() }).disposed(by: self.disposeBag)
+            self.bubble.addGestureRecognizer(tapGestureRecognizer!)
+        }
+    }
+
+    private func changeRelation(constraint: NSLayoutConstraint, relation: NSLayoutConstraint.Relation) {
+        let new = NSLayoutConstraint(item: constraint.firstItem!, attribute: constraint.firstAttribute, relatedBy: relation,
+                                     toItem: constraint.secondItem, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant)
+        self.bubbleHeightConstraint!.isActive = false
+        new.isActive = true
+        self.layoutIfNeeded()
+        self.bubbleHeightConstraint = new
+    }
+
+    private func prepareForTapGesture() {
+        if self.bubbleHeightConstraint != nil && self.bubbleHeightConstraint!.relation != .equal {
+            self.bubbleHeightConstraint?.constant = self.bubble.frame.height
+            changeRelation(constraint: self.bubbleHeightConstraint!, relation: .equal)
+        }
+    }
+
+    private func onTapGesture() {
+        self.prepareForTapGesture()
+
+        if self.timeLabel.isHidden {
+            self.previousBubbleConstraint = self.bubbleTopConstraint.constant
+            self.bubbleTopConstraint.constant = 32
+        } else {
+            self.bubbleTopConstraint.constant = self.previousBubbleConstraint ?? 1
+        }
+        // trigger animation
+        self.showTimeTap.accept(true)
+    }
+
     private func configureLongGesture(_ messageId: Int64, _ bubblePosition: BubblePosition, _ isTransfer: Bool) {
         self.messageId = messageId
         self.isCopyable = bubblePosition != .generated && !isTransfer
 
         self.bubble.isUserInteractionEnabled = true
         longGestureRecognizer = UILongPressGestureRecognizer()
-        longGestureRecognizer!.rx.event.bind(onNext: { [weak self] _ in self?.showCopyMenu() }).disposed(by: self.disposeBag)
+        longGestureRecognizer!.rx.event.bind(onNext: { [weak self] _ in self?.onLongGesture() }).disposed(by: self.disposeBag)
         self.bubble.addGestureRecognizer(longGestureRecognizer!)
     }
 
-    private func showCopyMenu() {
+    private func onLongGesture() {
         becomeFirstResponder()
         let menu = UIMenuController.shared
         if !menu.isMenuVisible {
@@ -207,44 +270,42 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
             action == #selector(UIResponderStandardEditActions.delete)
     }
 
+    func toggleCellTimeLabelVisibility() {
+        self.setCellTimeLabelVisibility(hide: !self.timeLabel.isHidden)
+    }
+
     private func setCellTimeLabelVisibility(hide: Bool) {
         self.timeLabel.isHidden = hide
         self.leftDivider.isHidden = hide
         self.rightDivider.isHidden = hide
     }
 
-    private func formatCellTimeLabel(_ item: MessageViewModel) {
+    private func configureCellTimeLabel(_ item: MessageViewModel) {
         // hide for potentially reused cell
         self.setCellTimeLabelVisibility(hide: true)
 
-        if item.timeStringShown == nil { return }
-
         // setup the label
         self.timeLabel.text = item.timeStringShown
         self.timeLabel.textColor = UIColor.jamiMsgCellTimeText
         self.timeLabel.font = UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.medium)
 
-        // show the time
-        self.setCellTimeLabelVisibility(hide: false)
+        if item.shouldShowTimeString {
+            // show the time
+            self.setCellTimeLabelVisibility(hide: false)
+        }
     }
 
+    // bubble grouping for cell
     // swiftlint:disable cyclomatic_complexity
     func applyBubbleStyleToCell(_ items: [MessageViewModel]?, cellForRowAt indexPath: IndexPath) {
 
         guard let items = items else { return }
         let item = items[indexPath.row]
 
-        let bubbleColor: UIColor = { (bubblePosition: BubblePosition) -> UIColor in
-            if item.content.containsOnlyEmoji {
-                return UIColor.jamiMsgCellEmoji
-            } else if bubblePosition == .received {
-                return UIColor.jamiMsgCellReceived
-            } else if item.isTransfer {
-                return UIColor(hex: 0xcfebf5, alpha: 1.0)
-            } else {
-                return UIColor.jamiMsgCellSent
-            }
-        }(item.bubblePosition())
+        self.topCorner.isHidden = true
+        self.bottomCorner.isHidden = true
+        self.bubbleBottomConstraint.constant = 8
+        self.bubbleTopConstraint.constant = 8
 
         if item.isTransfer {
             self.messageLabel.enabledTypes = []
@@ -266,49 +327,44 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
             }
         }
 
-        self.topCorner.isHidden = true
-        self.topCorner.backgroundColor = bubbleColor
-        self.bottomCorner.isHidden = true
-        self.bottomCorner.backgroundColor = bubbleColor
-        self.bubbleBottomConstraint.constant = 8
-        self.bubbleTopConstraint.constant = 8
+        item.sequencing = { (item: MessageViewModel) -> MessageSequencing in
+            var adjustedSequencing = item.sequencing
 
-        var adjustedSequencing = item.sequencing
+            if item.shouldShowTimeString {
+                self.bubbleTopConstraint.constant = 32
 
-        if item.timeStringShown != nil {
-            self.bubbleTopConstraint.constant = 32
-            adjustedSequencing = indexPath.row == items.count - 1 ?
-                .singleMessage : adjustedSequencing != .singleMessage && adjustedSequencing != .lastOfSequence ?
-                    .firstOfSequence : .singleMessage
-        }
+                if indexPath.row == items.count - 1 {
+                    adjustedSequencing =  .singleMessage
+                } else if adjustedSequencing != .singleMessage && adjustedSequencing != .lastOfSequence {
+                    adjustedSequencing = .firstOfSequence
+                } else {
+                    adjustedSequencing = .singleMessage
+                }
+            }
 
-        if indexPath.row + 1 < items.count {
-            if items[indexPath.row + 1].timeStringShown != nil {
+            if indexPath.row + 1 < items.count && items[indexPath.row + 1].shouldShowTimeString {
                 switch adjustedSequencing {
-                case .firstOfSequence:
-                    adjustedSequencing = .singleMessage
-                case .middleOfSequence:
-                    adjustedSequencing = .lastOfSequence
+                case .firstOfSequence: adjustedSequencing = .singleMessage
+                case .middleOfSequence: adjustedSequencing = .lastOfSequence
                 default: break
                 }
             }
-        }
-
-        item.sequencing = adjustedSequencing
+            return adjustedSequencing
+        }(item)
 
         switch item.sequencing {
-        case .middleOfSequence:
-            self.topCorner.isHidden = item.isTransfer
+        case .firstOfSequence:
             self.bottomCorner.isHidden = item.isTransfer
             self.bubbleBottomConstraint.constant = 1
-            self.bubbleTopConstraint.constant = item.timeStringShown != nil ? 32 : 1
-        case .firstOfSequence:
+            self.bubbleTopConstraint.constant = item.shouldShowTimeString ? 32 : 8
+        case .middleOfSequence:
+            self.topCorner.isHidden = item.isTransfer
             self.bottomCorner.isHidden = item.isTransfer
             self.bubbleBottomConstraint.constant = 1
-            self.bubbleTopConstraint.constant = item.timeStringShown != nil ? 32 : 8
+            self.bubbleTopConstraint.constant = item.shouldShowTimeString ? 32 : 1
         case .lastOfSequence:
             self.topCorner.isHidden = item.isTransfer
-            self.bubbleTopConstraint.constant = item.timeStringShown != nil ? 32 : 1
+            self.bubbleTopConstraint.constant = item.shouldShowTimeString ? 32 : 1
         default: break
         }
         if item.content.containsOnlyEmoji {
@@ -318,28 +374,53 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
         }
     }
 
-    // swiftlint:disable function_body_length
-    func configureFromItem(_ conversationViewModel: ConversationViewModel,
-                           _ items: [MessageViewModel]?,
-                           cellForRowAt indexPath: IndexPath) {
+    private func configureBackgroundColor(_ containsOnlyEmoji: Bool, _ bubblePosition: BubblePosition) {
         self.backgroundColor = UIColor.clear
         self.bubbleViewMask?.backgroundColor = UIColor.jamiMsgBackground
         self.transferImageView.backgroundColor = UIColor.jamiMsgBackground
-        buttonsHeightConstraint?.priority = UILayoutPriority(rawValue: 999.0)
-        guard let item = items?[indexPath.row] else { return }
 
+        let cellBgColor: UIColor = { (containsOnlyEmoji: Bool, bubblePosition: BubblePosition) -> UIColor in
+            switch bubblePosition {
+            case .generated: return UIColor.jamiMsgCellReceived
+            case .sent: return containsOnlyEmoji ? UIColor.jamiMsgCellEmoji : UIColor.jamiMsgCellSent
+            case .received: return containsOnlyEmoji ? UIColor.jamiMsgCellEmoji : UIColor.jamiMsgCellReceived
+            }
+            // use this if is a sent transfer?
+            // return UIColor(hex: 0xcfebf5, alpha: 1.0)
+            // was previously set in that case but was overridden afterwards so useless
+        }(containsOnlyEmoji, bubblePosition)
+
+        self.topCorner?.backgroundColor = cellBgColor
+        self.bottomCorner?.backgroundColor = cellBgColor
+        self.bubble.backgroundColor = cellBgColor
+    }
+
+    func configureFromItem(_ conversationViewModel: ConversationViewModel,
+                           _ items: [MessageViewModel]?,
+                           cellForRowAt indexPath: IndexPath) {
+
+        self.buttonsHeightConstraint?.priority = UILayoutPriority(rawValue: 999.0)
         self.transferImageView.removeFromSuperview()
         self.playerView?.removeFromSuperview()
         self.composingMsg.removeFromSuperview()
         self.playerHeight.value = 0
         self.bubbleViewMask?.isHidden = true
 
+        guard let item = items?[indexPath.row] else { return }
+
         // hide/show time label
-        self.formatCellTimeLabel(item)
+        self.configureCellTimeLabel(item)
+
+        self.prepareForReuseLongGesture()
         self.configureLongGesture(item.message.messageId, item.bubblePosition(), item.isTransfer)
 
-        if item.bubblePosition() == .generated {
-            self.bubble.backgroundColor = UIColor.jamiMsgCellReceived
+        self.prepareForReuseTapGesture()
+        self.configureTapGesture()
+
+        self.configureBackgroundColor(item.content.containsOnlyEmoji, item.bubblePosition())
+
+        switch item.bubblePosition() {
+        case .generated:
             self.messageLabel.setTextWithLineSpacing(withText: item.content, withLineSpacing: 10)
             if indexPath.row == 0 {
                 self.messageLabelMarginConstraint.constant = 4
@@ -349,76 +430,12 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
                 self.bubbleTopConstraint.constant = 32
             }
             return
-        } else if item.isTransfer {
-            self.messageLabel.lineBreakMode = .byTruncatingMiddle
-            let type = item.bubblePosition()
-            self.bubble.backgroundColor = type == .received ? UIColor.jamiMsgCellReceived : UIColor(hex: 0xcfebf5, alpha: 1.0)
-            if indexPath.row == 0 {
-                self.messageLabelMarginConstraint.constant = 4
-                self.bubbleTopConstraint.constant = 36
-            } else {
-                self.messageLabelMarginConstraint.constant = -2
-                self.bubbleTopConstraint.constant = 32
-            }
-            if item.bubblePosition() == .received {
-                self.acceptButton?.tintColor = UIColor(hex: 0x00b20b, alpha: 1.0)
-                self.cancelButton.tintColor = UIColor(hex: 0xf00000, alpha: 1.0)
-                self.progressBar.tintColor = UIColor.jamiMain
-            } else if item.bubblePosition() == .sent {
-                self.cancelButton.tintColor = UIColor(hex: 0xf00000, alpha: 1.0)
-                self.progressBar.tintColor = UIColor.jamiMain.lighten(by: 0.2)
-            }
 
-            if item.shouldDisplayTransferedImage {
-                self.displayTransferedImage(message: item, conversationID: conversationViewModel.conversation.value.conversationId, accountId: conversationViewModel.conversation.value.accountId)
-            }
+        case .sent:
+            self.configureTransferCell(item, conversationViewModel)
 
-            if let player = item.getPlayer(conversationViewModel: conversationViewModel) {
-                let screenWidth = UIScreen.main.bounds.width
-                // size for audio file transfer
-                var defaultSize = CGSize(width: 250, height: 100)
-                var origin = CGPoint(x: 0, y: 0)
-                // if have video update size to keep video ratio
-                if let firstImage = player.firstFrame,
-                    let frameSize = firstImage.getNewSize(of: CGSize(width: getMaxDimensionForTransfer(), height: getMaxDimensionForTransfer())) {
-                    defaultSize = frameSize
-                    let xOriginImageSend = screenWidth - 112 - (defaultSize.width)
-                    if item.bubblePosition() == .sent {
-                        origin = CGPoint(x: xOriginImageSend, y: 0)
-                    }
-                }
-                let frame = CGRect(origin: origin, size: defaultSize)
-                let pView = PlayerView(frame: frame)
-                pView.viewModel = player
-                player.delegate = self
-                self.playerView = pView
-                self.bubbleViewMask?.isHidden = false
-                self.playerView!.layer.cornerRadius = 20
-                self.playerView!.layer.masksToBounds = true
-                buttonsHeightConstraint?.priority = UILayoutPriority(rawValue: 250.0)
-                self.bubble.addSubview(self.playerView!)
-                self.bubble.heightAnchor.constraint(equalTo: self.playerView!.heightAnchor, constant: 1).isActive = true
-            }
-        }
-
-        // bubble grouping for cell
-        self.applyBubbleStyleToCell(items, cellForRowAt: indexPath)
+            self.applyBubbleStyleToCell(items, cellForRowAt: indexPath)
 
-        // special cases where top/bottom margins should be larger
-        if indexPath.row == 0 {
-            self.messageLabelMarginConstraint.constant = 4
-            self.bubbleTopConstraint.constant = 36
-        } else if items?.count == indexPath.row + 1 {
-            self.bubbleBottomConstraint.constant = 16
-        }
-
-        if item.bubblePosition() == .sent {
-            // When the message contains only emoji
-            if item.content.containsOnlyEmoji {
-                self.bubble.backgroundColor = UIColor.jamiMsgCellEmoji
-            } else {
-                self.bubble.backgroundColor = UIColor.jamiMsgCellSent
-            }
             if item.isTransfer {
                 // outgoing transfer
             } else {
@@ -433,28 +450,80 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
                     .map { value in value == MessageStatus.failure ? false : true }
                     .bind(to: self.failedStatusLabel.rx.isHidden)
                     .disposed(by: self.disposeBag)
-                if self.messageReadIndicator != nil {
-                    configureMessageReadAvatar(item, conversationViewModel)
-                }
+
+                self.configureMessageReadAvatar(item, conversationViewModel)
             }
-        } else if item.bubblePosition() == .received {
-            // When the message contains only emoji
-            if item.content.containsOnlyEmoji {
-                self.bubble.backgroundColor = UIColor.jamiMsgCellEmoji
-                if self.avatarBotomAlignConstraint != nil {
-                    self.avatarBotomAlignConstraint.constant = -14
-                }
-            } else {
-                self.bubble.backgroundColor = UIColor.jamiMsgCellReceived
-                if self.avatarBotomAlignConstraint != nil {
-                    self.avatarBotomAlignConstraint.constant = -1
-                }
-                if item.isComposingIndicator {
-                    addComposingMsgView()
-                }
+
+        case .received:
+            self.configureTransferCell(item, conversationViewModel)
+
+            self.applyBubbleStyleToCell(items, cellForRowAt: indexPath)
+
+            if self.avatarBotomAlignConstraint != nil {
+                self.avatarBotomAlignConstraint.constant = item.content.containsOnlyEmoji ? -14 : -1
             }
 
-            configureReceivedMessageAvatar(item.sequencing, conversationViewModel)
+            if item.isComposingIndicator {
+                self.addComposingMsgView()
+            }
+
+            self.configureReceivedMessageAvatar(item.sequencing, conversationViewModel)
+        }
+
+        // special cases where top/bottom margins should be larger
+        if indexPath.row == 0 {
+            self.messageLabelMarginConstraint.constant = 4
+            self.bubbleTopConstraint.constant = 36
+        } else if items?.count == indexPath.row + 1 {
+            self.bubbleBottomConstraint.constant = 16
+        } else if item.isTransfer {
+            self.messageLabelMarginConstraint.constant = -2
+        }
+    }
+
+    private func configureTransferCell(_ item: MessageViewModel, _ conversationViewModel: ConversationViewModel) {
+        guard item.isTransfer else { return }
+
+        self.messageLabel.lineBreakMode = .byTruncatingMiddle
+
+        if item.bubblePosition() == .received {
+            self.acceptButton?.tintColor = UIColor(hex: 0x00b20b, alpha: 1.0)
+            self.cancelButton.tintColor = UIColor(hex: 0xf00000, alpha: 1.0)
+            self.progressBar.tintColor = UIColor.jamiMain
+        } else if item.bubblePosition() == .sent {
+            self.cancelButton.tintColor = UIColor(hex: 0xf00000, alpha: 1.0)
+            self.progressBar.tintColor = UIColor.jamiMain.lighten(by: 0.2)
+        }
+
+        if item.shouldDisplayTransferedImage {
+            self.displayTransferedImage(message: item, conversationID: conversationViewModel.conversation.value.conversationId, accountId: conversationViewModel.conversation.value.accountId)
+        }
+
+        if let player = item.getPlayer(conversationViewModel: conversationViewModel) {
+            let screenWidth = UIScreen.main.bounds.width
+            // size for audio file transfer
+            var defaultSize = CGSize(width: 250, height: 100)
+            var origin = CGPoint(x: 0, y: 0)
+            // if have video update size to keep video ratio
+            if let firstImage = player.firstFrame,
+                let frameSize = firstImage.getNewSize(of: CGSize(width: getMaxDimensionForTransfer(), height: getMaxDimensionForTransfer())) {
+                defaultSize = frameSize
+                let xOriginImageSend = screenWidth - 112 - (defaultSize.width)
+                if item.bubblePosition() == .sent {
+                    origin = CGPoint(x: xOriginImageSend, y: 0)
+                }
+            }
+            let frame = CGRect(origin: origin, size: defaultSize)
+            let pView = PlayerView(frame: frame)
+            pView.viewModel = player
+            player.delegate = self
+            self.playerView = pView
+            self.bubbleViewMask?.isHidden = false
+            self.playerView!.layer.cornerRadius = 20
+            self.playerView!.layer.masksToBounds = true
+            self.buttonsHeightConstraint?.priority = UILayoutPriority(rawValue: 250.0)
+            self.bubble.addSubview(self.playerView!)
+            self.bubble.heightAnchor.constraint(equalTo: self.playerView!.heightAnchor, constant: 1).isActive = true
         }
     }
 
@@ -474,7 +543,8 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
             .disposed(by: self.disposeBag)
      }
 
-    fileprivate func configureMessageReadAvatar(_ item: MessageViewModel, _ conversationViewModel: ConversationViewModel) {
+    private func configureMessageReadAvatar(_ item: MessageViewModel, _ conversationViewModel: ConversationViewModel) {
+        guard self.messageReadIndicator != nil else { return }
 
         Observable<(Data?, String, Bool)>.combineLatest(conversationViewModel.profileImageData.asObservable(),
                                                         conversationViewModel.bestName.asObservable(),
diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib
index d1866690b1d530f76dbe622545fea8a5a4f9cf8b..53ce806237f51de3ac5ea2adf78fa593c68d3ce9 100644
--- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib
+++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
     <device id="retina4_7" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -95,7 +95,7 @@
                     </label>
                 </subviews>
                 <constraints>
-                    <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl"/>
+                    <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" priority="999" constant="8" id="1QQ-bu-6Bl"/>
                     <constraint firstItem="WBd-CS-7Qv" firstAttribute="height" secondItem="kZJ-Ay-LTR" secondAttribute="height" multiplier="0.5" constant="3" id="1qI-8e-UAL"/>
                     <constraint firstItem="XcL-CH-BiH" firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" id="2d4-0F-VWg"/>
                     <constraint firstItem="WBd-CS-7Qv" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" id="4Zp-8q-rFJ"/>
@@ -110,7 +110,7 @@
                     <constraint firstItem="zuX-zz-1Qq" firstAttribute="trailing" secondItem="mhg-uK-iD9" secondAttribute="leading" constant="-16" id="aUU-d6-Dse"/>
                     <constraint firstItem="mhg-uK-iD9" firstAttribute="centerX" secondItem="H2p-sc-9uM" secondAttribute="centerX" id="gD0-yo-bga"/>
                     <constraint firstItem="Nc6-du-YKs" firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="leading" constant="-16" id="gzL-Pd-yaK"/>
-                    <constraint firstItem="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="jhd-A8-c1o"/>
+                    <constraint firstItem="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" priority="999" constant="8" id="jhd-A8-c1o"/>
                     <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="64" id="nWe-5k-Qpn"/>
                     <constraint firstItem="eza-Ni-w3g" firstAttribute="centerY" secondItem="mhg-uK-iD9" secondAttribute="centerY" id="vhB-Uv-04a"/>
                     <constraint firstItem="zuX-zz-1Qq" firstAttribute="centerY" secondItem="mhg-uK-iD9" secondAttribute="centerY" id="xFW-jt-00h"/>
@@ -125,6 +125,7 @@
                 <outlet property="bottomCorner" destination="XcL-CH-BiH" id="4gw-IC-EAM"/>
                 <outlet property="bubble" destination="kZJ-Ay-LTR" id="hdG-fG-L69"/>
                 <outlet property="bubbleBottomConstraint" destination="1QQ-bu-6Bl" id="a4F-pf-cXL"/>
+                <outlet property="bubbleHeightConstraint" destination="1Kj-UZ-gu7" id="oAb-CU-Qui"/>
                 <outlet property="bubbleTopConstraint" destination="jhd-A8-c1o" id="40k-2d-6rW"/>
                 <outlet property="leftDivider" destination="zuX-zz-1Qq" id="9Jc-cV-VTA"/>
                 <outlet property="messageLabel" destination="lyR-7c-S2k" id="hd3-pz-Pwh"/>
diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib
index 2d7e7bb35db7b5b07f20f5195a8b65664f65cb64..e4cdf3d65f0c93198c42761751ec2c0660b2f07f 100644
--- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib
+++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib
@@ -53,7 +53,7 @@
                         </subviews>
                         <color key="backgroundColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="calibratedRGB"/>
                         <constraints>
-                            <constraint firstAttribute="height" relation="greaterThanOrEqual" priority="999" constant="31" id="1Kj-UZ-gu7"/>
+                            <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="31" id="1Kj-UZ-gu7"/>
                             <constraint firstItem="lyR-7c-S2k" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" constant="14" id="8m5-sR-xnh"/>
                             <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="34" id="BZE-kP-hPK"/>
                             <constraint firstAttribute="bottom" secondItem="lyR-7c-S2k" secondAttribute="bottom" constant="7" id="gwN-uX-PWd"/>
@@ -90,7 +90,7 @@
                         <rect key="frame" x="244.66666666666666" y="13.666666666666664" width="19.999999999999972" height="20"/>
                     </activityIndicatorView>
                     <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Failed" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="P5a-HI-uhr" userLabel="Failed Status Label">
-                        <rect key="frame" x="222.33333333333334" y="13.999999999999998" width="42.333333333333343" height="19.333333333333329"/>
+                        <rect key="frame" x="222.33333333333334" y="14" width="42.333333333333343" height="19"/>
                         <fontDescription key="fontDescription" type="system" pointSize="16"/>
                         <color key="textColor" red="0.94117647058823528" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
                         <nil key="highlightedColor"/>
@@ -110,7 +110,7 @@
                     </view>
                 </subviews>
                 <constraints>
-                    <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl" userLabel="Bubble Bottom Constraint"/>
+                    <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" priority="999" constant="8" id="1QQ-bu-6Bl" userLabel="Bubble Bottom Constraint"/>
                     <constraint firstAttribute="trailing" secondItem="nk1-r0-5jJ" secondAttribute="trailing" constant="4" id="1g3-Kl-0eG"/>
                     <constraint firstItem="h8N-aw-5lV" firstAttribute="leading" secondItem="ogn-wv-fZy" secondAttribute="trailing" constant="16" id="1jW-JR-t5r"/>
                     <constraint firstItem="78h-fZ-7yf" firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="leading" constant="-8" id="4ME-jl-Uol"/>
@@ -130,7 +130,7 @@
                     <constraint firstItem="2U4-l3-KET" firstAttribute="trailing" secondItem="ogn-wv-fZy" secondAttribute="leading" constant="-16" id="Xw6-H2-byY"/>
                     <constraint firstItem="hdz-AQ-xHI" firstAttribute="height" secondItem="kZJ-Ay-LTR" secondAttribute="height" multiplier="0.5" constant="3" id="f63-Xd-RQB"/>
                     <constraint firstAttribute="trailingMargin" secondItem="h8N-aw-5lV" secondAttribute="trailing" constant="16" id="iwC-fc-F9c"/>
-                    <constraint firstItem="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="jhd-A8-c1o"/>
+                    <constraint firstItem="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" priority="999" constant="8" id="jhd-A8-c1o"/>
                     <constraint firstItem="hdz-AQ-xHI" firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" id="lSl-vu-Wkl"/>
                     <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="102" id="nWe-5k-Qpn"/>
                     <constraint firstItem="2U4-l3-KET" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="16" id="uoy-US-ksI"/>
@@ -143,8 +143,8 @@
                 <outlet property="bottomCorner" destination="hdz-AQ-xHI" id="ChE-BT-0LS"/>
                 <outlet property="bubble" destination="kZJ-Ay-LTR" id="hdG-fG-L69"/>
                 <outlet property="bubbleBottomConstraint" destination="1QQ-bu-6Bl" id="woo-UQ-wXK"/>
+                <outlet property="bubbleHeightConstraint" destination="1Kj-UZ-gu7" id="VMV-Ea-Je4"/>
                 <outlet property="bubbleTopConstraint" destination="jhd-A8-c1o" id="cll-eA-OC5"/>
-                <outlet property="buttonsHeightConstraint" destination="1Kj-UZ-gu7" id="5XS-t5-9Mb"/>
                 <outlet property="failedStatusLabel" destination="P5a-HI-uhr" id="6Sq-NU-j0d"/>
                 <outlet property="leftDivider" destination="2U4-l3-KET" id="y4j-CT-gez"/>
                 <outlet property="messageLabel" destination="lyR-7c-S2k" id="hd3-pz-Pwh"/>
diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift
index 2d75154ea9898501df6887b9f33c9e4316c202d4..fbd504194b9aff06a79baf4441eecfd791960df1 100644
--- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift
@@ -46,7 +46,8 @@ class ConversationViewController: UIViewController,
     var messageViewModels: [MessageViewModel]?
     var textFieldShouldEndEditing = false
     var bottomOffset: CGFloat = 0
-    let scrollOffsetThreshold: CGFloat = 600
+    private let scrollOffsetThreshold: CGFloat = 600
+    private let messageGroupingInterval = 10 * 60 // 10 minutes
     var bottomHeight: CGFloat = 0.00
     var isExecutingDeleteMessage: Bool = false
 
@@ -739,26 +740,23 @@ class ConversationViewController: UIViewController,
 
     // MARK: - message formatting
     private func computeSequencing() {
-        var lastShownTime: Date?
+        var lastMessageTime: Date?
         for (index, messageViewModel) in self.messageViewModels!.enumerated() {
             // time labels
-            let time = messageViewModel.receivedDate
+            let currentMessageTime = messageViewModel.receivedDate
             if index == 0 ||  messageViewModel.bubblePosition() == .generated || messageViewModel.isTransfer {
                 // always show first message's time
-                messageViewModel.timeStringShown = getTimeLabelString(forTime: time)
-                lastShownTime = time
+                messageViewModel.shouldShowTimeString = true
             } else {
-                // only show time for new messages if beyond an arbitrary time frame (1 minute)
-                // from the previously shown time
-                let hourComp = Calendar.current.compare(lastShownTime!, to: time, toGranularity: .hour)
-                let minuteComp = Calendar.current.compare(lastShownTime!, to: time, toGranularity: .minute)
-                if (hourComp == .orderedSame && minuteComp == .orderedSame) || messageViewModel.isComposingIndicator {
-                    messageViewModel.timeStringShown = nil
+                // only show time for new messages if beyond an arbitrary time frame from the previously shown time
+                let timeDifference = currentMessageTime.timeIntervalSinceReferenceDate - lastMessageTime!.timeIntervalSinceReferenceDate
+                if Int(timeDifference) < messageGroupingInterval || messageViewModel.isComposingIndicator {
+                    messageViewModel.shouldShowTimeString = false
                 } else {
-                    messageViewModel.timeStringShown = getTimeLabelString(forTime: time)
-                    lastShownTime = time
+                    messageViewModel.shouldShowTimeString = true
                 }
             }
+            lastMessageTime = currentMessageTime
             // sequencing
             messageViewModel.sequencing = getMessageSequencing(forIndex: index)
         }
@@ -805,31 +803,6 @@ class ConversationViewController: UIViewController,
         return MessageSequencing.unknown
     }
 
-    private func getTimeLabelString(forTime time: Date) -> String {
-        // get the current time
-        let currentDateTime = Date()
-
-        // prepare formatter
-        let dateFormatter = DateFormatter()
-
-        if Calendar.current.compare(currentDateTime, to: time, toGranularity: .day) == .orderedSame {
-            // age: [0, received the previous day[
-            dateFormatter.dateFormat = "h:mma"
-        } else if Calendar.current.compare(currentDateTime, to: time, toGranularity: .weekOfYear) == .orderedSame {
-            // age: [received the previous day, received 7 days ago[
-            dateFormatter.dateFormat = "E h:mma"
-        } else if Calendar.current.compare(currentDateTime, to: time, toGranularity: .year) == .orderedSame {
-            // age: [received 7 days ago, received the previous year[
-            dateFormatter.dateFormat = "MMM d, h:mma"
-        } else {
-            // age: [received the previous year, inf[
-            dateFormatter.dateFormat = "MMM d, yyyy h:mma"
-        }
-
-        // generate the string containing the message time
-        return dateFormatter.string(from: time).uppercased()
-    }
-
     // swiftlint:disable cyclomatic_complexity
     func changeTransferStatus(_ cell: MessageCell,
                               _ indexPath: IndexPath?,
@@ -963,6 +936,7 @@ extension ConversationViewController: UITableViewDataSource {
 
             transferCellSetup(item, cell, tableView, indexPath)
             deleteCellSetup(cell)
+            tapToShowTimeCellSetup(cell)
 
             return cell
         }
@@ -980,6 +954,24 @@ extension ConversationViewController: UITableViewDataSource {
             .disposed(by: cell.disposeBag)
     }
 
+    private func tapToShowTimeCellSetup(_ cell: MessageCell) {
+        cell.tappedToShowTime
+            .observeOn(MainScheduler.instance)
+            .subscribe(onNext: { [weak self, weak cell] (tappedToShowTime) in
+                guard tappedToShowTime, let self = self, let cell = cell else { return }
+
+                let hide = !(cell.timeLabel!.isHidden)
+                if hide {
+                    cell.toggleCellTimeLabelVisibility()
+                }
+
+                self.tableView.performBatchUpdates({
+                    self.tableView.updateConstraintsIfNeeded()
+                }, completion: { _ in if !hide { cell.toggleCellTimeLabelVisibility() }})
+            })
+            .disposed(by: cell.disposeBag)
+    }
+
     // swiftlint:disable cyclomatic_complexity
     private func transferCellSetup(_ item: MessageViewModel, _ cell: MessageCell, _ tableView: UITableView, _ indexPath: IndexPath) {
         if item.isTransfer {
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift b/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift
index 8d4dc23d59daca991a5a241f77b239681f8daae1..b9e2833b4ba8f7a47b18f5e423e87af6eb4acb32 100644
--- a/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift
@@ -57,7 +57,12 @@ class MessageViewModel {
     fileprivate let conversationsService: ConversationsService
     fileprivate let dataTransferService: DataTransferService
     var message: MessageModel
-    var timeStringShown: String?
+
+    var shouldShowTimeString: Bool = false
+    lazy var timeStringShown: String = { [unowned self] in
+        return MessageViewModel.getTimeLabelString(forTime: self.receivedDate)
+    }()
+
     var sequencing: MessageSequencing = .unknown
     var isComposingIndicator: Bool = false
 
@@ -72,7 +77,6 @@ class MessageViewModel {
         self.injectBug = injectionBag
         self.message = message
         self.initialTransferStatus = message.transferStatus
-        self.timeStringShown = nil
         self.status.onNext(message.status)
         self.displayReadIndicator.accept(isLastDisplayed)
 
@@ -296,4 +300,29 @@ class MessageViewModel {
                       accountID: account.id,
                       conversationID: conversationID)
     }
+
+    private static func getTimeLabelString(forTime time: Date) -> String {
+         // get the current time
+         let currentDateTime = Date()
+
+         // prepare formatter
+         let dateFormatter = DateFormatter()
+
+         if Calendar.current.compare(currentDateTime, to: time, toGranularity: .day) == .orderedSame {
+             // age: [0, received the previous day[
+             dateFormatter.dateFormat = "h:mma"
+         } else if Calendar.current.compare(currentDateTime, to: time, toGranularity: .weekOfYear) == .orderedSame {
+             // age: [received the previous day, received 7 days ago[
+             dateFormatter.dateFormat = "E h:mma"
+         } else if Calendar.current.compare(currentDateTime, to: time, toGranularity: .year) == .orderedSame {
+             // age: [received 7 days ago, received the previous year[
+             dateFormatter.dateFormat = "MMM d, h:mma"
+         } else {
+             // age: [received the previous year, inf[
+             dateFormatter.dateFormat = "MMM d, yyyy h:mma"
+         }
+
+         // generate the string containing the message time
+         return dateFormatter.string(from: time).uppercased()
+     }
 }