diff --git a/Ring/Ring/Extensions/UIColor+Ring.swift b/Ring/Ring/Extensions/UIColor+Ring.swift index ae88929776ac3e689bb11ffc5933efad3e10fe61..db4ec9c3e3bc075587dbb35637c593e6adfdd315 100644 --- a/Ring/Ring/Extensions/UIColor+Ring.swift +++ b/Ring/Ring/Extensions/UIColor+Ring.swift @@ -53,4 +53,9 @@ extension UIColor { blue: 48.0/255.0, alpha: 1.0) + static let ringMsgCellTimeText = UIColor(colorLiteralRed: 128.0/255.0, + green: 128.0/255.0, + blue: 128.0/255.0, + alpha: 1.0) + } diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift index fa63c030205aee2ec2ea106338db4e94c9bf6595..8eea0f533594c28b4ad8ae5454e95e12bc5d0021 100644 --- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift +++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift @@ -30,4 +30,7 @@ class MessageCell: UITableViewCell, NibReusable { @IBOutlet weak var messageLabel: UILabel! @IBOutlet weak var bottomCorner: UIView! @IBOutlet weak var topCorner: UIView! + @IBOutlet weak var timeLabel: UILabel! + @IBOutlet weak var leftDivider: UIView! + @IBOutlet weak var rightDivider: UIView! } diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib index b381a8be6edd0eb6f85ef866c665e5b23b13e42e..63530ba25131ad89c38cb6aa02939d0a9258a9f2 100644 --- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib +++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib @@ -6,6 +6,7 @@ <dependencies> <deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> + <capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <objects> @@ -59,6 +60,27 @@ </userDefinedRuntimeAttribute> </userDefinedRuntimeAttributes> </view> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zuX-zz-1Qq" userLabel="Left Divider"> + <rect key="frame" x="24" y="16" width="138" height="1"/> + <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <constraints> + <constraint firstAttribute="height" constant="1" id="OBa-HX-Vts"/> + </constraints> + </view> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eza-Ni-w3g" userLabel="Right Divider"> + <rect key="frame" x="348.5" y="16" width="137.5" height="1"/> + <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <constraints> + <constraint firstAttribute="height" constant="1" id="7z5-Mi-Abp"/> + </constraints> + </view> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="11/14/2016 12:34PM" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mhg-uK-iD9" userLabel="Message Time"> + <rect key="frame" x="178" y="6" width="154.5" height="21"/> + <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <nil key="textColor"/> + <nil key="highlightedColor"/> + </label> </subviews> <constraints> <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl"/> @@ -67,10 +89,18 @@ <constraint firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" priority="1" constant="16" id="99Y-bR-Ioq"/> <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="H2p-sc-9uM" secondAttribute="leading" priority="1" constant="64" id="Eso-cy-OYs"/> <constraint firstItem="XcL-CH-BiH" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" id="GaI-yj-QFt"/> + <constraint firstItem="mhg-uK-iD9" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="-2" id="O3h-2G-pe5"/> + <constraint firstItem="eza-Ni-w3g" firstAttribute="leading" secondItem="mhg-uK-iD9" secondAttribute="trailing" constant="16" id="Pdf-ru-PnO"/> + <constraint firstItem="zuX-zz-1Qq" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="16" id="QoG-Zs-Lv7"/> <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" constant="64" id="TCY-7X-mFs"/> + <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="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="jhd-A8-c1o"/> <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" 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"/> <constraint firstItem="WBd-CS-7Qv" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" id="yBG-sT-w2a"/> + <constraint firstAttribute="trailingMargin" secondItem="eza-Ni-w3g" secondAttribute="trailing" constant="16" id="yMp-aN-6PX"/> </constraints> </tableViewCellContentView> <connections> @@ -78,7 +108,10 @@ <outlet property="bubble" destination="kZJ-Ay-LTR" id="hdG-fG-L69"/> <outlet property="bubbleBottomConstraint" destination="1QQ-bu-6Bl" id="a4F-pf-cXL"/> <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"/> + <outlet property="rightDivider" destination="eza-Ni-w3g" id="Jfy-s9-5t7"/> + <outlet property="timeLabel" destination="mhg-uK-iD9" id="x8D-vK-F6G"/> <outlet property="topCorner" destination="WBd-CS-7Qv" id="GCm-Hv-5Ei"/> </connections> <point key="canvasLocation" x="-411" y="-132.5"/> diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib index 41e65e646798012f611666493a47500e7318eed3..4b7eda38ec91e96d643fa9a6c1c618a4e5048a95 100644 --- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib +++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib @@ -6,6 +6,7 @@ <dependencies> <deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> + <capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <objects> @@ -59,16 +60,44 @@ </userDefinedRuntimeAttribute> </userDefinedRuntimeAttributes> </view> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="h8N-aw-5lV" userLabel="Right Divider"> + <rect key="frame" x="347.5" y="16" width="138.5" height="1"/> + <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <constraints> + <constraint firstAttribute="height" constant="1" id="3h1-r8-Nis"/> + </constraints> + </view> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2U4-l3-KET" userLabel="Left Divider"> + <rect key="frame" x="24" y="16" width="137" height="1"/> + <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <constraints> + <constraint firstAttribute="height" constant="1" id="gla-pJ-IsN"/> + </constraints> + </view> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="11/14/2016 12:34PM" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ogn-wv-fZy" userLabel="Message Time"> + <rect key="frame" x="177" y="6" width="154.5" height="21"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <nil key="textColor"/> + <nil key="highlightedColor"/> + </label> </subviews> <constraints> <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl" userLabel="Bubble Bottom Constraint"/> + <constraint firstItem="h8N-aw-5lV" firstAttribute="leading" secondItem="ogn-wv-fZy" secondAttribute="trailing" constant="16" id="1jW-JR-t5r"/> <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" priority="1" constant="64" id="99Y-bR-Ioq"/> <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" priority="1" constant="16" id="Eso-cy-OYs"/> + <constraint firstItem="ogn-wv-fZy" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="-2" id="Fxg-Wa-Rb9"/> + <constraint firstItem="2U4-l3-KET" firstAttribute="centerY" secondItem="ogn-wv-fZy" secondAttribute="centerY" id="J6Y-Ti-HDv"/> <constraint firstItem="EMh-bG-ilg" firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" id="MY3-Aj-94K"/> + <constraint firstItem="ogn-wv-fZy" firstAttribute="centerX" secondItem="H2p-sc-9uM" secondAttribute="centerX" id="RaG-SO-xFo"/> <constraint firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" constant="16" id="TCY-7X-mFs"/> + <constraint firstItem="h8N-aw-5lV" firstAttribute="centerY" secondItem="ogn-wv-fZy" secondAttribute="centerY" id="Xdu-7c-MbP"/> + <constraint firstItem="2U4-l3-KET" firstAttribute="trailing" secondItem="ogn-wv-fZy" secondAttribute="leading" constant="-16" id="Xw6-H2-byY"/> + <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="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="64" id="nWe-5k-Qpn"/> + <constraint firstItem="2U4-l3-KET" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="16" id="uoy-US-ksI"/> <constraint firstItem="EMh-bG-ilg" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" id="zEh-jv-0Ha"/> <constraint firstItem="hdz-AQ-xHI" firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" id="zWA-Jg-F6Q"/> </constraints> @@ -78,7 +107,10 @@ <outlet property="bubble" destination="kZJ-Ay-LTR" id="hdG-fG-L69"/> <outlet property="bubbleBottomConstraint" destination="1QQ-bu-6Bl" id="woo-UQ-wXK"/> <outlet property="bubbleTopConstraint" destination="jhd-A8-c1o" id="cll-eA-OC5"/> + <outlet property="leftDivider" destination="2U4-l3-KET" id="y4j-CT-gez"/> <outlet property="messageLabel" destination="lyR-7c-S2k" id="hd3-pz-Pwh"/> + <outlet property="rightDivider" destination="h8N-aw-5lV" id="9pc-93-BG6"/> + <outlet property="timeLabel" destination="ogn-wv-fZy" id="7yt-vi-cSp"/> <outlet property="topCorner" destination="EMh-bG-ilg" id="nHl-hn-BZ1"/> </connections> <point key="canvasLocation" x="-411" y="-132.5"/> diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift index c938de3edb29778ccc04479a5e44dde4f8894cf3..f84ab0b664ad2910d7a94ebf52e9a1305a44209a 100644 --- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift +++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift @@ -23,14 +23,6 @@ import RxSwift import Reusable import SwiftyBeaver -enum BubbleChaining { - case singleMessage - case firstOfSequence - case lastOfSequence - case middleOfSequence - case error -} - class ConversationViewController: UIViewController, UITextFieldDelegate, StoryboardBased, ViewModelBased { let log = SwiftyBeaver.self @@ -136,6 +128,7 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo //Bind the TableView to the ViewModel self.viewModel.messages.subscribe(onNext: { [weak self] (messageViewModels) in self?.messageViewModels = messageViewModels + self?.computeSequencing() self?.tableView.reloadData() }).disposed(by: self.disposeBag) @@ -203,62 +196,132 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo return textFieldShouldEndEditing } - func isFirstMessage(cellForRowAt indexPath: IndexPath) -> Bool { - return indexPath.row == 0 + func computeSequencing() { + var lastShownTime: Date? + for (index, messageViewModel) in self.messageViewModels!.enumerated() { + // time labels + let time = messageViewModel.receivedDate + if index == 0 { + // always show first message's time + messageViewModel.timeStringShown = getTimeLabelString(forTime: time) + lastShownTime = time + } 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.timeStringShown = nil + } else { + messageViewModel.timeStringShown = getTimeLabelString(forTime: time) + lastShownTime = time + } + } + // sequencing + messageViewModel.sequencing = getMessageSequencing(forIndex: index) + } + } + + 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: .year) == .orderedSame { + if Calendar.current.compare(currentDateTime, to: time, toGranularity: .weekOfYear) == .orderedSame { + if Calendar.current.compare(currentDateTime, to: time, toGranularity: .day) == .orderedSame { + // age: [0, received the previous day[ + dateFormatter.dateFormat = "h:mma" + } else { + // age: [received the previous day, received 7 days ago[ + dateFormatter.dateFormat = "E h:mma" + } + } else { + // age: [received 7 days ago, received the previous year[ + let day = Calendar.current.component(.day, from: time) + // apply appropriate suffix to day value + let suffix = day == 1 ? "st" : (day == 2 ? "nd" : (day == 3 ? "rd" : "th")) + dateFormatter.dateFormat = "MMM d'\(suffix),' h:mma" + } + } else { + // age: [received the previous year, inf[ + dateFormatter.dateFormat = "MMM d'th yyyy,' h:mma" + } + + // generate the string containing the message time + return dateFormatter.string(from: time).uppercased() } - func isLastMessage(cellForRowAt indexPath: IndexPath) -> Bool { - return self.messageViewModels?.count == indexPath.row + 1 + func formatTimeLabel(forCell cell: MessageCell, + withMessageVM messageVM: MessageViewModel) { + // hide for potentially reused cell + cell.timeLabel.isHidden = true + cell.leftDivider.isHidden = true + cell.rightDivider.isHidden = true + + if messageVM.timeStringShown == nil { + return + } + + // setup the label + cell.timeLabel.text = messageVM.timeStringShown + cell.timeLabel.textColor = UIColor.ringMsgCellTimeText + cell.timeLabel.font = UIFont.boldSystemFont(ofSize: 14.0) + + // show the time + cell.timeLabel.isHidden = false + cell.leftDivider.isHidden = false + cell.rightDivider.isHidden = false } - func getBubbleChaining(cellForRowAt indexPath: IndexPath) -> BubbleChaining { - if let msgViewModel = self.messageViewModels?[indexPath.row] { + func getMessageSequencing(forIndex index: Int) -> MessageSequencing { + if let msgViewModel = self.messageViewModels?[index] { let msgOwner = msgViewModel.bubblePosition() - if self.messageViewModels?.count == 1 || indexPath.row == 0 { - if self.messageViewModels?.count == indexPath.row + 1 { - return BubbleChaining.singleMessage + if self.messageViewModels?.count == 1 || index == 0 { + if self.messageViewModels?.count == index + 1 { + return MessageSequencing.singleMessage } - let nextMsgViewModel = indexPath.row + 1 <= (self.messageViewModels?.count)! - ? self.messageViewModels?[indexPath.row + 1] : nil + let nextMsgViewModel = index + 1 <= (self.messageViewModels?.count)! + ? self.messageViewModels?[index + 1] : nil if nextMsgViewModel != nil { return msgOwner != nextMsgViewModel?.bubblePosition() - ? BubbleChaining.singleMessage : BubbleChaining.firstOfSequence + ? MessageSequencing.singleMessage : MessageSequencing.firstOfSequence } - } else if self.messageViewModels?.count == indexPath.row + 1 { - let lastMsgViewModel = indexPath.row - 1 >= 0 && indexPath.row - 1 < (self.messageViewModels?.count)! - ? self.messageViewModels?[indexPath.row - 1] : nil + } else if self.messageViewModels?.count == index + 1 { + let lastMsgViewModel = index - 1 >= 0 && index - 1 < (self.messageViewModels?.count)! + ? self.messageViewModels?[index - 1] : nil if lastMsgViewModel != nil { return msgOwner != lastMsgViewModel?.bubblePosition() - ? BubbleChaining.singleMessage : BubbleChaining.lastOfSequence + ? MessageSequencing.singleMessage : MessageSequencing.lastOfSequence } } - let lastMsgViewModel = indexPath.row - 1 >= 0 && indexPath.row - 1 < (self.messageViewModels?.count)! - ? self.messageViewModels?[indexPath.row - 1] : nil - let nextMsgViewModel = indexPath.row + 1 <= (self.messageViewModels?.count)! - ? self.messageViewModels?[indexPath.row + 1] : nil - var chaining = BubbleChaining.singleMessage + let lastMsgViewModel = index - 1 >= 0 && index - 1 < (self.messageViewModels?.count)! + ? self.messageViewModels?[index - 1] : nil + let nextMsgViewModel = index + 1 <= (self.messageViewModels?.count)! + ? self.messageViewModels?[index + 1] : nil + var sequencing = MessageSequencing.singleMessage if (lastMsgViewModel != nil) && (nextMsgViewModel != nil) { if msgOwner != lastMsgViewModel?.bubblePosition() && msgOwner == nextMsgViewModel?.bubblePosition() { - chaining = BubbleChaining.firstOfSequence + sequencing = MessageSequencing.firstOfSequence } else if msgOwner != nextMsgViewModel?.bubblePosition() && msgOwner == lastMsgViewModel?.bubblePosition() { - chaining = BubbleChaining.lastOfSequence + sequencing = MessageSequencing.lastOfSequence } else if msgOwner == nextMsgViewModel?.bubblePosition() && msgOwner == lastMsgViewModel?.bubblePosition() { - chaining = BubbleChaining.middleOfSequence + sequencing = MessageSequencing.middleOfSequence } } - return chaining + return sequencing } - return BubbleChaining.error + return MessageSequencing.unknown } func applyBubbleStyleToCell(toCell cell: MessageCell, - withChaining chaining: BubbleChaining, - withContent content: String, - withType type: BubblePosition) { - + cellForRowAt indexPath: IndexPath, + withMessageVM messageVM: MessageViewModel) { + let type = messageVM.bubblePosition() let bubbleColor = type == .received ? UIColor.ringMsgCellReceived : UIColor.ringMsgCellSent - cell.messageLabel.setTextWithLineSpacing(withText: content, withLineSpacing: 2) + cell.messageLabel.setTextWithLineSpacing(withText: messageVM.content, withLineSpacing: 2) cell.topCorner.isHidden = true cell.topCorner.backgroundColor = bubbleColor @@ -267,20 +330,60 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo cell.bubbleBottomConstraint.constant = 8 cell.bubbleTopConstraint.constant = 8 - switch chaining { + var adjustedSequencing = messageVM.sequencing + + if messageVM.timeStringShown != nil { + cell.bubbleTopConstraint.constant = 32 + adjustedSequencing = indexPath.row == (self.messageViewModels?.count)! - 1 ? + .singleMessage : adjustedSequencing != .singleMessage && adjustedSequencing != .lastOfSequence ? + .firstOfSequence : .singleMessage + } + + if indexPath.row + 1 < (self.messageViewModels?.count)! { + if self.messageViewModels?[indexPath.row + 1].timeStringShown != nil { + switch adjustedSequencing { + case .firstOfSequence: + adjustedSequencing = .singleMessage + case .middleOfSequence: + adjustedSequencing = .lastOfSequence + default: break + } + } + } + + switch adjustedSequencing { case .middleOfSequence: cell.topCorner.isHidden = false cell.bottomCorner.isHidden = false cell.bubbleBottomConstraint.constant = 1 - cell.bubbleTopConstraint.constant = 1 + cell.bubbleTopConstraint.constant = messageVM.timeStringShown != nil ? 32 : 1 case .firstOfSequence: cell.bottomCorner.isHidden = false cell.bubbleBottomConstraint.constant = 1 + cell.bubbleTopConstraint.constant = messageVM.timeStringShown != nil ? 32 : 8 case .lastOfSequence: cell.topCorner.isHidden = false - cell.bubbleTopConstraint.constant = 1 + cell.bubbleTopConstraint.constant = messageVM.timeStringShown != nil ? 32 : 1 default: break } + + } + + func formatCell(withCell cell: MessageCell, + cellForRowAt indexPath: IndexPath, + withMessageVM messageVM: MessageViewModel) { + // hide/show time label + formatTimeLabel(forCell: cell, withMessageVM: messageVM) + + // bubble grouping for cell + applyBubbleStyleToCell(toCell: cell, cellForRowAt: indexPath, withMessageVM: messageVM) + + // special cases where top/bottom margins should be larger + if indexPath.row == 0 { + cell.bubbleTopConstraint.constant = 32 + } else if self.messageViewModels?.count == indexPath.row + 1 { + cell.bubbleBottomConstraint.constant = 16 + } } } @@ -291,38 +394,14 @@ extension ConversationViewController: UITableViewDataSource { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if let messageViewModel = self.messageViewModels?[indexPath.row] { - let chaining = self.getBubbleChaining(cellForRowAt: indexPath) if messageViewModel.bubblePosition() == .received { - // left side (incoming) let cell = tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellReceived.self) - - // Format cell - applyBubbleStyleToCell(toCell: cell, withChaining: chaining, withContent: messageViewModel.content, withType: .received) - - // Special cases where top/bottom margins should be larger - if isFirstMessage(cellForRowAt: indexPath) { - cell.bubbleTopConstraint.constant = 16 - } else if isLastMessage(cellForRowAt: indexPath) { - cell.bubbleBottomConstraint.constant = 16 - } - + formatCell(withCell: cell, cellForRowAt: indexPath, withMessageVM: messageViewModel) return cell } else { - // right side (outgoing) let cell = tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellSent.self) - - // Format cell - applyBubbleStyleToCell(toCell: cell, withChaining: chaining, withContent: messageViewModel.content, withType: .sent) - - // Special cases where top/bottom margins should be larger - if isFirstMessage(cellForRowAt: indexPath) { - cell.bubbleTopConstraint.constant = 16 - } else if isLastMessage(cellForRowAt: indexPath) { - cell.bubbleBottomConstraint.constant = 16 - } - + formatCell(withCell: cell, cellForRowAt: indexPath, withMessageVM: messageViewModel) return cell } } diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift b/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift index b766246ef1b25a0e8202fb56764e6135969493a3..63d091df0ed5563e66d5d9e32da820f532978c3e 100644 --- a/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift +++ b/Ring/Ring/Features/Conversations/Conversation/MessageViewModel.swift @@ -26,6 +26,14 @@ enum BubblePosition { case generated } +enum MessageSequencing { + case singleMessage + case firstOfSequence + case lastOfSequence + case middleOfSequence + case unknown +} + enum GeneratedMessageType: String { case sendContactRequest = "The invitation has been sent" case receivedContactRequest = "Contact request received" @@ -37,16 +45,32 @@ class MessageViewModel { fileprivate let accountService: AccountsService fileprivate var message: MessageModel + var timeStringShown: String? + var sequencing: MessageSequencing = .unknown + init(withInjectionBag injectionBag: InjectionBag, withMessage message: MessageModel) { self.accountService = injectionBag.accountService self.message = message + self.timeStringShown = nil } var content: String { return self.message.content } + var receivedDate: Date { + return self.message.receivedDate + } + + var id: Int64 { + return self.message.id + } + + var status: MessageStatus { + return self.message.status + } + func bubblePosition() -> BubblePosition { if self.message.isGenerated { return .generated