Merge pull request #5518 from vector-im/steve/bubbles_text_msg

Message bubbles: Create new text message cells for bubbles
This commit is contained in:
SBiOSoftWhare
2022-02-09 17:29:11 +01:00
committed by GitHub
53 changed files with 1719 additions and 459 deletions
@@ -77,7 +77,7 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType {
override var pictureView: MXKImageView! {
get {
guard let bubbleCellContentView = self.bubbleCellContentView,
bubbleCellContentView.showSenderInfo else {
bubbleCellContentView.showSenderAvatar else {
return nil
}
@@ -93,8 +93,7 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType {
override var userNameLabel: UILabel! {
get {
guard let bubbleCellContentView = self.bubbleCellContentView,
bubbleCellContentView.showSenderInfo else {
guard let bubbleCellContentView = self.bubbleCellContentView, bubbleCellContentView.showSenderName else {
return nil
}
@@ -111,7 +110,7 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType {
override var userNameTapGestureMaskView: UIView! {
get {
guard let bubbleCellContentView = self.bubbleCellContentView,
bubbleCellContentView.showSenderInfo else {
bubbleCellContentView.showSenderName else {
return nil
}
@@ -144,25 +143,7 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType {
// MARK: - Public
// MARK: - Overrides
override func setupViews() {
super.setupViews()
let showEncryptionStatus = bubbleCellContentView?.showEncryptionStatus ?? false
if showEncryptionStatus {
self.setupEncryptionStatusViewTapGestureRecognizer()
}
}
override class func defaultReuseIdentifier() -> String! {
return String(describing: self)
}
override func didEndDisplay() {
super.didEndDisplay()
func removeDecorationViews() {
if let bubbleCellReadReceiptsDisplayable = self as? BubbleCellReadReceiptsDisplayable {
bubbleCellReadReceiptsDisplayable.removeReadReceiptsView()
}
@@ -178,6 +159,36 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType {
if let timestampDisplayable = self as? TimestampDisplayable {
timestampDisplayable.removeTimestampView()
}
if let urlPreviewDisplayable = self as? RoomCellURLPreviewDisplayable {
urlPreviewDisplayable.removeURLPreviewView()
}
}
// MARK: - Overrides
override var isTextViewNeedsPositioningVerticalSpace: Bool {
return false
}
override func setupViews() {
super.setupViews()
let showEncryptionStatus = bubbleCellContentView?.showEncryptionStatus ?? false
if showEncryptionStatus {
self.setupEncryptionStatusViewTapGestureRecognizer()
}
}
override class func defaultReuseIdentifier() -> String! {
return String(describing: self)
}
override func prepareForReuse() {
super.prepareForReuse()
self.removeDecorationViews()
}
override func render(_ cellData: MXKCellData!) {
@@ -234,11 +245,28 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType {
self.bubbleCellContentView = bubbleCellContentView
}
// MARK: - RoomCellURLPreviewDisplayable
// Cannot use default implementation with ObjC protocol, if self conforms to BubbleCellReadReceiptsDisplayable method below will be used
func addURLPreviewView(_ urlPreviewView: UIView) {
self.bubbleCellContentView?.addURLPreviewView(urlPreviewView)
// tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
self.addTemporarySubview(urlPreviewView)
}
func removeURLPreviewView() {
self.bubbleCellContentView?.removeURLPreviewView()
}
// MARK: - BubbleCellReadReceiptsDisplayable
// Cannot use default implementation with ObjC protocol, if self conforms to BubbleCellReadReceiptsDisplayable method below will be used
func addReadReceiptsView(_ readReceiptsView: UIView) {
self.bubbleCellContentView?.addReadReceiptsView(readReceiptsView)
// tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
self.addTemporarySubview(readReceiptsView)
}
func removeReadReceiptsView() {
@@ -250,6 +278,9 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType {
func addReactionsView(_ reactionsView: UIView) {
self.bubbleCellContentView?.addReactionsView(reactionsView)
// tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
self.addTemporarySubview(reactionsView)
}
func removeReactionsView() {
@@ -260,6 +291,9 @@ class BaseBubbleCell: MXKRoomBubbleTableViewCell, BaseBubbleCellType {
func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView) {
self.bubbleCellContentView?.addThreadSummaryView(threadSummaryView)
// tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
self.addTemporarySubview(threadSummaryView)
}
func removeThreadSummaryView() {
@@ -29,15 +29,18 @@ final class BubbleCellContentView: UIView, NibLoadable {
@IBOutlet weak var paginationLabel: UILabel!
@IBOutlet weak var paginationSeparatorView: UIView!
@IBOutlet weak var senderInfoContainerView: UIView!
@IBOutlet weak var avatarImageView: MXKImageView!
@IBOutlet weak var userNameContainerView: UIView!
@IBOutlet weak var userNameLabel: UILabel!
@IBOutlet weak var userNameTouchMaskView: UIView!
@IBOutlet weak var avatarContainerView: UIView!
@IBOutlet weak var avatarImageView: MXKImageView!
@IBOutlet weak var innerContentView: UIView!
@IBOutlet weak var innerContentViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var innerContentViewTrailingConstraint: NSLayoutConstraint!
@IBOutlet weak var innerContentViewBottomContraint: NSLayoutConstraint!
@IBOutlet weak var encryptionStatusContainerView: UIView!
@IBOutlet weak var encryptionImageView: UIImageView!
@@ -45,18 +48,38 @@ final class BubbleCellContentView: UIView, NibLoadable {
@IBOutlet weak var bubbleInfoContainer: UIView!
@IBOutlet weak var bubbleInfoContainerTopConstraint: NSLayoutConstraint!
@IBOutlet weak var urlPreviewContainerView: UIView!
@IBOutlet weak var urlPreviewContentView: UIView!
@IBOutlet weak var urlPreviewContentViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var urlPreviewContentViewTrailingConstraint: NSLayoutConstraint!
@IBOutlet weak var readReceiptsContainerView: UIView!
@IBOutlet weak var readReceiptsContentView: UIView!
@IBOutlet weak var reactionsContainerView: UIView!
@IBOutlet weak var reactionsContentView: UIView!
@IBOutlet weak var reactionsContentViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var reactionsContentViewTrailingConstraint: NSLayoutConstraint!
@IBOutlet weak var threadSummaryContainerView: UIView!
@IBOutlet weak var threadSummaryContentView: UIView!
@IBOutlet weak var threadSummaryContentViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var threadSummaryContentViewTrailingConstraint: NSLayoutConstraint!
@IBOutlet weak var threadSummaryContentViewBottomConstraint: NSLayoutConstraint!
@IBOutlet weak var bubbleOverlayContainer: UIView!
// MARK: Private
private var showURLPreview: Bool {
get {
return !self.urlPreviewContainerView.isHidden
}
set {
self.urlPreviewContainerView.isHidden = !newValue
}
}
private var showReadReceipts: Bool {
get {
return !self.readReceiptsContainerView.isHidden
@@ -96,10 +119,29 @@ final class BubbleCellContentView: UIView, NibLoadable {
var showSenderInfo: Bool {
get {
return !self.senderInfoContainerView.isHidden
return self.showSenderAvatar && self.showSenderName
}
set {
self.senderInfoContainerView.isHidden = !newValue
self.showSenderAvatar = newValue
self.showSenderName = newValue
}
}
var showSenderAvatar: Bool {
get {
return !self.avatarContainerView.isHidden
}
set {
self.avatarContainerView.isHidden = !newValue
}
}
var showSenderName: Bool {
get {
return !self.userNameContainerView.isHidden
}
set {
self.userNameContainerView.isHidden = !newValue
}
}
@@ -112,6 +154,8 @@ final class BubbleCellContentView: UIView, NibLoadable {
}
}
var decorationViewsAlignment: RoomCellDecorationAlignment = .left
// MARK: - Setup
class func instantiate() -> BubbleCellContentView {
@@ -147,7 +191,24 @@ extension BubbleCellContentView: BubbleCellReactionsDisplayable {
func addReactionsView(_ reactionsView: UIView) {
self.reactionsContentView.vc_removeAllSubviews()
self.reactionsContentView.vc_addSubViewMatchingParent(reactionsView)
// Update reactions alignment according to current decoration alignment
if let bubbleReactionsView = reactionsView as? BubbleReactionsView {
let reactionsAlignment: BubbleReactionsViewAlignment
switch self.decorationViewsAlignment {
case .left:
reactionsAlignment = .left
case .right:
reactionsAlignment = .right
}
bubbleReactionsView.alignment = reactionsAlignment
}
self.reactionsContentView.vc_addSubViewMatchingParent(reactionsView)
self.showReactions = true
}
@@ -159,24 +220,87 @@ extension BubbleCellContentView: BubbleCellReactionsDisplayable {
// MARK: - BubbleCellThreadSummaryDisplayable
extension BubbleCellContentView: BubbleCellThreadSummaryDisplayable {
func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView) {
self.threadSummaryContainerView.vc_removeAllSubviews()
self.threadSummaryContainerView.addSubview(threadSummaryView)
guard let containerView = self.threadSummaryContentView else {
return
}
containerView.vc_removeAllSubviews()
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(threadSummaryView)
let leadingConstraint: NSLayoutConstraint
let trailingConstraint: NSLayoutConstraint
if self.decorationViewsAlignment == .right {
leadingConstraint = threadSummaryView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor)
trailingConstraint = threadSummaryView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor)
} else {
leadingConstraint = threadSummaryView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor)
trailingConstraint = threadSummaryView.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor)
}
NSLayoutConstraint.activate([
threadSummaryView.leadingAnchor.constraint(equalTo: innerContentView.leadingAnchor),
threadSummaryView.topAnchor.constraint(equalTo: threadSummaryContainerView.topAnchor),
leadingConstraint,
threadSummaryView.topAnchor.constraint(equalTo: containerView.topAnchor),
threadSummaryView.heightAnchor.constraint(equalToConstant: RoomBubbleCellLayout.threadSummaryViewHeight),
threadSummaryView.bottomAnchor.constraint(equalTo: threadSummaryContainerView.bottomAnchor),
threadSummaryView.trailingAnchor.constraint(lessThanOrEqualTo: threadSummaryContainerView.trailingAnchor,
constant: -RoomBubbleCellLayout.reactionsViewRightMargin)
threadSummaryView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
trailingConstraint
])
self.showThreadSummary = true
}
func removeThreadSummaryView() {
self.showThreadSummary = false
self.threadSummaryContainerView.vc_removeAllSubviews()
self.threadSummaryContentView.vc_removeAllSubviews()
}
}
// MARK: - RoomCellURLPreviewDisplayable
extension BubbleCellContentView: RoomCellURLPreviewDisplayable {
func addURLPreviewView(_ urlPreviewView: UIView) {
guard let containerView = self.urlPreviewContentView else {
return
}
containerView.vc_removeAllSubviews()
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(urlPreviewView)
if let urlPreviewView = urlPreviewView as? URLPreviewView {
urlPreviewView.availableWidth = containerView.frame.width
}
let leadingConstraint: NSLayoutConstraint
let trailingConstraint: NSLayoutConstraint
if self.decorationViewsAlignment == .right {
leadingConstraint = urlPreviewView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor)
trailingConstraint = urlPreviewView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor)
} else {
leadingConstraint = urlPreviewView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor)
trailingConstraint = urlPreviewView.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor)
}
NSLayoutConstraint.activate([
leadingConstraint,
urlPreviewView.topAnchor.constraint(equalTo: containerView.topAnchor),
urlPreviewView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
trailingConstraint
])
self.showURLPreview = true
}
func removeURLPreviewView() {
self.showURLPreview = false
self.urlPreviewContentView.vc_removeAllSubviews()
}
}
@@ -4,6 +4,7 @@
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,7 +18,7 @@
<rect key="frame" x="0.0" y="0.0" width="595" height="97"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="5GX-gn-bK1">
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="5GX-gn-bK1">
<rect key="frame" x="0.0" y="0.0" width="595" height="97"/>
<subviews>
<view hidden="YES" contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="u1e-Q2-PhY">
@@ -62,8 +63,8 @@
<constraint firstAttribute="bottom" secondItem="Ro1-vP-6Ha" secondAttribute="bottom" constant="10" id="UcW-P4-rQv"/>
</constraints>
</view>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="w0C-6a-f5M">
<rect key="frame" x="0.0" y="0.0" width="595" height="31"/>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1cK-e6-Mg5">
<rect key="frame" x="0.0" y="0.0" width="595" height="0.0"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleAspectFill" translatesAutoresizingMaskIntoConstraints="NO" id="yXz-Za-4yR" customClass="MXKImageView">
<rect key="frame" x="13" y="10" width="30" height="30"/>
@@ -74,6 +75,16 @@
<constraint firstAttribute="width" constant="30" id="y7F-jl-kEF"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="yXz-Za-4yR" firstAttribute="leading" secondItem="1cK-e6-Mg5" secondAttribute="leading" constant="13" id="UjU-eY-ARJ"/>
<constraint firstItem="yXz-Za-4yR" firstAttribute="top" secondItem="1cK-e6-Mg5" secondAttribute="top" constant="10" id="jlf-dg-fp0"/>
</constraints>
</view>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="w0C-6a-f5M">
<rect key="frame" x="0.0" y="0.0" width="595" height="31"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ohU-Sc-mgb">
<rect key="frame" x="46" y="4" width="534" height="30"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
@@ -94,9 +105,7 @@
<constraints>
<constraint firstAttribute="bottom" secondItem="meG-P8-61b" secondAttribute="bottom" constant="3" id="HDT-eS-UWo"/>
<constraint firstItem="ohU-Sc-mgb" firstAttribute="trailing" secondItem="meG-P8-61b" secondAttribute="trailing" id="Lbz-vD-hax"/>
<constraint firstItem="yXz-Za-4yR" firstAttribute="leading" secondItem="w0C-6a-f5M" secondAttribute="leading" constant="13" id="QlM-xG-bob"/>
<constraint firstItem="ohU-Sc-mgb" firstAttribute="bottom" secondItem="meG-P8-61b" secondAttribute="bottom" constant="6" id="U50-ZB-dRG"/>
<constraint firstItem="yXz-Za-4yR" firstAttribute="top" secondItem="w0C-6a-f5M" secondAttribute="top" constant="10" id="UMP-G3-iPN"/>
<constraint firstItem="meG-P8-61b" firstAttribute="top" secondItem="w0C-6a-f5M" secondAttribute="top" constant="10" id="baE-tR-0Ck"/>
<constraint firstItem="ohU-Sc-mgb" firstAttribute="leading" secondItem="meG-P8-61b" secondAttribute="leading" constant="-10" id="ktD-JC-hfG"/>
<constraint firstItem="ohU-Sc-mgb" firstAttribute="top" secondItem="meG-P8-61b" secondAttribute="top" constant="-6" id="s5V-Fj-iNL"/>
@@ -159,26 +168,25 @@
<constraint firstItem="oeI-eO-mFK" firstAttribute="leading" secondItem="vcq-cR-uBc" secondAttribute="leading" priority="750" id="vsh-pW-S46"/>
</constraints>
</view>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4zo-V8-CNe">
<rect key="frame" x="0.0" y="0.0" width="595" height="12"/>
<view hidden="YES" clipsSubviews="YES" contentMode="scaleAspectFit" verticalHuggingPriority="254" translatesAutoresizingMaskIntoConstraints="NO" id="57V-Sl-EmD">
<rect key="frame" x="0.0" y="0.0" width="595" height="0.0"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rQt-NH-Cb0">
<rect key="frame" x="439" y="0.0" width="150" height="12"/>
<view clipsSubviews="YES" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="vpX-Nl-AEt">
<rect key="frame" x="56" y="0.0" width="524" height="0.0"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="150" id="fsY-DK-hUg"/>
<constraint firstAttribute="height" priority="250" placeholder="YES" id="0Sv-R6-k3e"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="rQt-NH-Cb0" secondAttribute="bottom" id="6yV-vn-doj"/>
<constraint firstItem="rQt-NH-Cb0" firstAttribute="top" secondItem="4zo-V8-CNe" secondAttribute="top" id="DNc-jy-Urh"/>
<constraint firstAttribute="height" constant="12" id="TxZ-dJ-UI6"/>
<constraint firstAttribute="trailing" secondItem="rQt-NH-Cb0" secondAttribute="trailing" constant="6" id="lq2-AY-Lus"/>
<constraint firstAttribute="trailing" secondItem="vpX-Nl-AEt" secondAttribute="trailing" constant="15" id="ZmA-Ns-chh"/>
<constraint firstAttribute="bottom" secondItem="vpX-Nl-AEt" secondAttribute="bottom" id="blD-s1-b1N"/>
<constraint firstItem="vpX-Nl-AEt" firstAttribute="top" secondItem="57V-Sl-EmD" secondAttribute="top" id="sFV-XK-lE4"/>
</constraints>
</view>
<view hidden="YES" clipsSubviews="YES" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="Dj1-m6-1Jw">
<view hidden="YES" clipsSubviews="YES" contentMode="scaleAspectFit" verticalHuggingPriority="252" translatesAutoresizingMaskIntoConstraints="NO" id="Dj1-m6-1Jw">
<rect key="frame" x="0.0" y="0.0" width="595" height="0.0"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="SNw-aM-ILI">
@@ -196,17 +204,54 @@
<constraint firstAttribute="trailing" secondItem="SNw-aM-ILI" secondAttribute="trailing" constant="15" id="ynR-d4-6cf"/>
</constraints>
</view>
<view hidden="YES" contentMode="scaleToFill" verticalHuggingPriority="253" translatesAutoresizingMaskIntoConstraints="NO" id="4zo-V8-CNe">
<rect key="frame" x="0.0" y="0.0" width="595" height="16"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rQt-NH-Cb0">
<rect key="frame" x="439" y="0.0" width="150" height="16"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="WZx-xk-gav"/>
<constraint firstAttribute="width" constant="150" id="fsY-DK-hUg"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="rQt-NH-Cb0" secondAttribute="bottom" id="6yV-vn-doj"/>
<constraint firstItem="rQt-NH-Cb0" firstAttribute="top" secondItem="4zo-V8-CNe" secondAttribute="top" id="DNc-jy-Urh"/>
<constraint firstAttribute="trailing" secondItem="rQt-NH-Cb0" secondAttribute="trailing" constant="6" id="lq2-AY-Lus"/>
</constraints>
</view>
<view hidden="YES" clipsSubviews="YES" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="2eB-kB-m20">
<rect key="frame" x="0.0" y="0.0" width="595" height="0.0"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="snf-Ea-To0">
<rect key="frame" x="56" y="0.0" width="524" height="0.0"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" priority="250" placeholder="YES" id="ymW-ys-P0T"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="snf-Ea-To0" secondAttribute="bottom" id="4tt-w0-4JE"/>
<constraint firstItem="snf-Ea-To0" firstAttribute="top" secondItem="2eB-kB-m20" secondAttribute="top" id="Im9-Z0-ieI"/>
<constraint firstAttribute="trailing" secondItem="snf-Ea-To0" secondAttribute="trailing" constant="15" id="Qwm-Of-Zgc"/>
</constraints>
</view>
</subviews>
<constraints>
<constraint firstItem="Dj1-m6-1Jw" firstAttribute="width" secondItem="5GX-gn-bK1" secondAttribute="width" id="0Px-jL-CMJ"/>
<constraint firstItem="1cK-e6-Mg5" firstAttribute="width" secondItem="5GX-gn-bK1" secondAttribute="width" id="2De-xT-k5e"/>
<constraint firstItem="meG-P8-61b" firstAttribute="trailing" secondItem="oeI-eO-mFK" secondAttribute="trailing" id="2Dy-o0-r33"/>
<constraint firstItem="snf-Ea-To0" firstAttribute="leading" secondItem="oeI-eO-mFK" secondAttribute="leading" id="2lm-T3-dEu"/>
<constraint firstItem="w0C-6a-f5M" firstAttribute="width" secondItem="5GX-gn-bK1" secondAttribute="width" id="5nl-Le-VDi"/>
<constraint firstItem="u1e-Q2-PhY" firstAttribute="width" secondItem="5GX-gn-bK1" secondAttribute="width" id="KrJ-dm-TaV"/>
<constraint firstItem="57V-Sl-EmD" firstAttribute="width" secondItem="5GX-gn-bK1" secondAttribute="width" id="a2p-Bn-M5e"/>
<constraint firstItem="4zo-V8-CNe" firstAttribute="width" secondItem="5GX-gn-bK1" secondAttribute="width" id="bdq-sQ-NQy"/>
<constraint firstItem="vpX-Nl-AEt" firstAttribute="leading" secondItem="oeI-eO-mFK" secondAttribute="leading" id="lTt-Qx-fuQ"/>
<constraint firstItem="meG-P8-61b" firstAttribute="leading" secondItem="oeI-eO-mFK" secondAttribute="leading" id="lq1-xP-tea"/>
<constraint firstItem="SNw-aM-ILI" firstAttribute="leading" secondItem="oeI-eO-mFK" secondAttribute="leading" id="x1n-oT-dez"/>
</constraints>
@@ -227,6 +272,7 @@
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="avatarContainerView" destination="1cK-e6-Mg5" id="8j5-Km-Jfj"/>
<outlet property="avatarImageView" destination="yXz-Za-4yR" id="f56-93-gxa"/>
<outlet property="bubbleInfoContainer" destination="7Y6-Py-paB" id="uLv-MM-HIL"/>
<outlet property="bubbleInfoContainerTopConstraint" destination="Wx9-0o-vzm" id="nLG-nC-lwV"/>
@@ -234,6 +280,7 @@
<outlet property="encryptionImageView" destination="Ujc-3c-e5B" id="7zc-Y7-1jT"/>
<outlet property="encryptionStatusContainerView" destination="uHE-o7-sCe" id="Dl7-QS-WKl"/>
<outlet property="innerContentView" destination="oeI-eO-mFK" id="ap1-He-C6g"/>
<outlet property="innerContentViewBottomContraint" destination="8M5-uW-82s" id="Vn0-6C-LSW"/>
<outlet property="innerContentViewLeadingConstraint" destination="0Fr-0L-9tU" id="ByY-oe-d8Y"/>
<outlet property="innerContentViewTrailingConstraint" destination="Pbe-4d-q6Y" id="24b-AS-hX4"/>
<outlet property="paginationLabel" destination="r7y-FK-GWS" id="R9V-ix-mDu"/>
@@ -241,10 +288,20 @@
<outlet property="paginationTitleContainerView" destination="u1e-Q2-PhY" id="Osl-dF-fpA"/>
<outlet property="reactionsContainerView" destination="Dj1-m6-1Jw" id="p5e-n1-Wca"/>
<outlet property="reactionsContentView" destination="SNw-aM-ILI" id="wMe-f0-qhq"/>
<outlet property="reactionsContentViewLeadingConstraint" destination="x1n-oT-dez" id="LUh-3Q-SVr"/>
<outlet property="reactionsContentViewTrailingConstraint" destination="ynR-d4-6cf" id="Sy0-o9-vAC"/>
<outlet property="readReceiptsContainerView" destination="4zo-V8-CNe" id="7ek-u4-CX8"/>
<outlet property="readReceiptsContentView" destination="rQt-NH-Cb0" id="tqw-je-kp9"/>
<outlet property="senderInfoContainerView" destination="w0C-6a-f5M" id="fZE-vY-0nR"/>
<outlet property="threadSummaryContainerView" destination="2eB-kB-m20" id="0Y4-4E-I9K"/>
<outlet property="threadSummaryContentView" destination="snf-Ea-To0" id="6Oo-Gj-e4Z"/>
<outlet property="threadSummaryContentViewBottomConstraint" destination="4tt-w0-4JE" id="6db-Jz-jys"/>
<outlet property="threadSummaryContentViewLeadingConstraint" destination="2lm-T3-dEu" id="jR6-sh-Nul"/>
<outlet property="threadSummaryContentViewTrailingConstraint" destination="Qwm-Of-Zgc" id="dfE-aR-MQQ"/>
<outlet property="urlPreviewContainerView" destination="57V-Sl-EmD" id="9c6-ai-BY8"/>
<outlet property="urlPreviewContentView" destination="vpX-Nl-AEt" id="P8S-Fg-VFQ"/>
<outlet property="urlPreviewContentViewLeadingConstraint" destination="lTt-Qx-fuQ" id="5Fc-zl-b1j"/>
<outlet property="urlPreviewContentViewTrailingConstraint" destination="ZmA-Ns-chh" id="EXg-Rr-cuJ"/>
<outlet property="userNameContainerView" destination="w0C-6a-f5M" id="fZE-vY-0nR"/>
<outlet property="userNameLabel" destination="meG-P8-61b" id="ETK-ag-WYR"/>
<outlet property="userNameTouchMaskView" destination="ohU-Sc-mgb" id="FwW-aL-kc5"/>
</connections>
@@ -253,5 +310,8 @@
</objects>
<resources>
<image name="encryption_warning" width="12" height="12"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
@@ -0,0 +1,23 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// BubbleCellContentView decoration view items alignment
enum RoomCellDecorationAlignment {
case left
case right
}
@@ -0,0 +1,23 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// `RoomCellURLPreviewDisplayable` is a protocol indicating that a cell support displaying a URL preview.
@objc protocol RoomCellURLPreviewDisplayable {
func addURLPreviewView(_ urlPreviewView: UIView)
func removeURLPreviewView()
}
@@ -239,6 +239,16 @@ extern NSString *const kMXKRoomBubbleCellUrlItemInteraction;
*/
@property (nonatomic) WKWebView *attachmentWebView;
/**
Indicate true if the cell needs vertical space in the text to position UI components.
*/
@property (nonatomic, readonly) BOOL isTextViewNeedsPositioningVerticalSpace;
/**
Use bubbleData.attributedTextMessage or bubbleData.attributedTextMessageWithoutPositioningSpace according to isTextViewNeedsPositioningVerticalSpace value.
*/
@property (nonatomic, readonly) NSAttributedString *suitableAttributedTextMessage;
/**
Called during the designated initializer of the UITableViewCell class to set the default
properties values.
@@ -30,6 +30,8 @@
#import "MXKMessageTextView.h"
#import "UITextView+MatrixKit.h"
#import "GeneratedInterface-Swift.h"
#pragma mark - Constant definitions
NSString *const kMXKRoomBubbleCellTapOnMessageTextView = @"kMXKRoomBubbleCellTapOnMessageTextView";
NSString *const kMXKRoomBubbleCellTapOnSenderNameLabel = @"kMXKRoomBubbleCellTapOnSenderNameLabel";
@@ -122,6 +124,7 @@ static BOOL _disableLongPressGestureOnEvent;
_allTextHighlighted = NO;
_isAutoAnimatedGif = NO;
_tmpSubviews = [NSMutableArray array];
_isTextViewNeedsPositioningVerticalSpace = YES;
}
- (void)awakeFromNib
@@ -366,18 +369,23 @@ static BOOL _disableLongPressGestureOnEvent;
{
if (_allTextHighlighted)
{
NSMutableAttributedString *highlightedString = [[NSMutableAttributedString alloc] initWithAttributedString:bubbleData.attributedTextMessage];
NSMutableAttributedString *highlightedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.suitableAttributedTextMessage];
UIColor *color = self.tintColor ? self.tintColor : [UIColor lightGrayColor];
[highlightedString addAttribute:NSBackgroundColorAttributeName value:color range:NSMakeRange(0, highlightedString.length)];
self.messageTextView.attributedText = highlightedString;
}
else
{
self.messageTextView.attributedText = bubbleData.attributedTextMessage;
self.messageTextView.attributedText = self.suitableAttributedTextMessage;
}
}
}
- (NSAttributedString *)suitableAttributedTextMessage
{
return self.isTextViewNeedsPositioningVerticalSpace ? bubbleData.attributedTextMessage : bubbleData.attributedTextMessageWithoutPositioningSpace;
}
- (void)highlightTextMessageForEvent:(NSString*)eventId
{
if (self.messageTextView)
@@ -389,7 +397,7 @@ static BOOL _disableLongPressGestureOnEvent;
else
{
// Restore original string
self.messageTextView.attributedText = bubbleData.attributedTextMessage;
self.messageTextView.attributedText = self.suitableAttributedTextMessage;
}
}
}
@@ -559,14 +567,14 @@ static BOOL _disableLongPressGestureOnEvent;
// Underline attached file name
if (self.isBubbleDataContainsFileAttachment)
{
NSMutableAttributedString *updatedText = [[NSMutableAttributedString alloc] initWithAttributedString:bubbleData.attributedTextMessage];
NSMutableAttributedString *updatedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.suitableAttributedTextMessage];
[updatedText addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, updatedText.length)];
newText = updatedText;
}
else
{
newText = bubbleData.attributedTextMessage;
newText = self.suitableAttributedTextMessage;
}
// update the text only if it is required
@@ -927,8 +935,24 @@ static BOOL _disableLongPressGestureOnEvent;
}
else if (cell.messageTextView)
{
CGFloat maxTextViewWidth;
RoomTimelineConfiguration *timelineConfiguration = [RoomTimelineConfiguration shared];
id<RoomCellLayoutUpdating> cellLayoutUpdater = timelineConfiguration.currentStyle.cellLayoutUpdater;
// Handle updated text view layout if needed
if (cellLayoutUpdater)
{
maxTextViewWidth = [cellLayoutUpdater maximumTextViewWidthFor:cell cellData:cellData maximumCellWidth:maxWidth];
}
else
{
maxTextViewWidth = maxWidth - (cell.msgTextViewLeadingConstraint.constant + cell.msgTextViewTrailingConstraint.constant);
}
// Update maximum width available for the textview
bubbleData.maxTextViewWidth = maxWidth - (cell.msgTextViewLeadingConstraint.constant + cell.msgTextViewTrailingConstraint.constant);
bubbleData.maxTextViewWidth = maxTextViewWidth;
// Retrieve the suggested height of the message content
rowHeight = bubbleData.contentSize.height;
@@ -950,101 +974,33 @@ static BOOL _disableLongPressGestureOnEvent;
{
[super prepareForReuse];
bubbleData = nil;
delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.readReceiptsAlignment = ReadReceiptAlignmentLeft;
_allTextHighlighted = NO;
_isAutoAnimatedGif = NO;
[self removeHTMLBlockquoteSideBorderViews];
[self removeTemporarySubviews];
[self cleanAttachmentView];
[self clearBubbleInfoContainer];
[self clearBubbleOverlayContainer];
[self resetConstraintsConstantToDefault];
[self clearAttachmentWebView];
[self didEndDisplay];
}
- (void)didEndDisplay
{
bubbleData = nil;
for (UIView *sideBorder in htmlBlockquoteSideBorderViews)
{
[sideBorder removeFromSuperview];
}
[htmlBlockquoteSideBorderViews removeAllObjects];
htmlBlockquoteSideBorderViews = nil;
if (_attachmentWebView)
{
[_attachmentWebView removeFromSuperview];
_attachmentWebView.navigationDelegate = nil;
_attachmentWebView = nil;
}
[self removeReadMarkerView];
[self cleanProgressView];
if (_readMarkerView)
{
[_readMarkerView removeFromSuperview];
_readMarkerView = nil;
_readMarkerViewTopConstraint = nil;
_readMarkerViewLeadingConstraint = nil;
_readMarkerViewTrailingConstraint = nil;
_readMarkerViewHeightConstraint = nil;
}
if (self.attachmentView)
{
// Remove all gesture recognizer
while (self.attachmentView.gestureRecognizers.count)
{
[self.attachmentView removeGestureRecognizer:self.attachmentView.gestureRecognizers[0]];
}
// Prevent the cell from displaying again the image in case of reuse.
self.attachmentView.image = nil;
}
// Remove potential dateTime (or unsent) label(s)
if (self.bubbleInfoContainer && self.bubbleInfoContainer.subviews.count > 0)
{
NSArray* subviews = self.bubbleInfoContainer.subviews;
for (UIView *view in subviews)
{
[view removeFromSuperview];
}
}
self.bubbleInfoContainer.hidden = YES;
// Remove temporary subviews
for (UIView *view in self.tmpSubviews)
{
[view removeFromSuperview];
}
[self.tmpSubviews removeAllObjects];
// Remove potential overlay subviews
if (self.bubbleOverlayContainer)
{
NSArray* subviews = self.bubbleOverlayContainer.subviews;
for (UIView *view in subviews)
{
[view removeFromSuperview];
}
self.bubbleOverlayContainer.hidden = YES;
}
if (self.progressView)
{
[self stopProgressUI];
// Remove long tap gesture on the progressView
while (self.progressView.gestureRecognizers.count)
{
[self.progressView removeGestureRecognizer:self.progressView.gestureRecognizers[0]];
}
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
delegate = nil;
self.readReceiptsAlignment = ReadReceiptAlignmentLeft;
_allTextHighlighted = NO;
_isAutoAnimatedGif = NO;
[self resetConstraintsConstantToDefault];
// TODO: Stop gif animation
}
- (BOOL)shouldInteractWithURL:(NSURL *)URL urlItemInteraction:(UITextItemInteraction)urlItemInteraction associatedEvent:(MXEvent*)associatedEvent
@@ -1135,6 +1091,111 @@ static BOOL _disableLongPressGestureOnEvent;
[self.tmpSubviews addObject:subview];
}
#pragma mark - Cleaning
- (void)removeHTMLBlockquoteSideBorderViews
{
for (UIView *sideBorder in htmlBlockquoteSideBorderViews)
{
[sideBorder removeFromSuperview];
}
[htmlBlockquoteSideBorderViews removeAllObjects];
htmlBlockquoteSideBorderViews = nil;
}
- (void)removeReadMarkerView
{
if (_readMarkerView)
{
[_readMarkerView removeFromSuperview];
_readMarkerView = nil;
_readMarkerViewTopConstraint = nil;
_readMarkerViewLeadingConstraint = nil;
_readMarkerViewTrailingConstraint = nil;
_readMarkerViewHeightConstraint = nil;
}
}
- (void)removeTemporarySubviews
{
// Remove temporary subviews
for (UIView *view in self.tmpSubviews)
{
[view removeFromSuperview];
}
[self.tmpSubviews removeAllObjects];
}
- (void)cleanAttachmentView
{
if (self.attachmentView)
{
// Remove all gesture recognizer
while (self.attachmentView.gestureRecognizers.count)
{
[self.attachmentView removeGestureRecognizer:self.attachmentView.gestureRecognizers[0]];
}
// Prevent the cell from displaying again the image in case of reuse.
self.attachmentView.image = nil;
}
}
- (void)clearBubbleInfoContainer
{
// Remove potential dateTime (or unsent) label(s)
if (self.bubbleInfoContainer && self.bubbleInfoContainer.subviews.count > 0)
{
NSArray* subviews = self.bubbleInfoContainer.subviews;
for (UIView *view in subviews)
{
[view removeFromSuperview];
}
}
self.bubbleInfoContainer.hidden = YES;
}
- (void)clearBubbleOverlayContainer
{
// Remove potential overlay subviews
if (self.bubbleOverlayContainer)
{
NSArray* subviews = self.bubbleOverlayContainer.subviews;
for (UIView *view in subviews)
{
[view removeFromSuperview];
}
self.bubbleOverlayContainer.hidden = YES;
}
}
- (void)cleanProgressView
{
if (self.progressView)
{
[self stopProgressUI];
// Remove long tap gesture on the progressView
while (self.progressView.gestureRecognizers.count)
{
[self.progressView removeGestureRecognizer:self.progressView.gestureRecognizers[0]];
}
}
}
- (void)clearAttachmentWebView
{
if (_attachmentWebView)
{
[_attachmentWebView removeFromSuperview];
_attachmentWebView.navigationDelegate = nil;
_attachmentWebView = nil;
}
}
#pragma mark - Attachment progress handling
- (void)updateProgressUI:(NSDictionary*)statisticsDict
@@ -47,6 +47,34 @@ typedef NS_ENUM(NSUInteger, RoomTimelineCellIdentifier) {
RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName,
RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName,
// - Emote
// -- Incoming
// --- Clear
RoomTimelineCellIdentifierIncomingEmote,
RoomTimelineCellIdentifierIncomingEmoteWithoutSenderInfo,
RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitle,
RoomTimelineCellIdentifierIncomingEmoteWithoutSenderName,
RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitleWithoutSenderName,
// --- Encrypted
RoomTimelineCellIdentifierIncomingEmoteEncrypted,
RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderInfo,
RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitle,
RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderName,
RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitleWithoutSenderName,
// -- Outgoing
// --- Clear
RoomTimelineCellIdentifierOutgoingEmote,
RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderInfo,
RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitle,
RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderName,
RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitleWithoutSenderName,
// --- Encrypted
RoomTimelineCellIdentifierOutgoingEmoteEncrypted,
RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderInfo,
RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitle,
RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderName,
RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitleWithoutSenderName,
// - Attachment
// -- Incoming
// --- Clear
@@ -15,6 +15,7 @@
*/
import UIKit
import MatrixSDK
@objc protocol SizableBaseBubbleCellType: BaseBubbleCellType {
static func sizingViewHeightHashValue(from bubbleCellData: MXKRoomBubbleCellData) -> Int
@@ -32,6 +33,8 @@ class SizableBaseBubbleCell: BaseBubbleCell, SizableBaseBubbleCellType {
private static let reactionsViewSizer = BubbleReactionsViewSizer()
private static let reactionsViewModelBuilder = BubbleReactionsViewModelBuilder()
private static let urlPreviewViewSizer = URLPreviewViewSizer()
private class var sizingView: SizableBaseBubbleCell {
let sizingView: SizableBaseBubbleCell
@@ -135,7 +138,22 @@ class SizableBaseBubbleCell: BaseBubbleCell, SizableBaseBubbleCellType {
if sizingView is BubbleCellThreadSummaryDisplayable,
let roomBubbleCellData = cellData as? RoomBubbleCellData,
roomBubbleCellData.hasThreadRoot {
let bottomMargin = sizingView.bubbleCellContentView?.threadSummaryContentViewBottomConstraint.constant ?? 0
height += RoomBubbleCellLayout.threadSummaryViewHeight
height += bottomMargin
}
// Add URL preview view height if needed
if sizingView is RoomCellURLPreviewDisplayable,
let roomBubbleCellData = cellData as? RoomBubbleCellData, let firstBubbleComponent =
roomBubbleCellData.getFirstBubbleComponentWithDisplay(), firstBubbleComponent.showURLPreview, let urlPreviewData = firstBubbleComponent.urlPreviewData as? URLPreviewData {
let urlPreviewMaxWidth = sizingView.bubbleCellContentView?.urlPreviewContentView.frame.width ?? roomBubbleCellData.maxTextViewWidth
let urlPreviewHeight = self.urlPreviewViewSizer.height(for: urlPreviewData, fittingWidth: urlPreviewMaxWidth)
height+=urlPreviewHeight
}
return height
@@ -0,0 +1,29 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
/// Bubble style room cell layout constants
@objcMembers
final class BubbleRoomCellLayoutConstants: NSObject {
static let outgoingBubbleBackgroundMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 80, bottom: 0, right: 34)
static let incomingBubbleBackgroundMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 80)
static let threadSummaryViewMargins: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0)
}
@@ -50,40 +50,16 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
func updateLayout(forIncomingTextMessageCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) {
if let messageBubbleBackgroundView = cell.messageBubbleBackgroundView {
if self.canUseBubbleBackground(forCell: cell, withCellData: cellData) {
messageBubbleBackgroundView.isHidden = false
self.updateMessageBubbleBackgroundView(messageBubbleBackgroundView, withCell: cell, andCellData: cellData)
} else {
messageBubbleBackgroundView.isHidden = true
}
}
}
func updateLayout(forOutgoingTextMessageCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) {
if let messageBubbleBackgroundView = cell.messageBubbleBackgroundView {
if self.canUseBubbleBackground(forCell: cell, withCellData: cellData) {
messageBubbleBackgroundView.isHidden = false
self.updateMessageBubbleBackgroundView(messageBubbleBackgroundView, withCell: cell, andCellData: cellData)
} else {
messageBubbleBackgroundView.isHidden = true
}
}
}
func setupLayout(forIncomingTextMessageCell cell: MXKRoomBubbleTableViewCell) {
self.setupIncomingMessageTextViewMargins(for: cell)
self.addBubbleBackgroundViewToCell(cell, backgroundColor: self.incomingColor)
cell.setNeedsUpdateConstraints()
}
@@ -94,8 +70,6 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
// Hide avatar view
cell.pictureView?.isHidden = true
self.addBubbleBackgroundViewToCell(cell, backgroundColor: self.outgoingColor)
cell.setNeedsUpdateConstraints()
}
@@ -123,6 +97,49 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
}
}
func maximumTextViewWidth(for cell: MXKRoomBubbleTableViewCell, cellData: MXKCellData, maximumCellWidth: CGFloat) -> CGFloat {
guard cell.messageTextView != nil else {
return 0
}
let maxTextViewWidth: CGFloat
let textViewleftMargin: CGFloat
let textViewRightMargin: CGFloat
if let roomBubbleCellData = cellData as? RoomBubbleCellData, cell is MXKRoomIncomingTextMsgBubbleCell || cell is MXKRoomOutgoingTextMsgBubbleCell {
if roomBubbleCellData.isIncoming {
let textViewInsets = self.getIncomingMessageTextViewInsets(from: cell)
textViewleftMargin = cell.msgTextViewLeadingConstraint.constant + textViewInsets.left
// Right inset is in fact margin in this case
textViewRightMargin = textViewInsets.right
} else {
let textViewMargins = self.getOutgoingMessageTextViewMargins(from: cell)
textViewleftMargin = textViewMargins.left
textViewRightMargin = textViewMargins.right
}
} else {
textViewleftMargin = cell.msgTextViewLeadingConstraint.constant
textViewRightMargin = cell.msgTextViewTrailingConstraint.constant
}
maxTextViewWidth = maximumCellWidth - (textViewleftMargin + textViewRightMargin)
guard maxTextViewWidth >= 0 else {
return 0
}
guard maxTextViewWidth <= maximumCellWidth else {
return maxTextViewWidth
}
return maxTextViewWidth
}
// MARK: Themable
func update(theme: Theme) {
@@ -131,166 +148,21 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
// MARK: - Private
// MARK: Bubble background view
private func createBubbleBackgroundView(with backgroundColor: UIColor) -> RoomMessageBubbleBackgroundView {
let bubbleBackgroundView = RoomMessageBubbleBackgroundView()
bubbleBackgroundView.backgroundColor = backgroundColor
return bubbleBackgroundView
}
private func addBubbleBackgroundViewToCell(_ bubbleCell: MXKRoomBubbleTableViewCell, backgroundColor: UIColor) {
guard let messageTextView = bubbleCell.messageTextView else {
return
}
let topMargin: CGFloat = 0.0
let leftMargin: CGFloat = 5.0
let rightMargin: CGFloat = 45.0 // Add extra space for timestamp
let bubbleBackgroundView = self.createBubbleBackgroundView(with: backgroundColor)
bubbleCell.contentView.insertSubview(bubbleBackgroundView, at: 0)
let topAnchor = messageTextView.topAnchor
let leadingAnchor = messageTextView.leadingAnchor
let trailingAnchor = messageTextView.trailingAnchor
bubbleBackgroundView.updateHeight(messageTextView.frame.height)
NSLayoutConstraint.activate([
bubbleBackgroundView.topAnchor.constraint(equalTo: topAnchor, constant: topMargin),
bubbleBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -leftMargin),
bubbleBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: rightMargin)
])
}
private func canUseBubbleBackground(forCell cell: MXKRoomBubbleTableViewCell, withCellData cellData: MXKRoomBubbleCellData) -> Bool {
guard let firstComponent = cellData.getFirstBubbleComponentWithDisplay(), let firstEvent = firstComponent.event else {
return false
}
switch firstEvent.eventType {
case .roomMessage:
if let messageType = firstEvent.messageType {
switch messageType {
case .text, .file:
return true
case .emote:
// Explicitely disable bubble for emotes
return false
default:
break
}
}
default:
break
}
return false
}
private func getTextMessageHeight(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> CGFloat? {
guard let roomBubbleCellData = cellData as? RoomBubbleCellData,
let lastBubbleComponent = cellData.getLastBubbleComponentWithDisplay(),
let firstComponent = roomBubbleCellData.getFirstBubbleComponentWithDisplay() else {
return nil
}
let bubbleHeight: CGFloat
let lastEventId = lastBubbleComponent.event.eventId
let lastMessageBottomPosition = cell.bottomPosition(ofEvent: lastEventId)
let firstEventId = firstComponent.event.eventId
let firstMessageTopPosition = cell.topPosition(ofEvent: firstEventId)
let additionalContentHeight = roomBubbleCellData.additionalContentHeight
bubbleHeight = lastMessageBottomPosition - firstMessageTopPosition - additionalContentHeight
guard bubbleHeight >= 0 else {
return nil
}
return bubbleHeight
}
// TODO: Improve text message height calculation
// This method is closer to final result but lack of stability because of extra vertical space not handled here.
// private func getTextMessageHeight(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> CGFloat? {
//
// guard let roomBubbleCellData = cellData as? RoomBubbleCellData,
// let firstComponent = roomBubbleCellData.getFirstBubbleComponentWithDisplay() else {
// return nil
// }
//
// let bubbleHeight: CGFloat
//
// let componentIndex = cellData.bubbleComponentIndex(forEventId: firstComponent.event.eventId)
//
// let componentFrame = cell.componentFrameInContentView(for: componentIndex)
//
// bubbleHeight = componentFrame.height
//
// guard bubbleHeight >= 0 else {
// return nil
// }
//
// return bubbleHeight
// }
private func getMessageBubbleBackgroundHeight(for cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> CGFloat? {
var finalBubbleHeight: CGFloat?
let extraMargin: CGFloat = 4.0
if let bubbleHeight = self.getTextMessageHeight(for: cell, andCellData: cellData) {
finalBubbleHeight = bubbleHeight + extraMargin
} else if let messageTextViewHeight = cell.messageTextView?.frame.height {
finalBubbleHeight = messageTextViewHeight + extraMargin
}
return finalBubbleHeight
}
@discardableResult
private func updateMessageBubbleBackgroundView(_ roomMessageBubbleBackgroundView: RoomMessageBubbleBackgroundView, withCell cell: MXKRoomBubbleTableViewCell, andCellData cellData: MXKRoomBubbleCellData) -> Bool {
if let bubbleHeight = self.getMessageBubbleBackgroundHeight(for: cell, andCellData: cellData) {
return roomMessageBubbleBackgroundView.updateHeight(bubbleHeight)
} else {
return false
}
}
// MARK: Text message
private func getIncomingMessageTextViewInsets(from bubbleCell: MXKRoomBubbleTableViewCell) -> UIEdgeInsets {
let messageViewMarginTop: CGFloat
let messageViewMarginBottom: CGFloat = -2.0
let messageViewMarginLeft: CGFloat = 3.0
let messageViewMarginRight: CGFloat = 80
if bubbleCell.userNameLabel != nil {
messageViewMarginTop = 10.0
} else {
messageViewMarginTop = 0.0
}
let bubbleBgRightMargin: CGFloat = 45
let messageViewMarginTop: CGFloat = 0
let messageViewMarginBottom: CGFloat = -0
let messageViewMarginLeft: CGFloat = 0
let messageViewMarginRight: CGFloat = 80 + bubbleBgRightMargin
let messageViewInsets = UIEdgeInsets(top: messageViewMarginTop, left: messageViewMarginLeft, bottom: messageViewMarginBottom, right: messageViewMarginRight)
return messageViewInsets
}
// MARK: Text message
private func setupIncomingMessageTextViewMargins(for cell: MXKRoomBubbleTableViewCell) {
guard cell.messageTextView != nil else {
@@ -302,7 +174,23 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
cell.msgTextViewBottomConstraint.constant += messageViewInsets.bottom
cell.msgTextViewTopConstraint.constant += messageViewInsets.top
cell.msgTextViewLeadingConstraint.constant += messageViewInsets.left
cell.msgTextViewTrailingConstraint.constant += messageViewInsets.right
// Right inset is in fact margin in this case
cell.msgTextViewTrailingConstraint.constant = messageViewInsets.right
}
private func getOutgoingMessageTextViewMargins(from bubbleCell: MXKRoomBubbleTableViewCell) -> UIEdgeInsets {
let innerContentLeftMargin: CGFloat = 57
let messageViewMarginTop: CGFloat = 0
let messageViewMarginBottom: CGFloat = 0
let messageViewMarginLeft: CGFloat = 80.0 + innerContentLeftMargin
let messageViewMarginRight: CGFloat = 78.0
let messageViewInsets = UIEdgeInsets(top: messageViewMarginTop, left: messageViewMarginLeft, bottom: messageViewMarginBottom, right: messageViewMarginRight)
return messageViewInsets
}
private func setupOutgoingMessageTextViewMargins(for cell: MXKRoomBubbleTableViewCell) {
@@ -313,16 +201,14 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
let contentView = cell.contentView
let leftMargin: CGFloat = 80.0
let rightMargin: CGFloat = 78.0
let bottomMargin: CGFloat = -2.0
let messageViewMargins = self.getOutgoingMessageTextViewMargins(from: cell)
cell.msgTextViewLeadingConstraint.isActive = false
cell.msgTextViewTrailingConstraint.isActive = false
let leftConstraint = messageTextView.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor, constant: leftMargin)
let leftConstraint = messageTextView.leadingAnchor.constraint(greaterThanOrEqualTo: contentView.leadingAnchor, constant: messageViewMargins.left)
let rightConstraint = messageTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -rightMargin)
let rightConstraint = messageTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -messageViewMargins.right)
NSLayoutConstraint.activate([
leftConstraint,
@@ -332,9 +218,11 @@ class BubbleRoomCellLayoutUpdater: RoomCellLayoutUpdating {
cell.msgTextViewLeadingConstraint = leftConstraint
cell.msgTextViewTrailingConstraint = rightConstraint
cell.msgTextViewBottomConstraint.constant += bottomMargin
cell.msgTextViewBottomConstraint.constant += messageViewMargins.bottom
}
// MARK: File attachment
private func setupOutgoingFileAttachViewMargins(for cell: MXKRoomBubbleTableViewCell) {
guard let attachmentView = cell.attachmentView else {
@@ -29,24 +29,19 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
override func addTimestampLabel(toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData) {
if let timestampDisplayable = cell as? TimestampDisplayable, let timestampLabel = self.createTimestampLabel(for: cellData) {
guard let timestampLabel = self.createTimestampLabel(for: cellData) else {
super.addTimestampLabel(toCell: cell, cellData: cellData)
return
}
if let timestampDisplayable = cell as? TimestampDisplayable {
timestampDisplayable.addTimestampView(timestampLabel)
} else if let bubbleBackgroundView = cell.messageBubbleBackgroundView, bubbleBackgroundView.isHidden == false, let timestampLabel = self.createTimestampLabel(for: cellData) {
// If cell contains a bubble background, add the timestamp inside of it
self.addTimestampLabel(timestampLabel,
to: cell,
on: bubbleBackgroundView,
constrainingView: bubbleBackgroundView)
} else if cellData.isAttachmentWithThumbnail {
if cellData.attachment?.type == .sticker,
let attachmentView = cell.attachmentView,
let timestampLabel = self.createTimestampLabel(for: cellData) {
let attachmentView = cell.attachmentView {
// Prevent overlap with send status icon
let bottomMargin: CGFloat = 20.0
@@ -59,9 +54,11 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
rightMargin: rightMargin,
bottomMargin: bottomMargin)
} else if let attachmentView = cell.attachmentView, let timestampLabel = self.createTimestampLabel(for: cellData, textColor: self.theme.baseIconPrimaryColor) {
} else if let attachmentView = cell.attachmentView {
// For media with thumbnail cells, add timestamp inside thumbnail
timestampLabel.textColor = self.theme.baseIconPrimaryColor
self.addTimestampLabel(timestampLabel,
to: cell,
on: cell.contentView,
@@ -70,7 +67,7 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
} else {
super.addTimestampLabel(toCell: cell, cellData: cellData)
}
} else if let voiceMessageCell = cell as? VoiceMessageBubbleCell, let playbackView = voiceMessageCell.playbackController?.playbackView, let timestampLabel = self.createTimestampLabel(for: cellData) {
} else if let voiceMessageCell = cell as? VoiceMessageBubbleCell, let playbackView = voiceMessageCell.playbackController?.playbackView {
// Add timestamp on cell inherting from VoiceMessageBubbleCell
@@ -79,7 +76,7 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
on: cell.contentView,
constrainingView: playbackView)
} else if let fileWithoutThumbnailCell = cell as? FileWithoutThumbnailBaseBubbleCell, let fileAttachementView = fileWithoutThumbnailCell.fileAttachementView, let timestampLabel = self.createTimestampLabel(for: cellData) {
} else if let fileWithoutThumbnailCell = cell as? FileWithoutThumbnailBaseBubbleCell, let fileAttachementView = fileWithoutThumbnailCell.fileAttachementView {
// Add timestamp on cell inherting from VoiceMessageBubbleCell
@@ -96,13 +93,13 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
override func addReactionView(_ reactionsView: BubbleReactionsView,
toCell cell: MXKRoomBubbleTableViewCell, cellData: RoomBubbleCellData, contentViewPositionY: CGFloat, upperDecorationView: UIView?) {
cell.addTemporarySubview(reactionsView)
if let reactionsDisplayable = cell as? BubbleCellReactionsDisplayable {
reactionsDisplayable.addReactionsView(reactionsView)
return
}
cell.addTemporarySubview(reactionsView)
reactionsView.translatesAutoresizingMaskIntoConstraints = false
let cellContentView = cell.contentView
@@ -170,44 +167,49 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
cellData: RoomBubbleCellData,
contentViewPositionY: CGFloat) {
cell.addTemporarySubview(urlPreviewView)
let cellContentView = cell.contentView
urlPreviewView.translatesAutoresizingMaskIntoConstraints = false
urlPreviewView.availableWidth = cellData.maxTextViewWidth
cellContentView.addSubview(urlPreviewView)
let leadingOrTrailingConstraint: NSLayoutConstraint
// Incoming message
if cellData.isIncoming {
if let urlPreviewDisplayable = cell as? RoomCellURLPreviewDisplayable {
urlPreviewView.translatesAutoresizingMaskIntoConstraints = false
urlPreviewDisplayable.addURLPreviewView(urlPreviewView)
} else {
cell.addTemporarySubview(urlPreviewView)
let cellContentView = cell.contentView
urlPreviewView.translatesAutoresizingMaskIntoConstraints = false
urlPreviewView.availableWidth = cellData.maxTextViewWidth
cellContentView.addSubview(urlPreviewView)
let leadingOrTrailingConstraint: NSLayoutConstraint
// Incoming message
if cellData.isIncoming {
var leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin
if cellData.containsBubbleComponentWithEncryptionBadge {
leftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin
var leftMargin = RoomBubbleCellLayout.reactionsViewLeftMargin
if cellData.containsBubbleComponentWithEncryptionBadge {
leftMargin += RoomBubbleCellLayout.encryptedContentLeftMargin
}
leftMargin-=5.0
leadingOrTrailingConstraint = urlPreviewView.leadingAnchor.constraint(equalTo: cellContentView.leadingAnchor, constant: leftMargin)
} else {
// Outgoing message
// TODO: Use constants
let rightMargin: CGFloat = 34.0
leadingOrTrailingConstraint = urlPreviewView.trailingAnchor.constraint(equalTo: cellContentView.trailingAnchor, constant: -rightMargin)
}
leftMargin-=5.0
let topMargin = contentViewPositionY + RoomBubbleCellLayout.urlPreviewViewTopMargin + RoomBubbleCellLayout.reactionsViewTopMargin
leadingOrTrailingConstraint = urlPreviewView.leadingAnchor.constraint(equalTo: cellContentView.leadingAnchor, constant: leftMargin)
} else {
// Outgoing message
// TODO: Use constants
let rightMargin: CGFloat = 34.0
leadingOrTrailingConstraint = urlPreviewView.trailingAnchor.constraint(equalTo: cellContentView.trailingAnchor, constant: -rightMargin)
// Set the preview view's origin
NSLayoutConstraint.activate([
leadingOrTrailingConstraint,
urlPreviewView.topAnchor.constraint(equalTo: cellContentView.topAnchor, constant: topMargin)
])
}
let topMargin = contentViewPositionY + RoomBubbleCellLayout.urlPreviewViewTopMargin + RoomBubbleCellLayout.reactionsViewTopMargin
// Set the preview view's origin
NSLayoutConstraint.activate([
leadingOrTrailingConstraint,
urlPreviewView.topAnchor.constraint(equalTo: cellContentView.topAnchor, constant: topMargin)
])
}
override func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView,
@@ -216,11 +218,11 @@ class BubbleRoomTimelineCellDecorator: PlainRoomTimelineCellDecorator {
contentViewPositionY: CGFloat,
upperDecorationView: UIView?) {
cell.addTemporarySubview(threadSummaryView)
if let threadSummaryDisplayable = cell as? BubbleCellThreadSummaryDisplayable {
threadSummaryDisplayable.addThreadSummaryView(threadSummaryView)
} else {
cell.addTemporarySubview(threadSummaryView)
threadSummaryView.translatesAutoresizingMaskIntoConstraints = false
let cellContentView = cell.contentView
@@ -64,6 +64,28 @@
[self registerFileWithoutThumbnailCellsForTableView:tableView];
}
- (void)registerIncomingTextMessageCellsForTableView:(UITableView*)tableView
{
// Also register legacy cells for notice and emotes
[super registerIncomingTextMessageCellsForTableView:tableView];
[tableView registerClass:TextMessageIncomingBubbleCell.class forCellReuseIdentifier:TextMessageIncomingBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:TextMessageIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageIncomingWithoutSenderNameBubbleCell.class forCellReuseIdentifier:TextMessageIncomingWithoutSenderNameBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageIncomingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:TextMessageIncomingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier];
}
- (void)registerOutgoingTextMessageCellsForTableView:(UITableView*)tableView
{
// Also register legacy cells for notice and emotes
[super registerOutgoingTextMessageCellsForTableView:tableView];
[tableView registerClass:TextMessageOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:TextMessageOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:TextMessageOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
[tableView registerClass:TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.class forCellReuseIdentifier:TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.defaultReuseIdentifier];
}
- (void)registerVoiceMessageCellsForTableView:(UITableView*)tableView
{
// Incoming
@@ -112,22 +134,77 @@
#pragma mark - Mapping
- (NSDictionary<NSNumber*, Class>*)incomingTextMessageCellsMapping
{
return @{
// Clear
@(RoomTimelineCellIdentifierIncomingTextMessage) : TextMessageIncomingBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderInfo) : TextMessageIncomingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitle) : TextMessageIncomingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageWithoutSenderName) : TextMessageIncomingWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageWithPaginationTitleWithoutSenderName) : TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierIncomingTextMessageEncrypted) : TextMessageIncomingBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderInfo) : TextMessageIncomingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitle) : TextMessageIncomingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithoutSenderName) : TextMessageIncomingWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingTextMessageEncryptedWithPaginationTitleWithoutSenderName) : TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)outgoingTextMessageCellsMapping
{
// Hide sender info and avatar for bubble outgoing messages
return @{
// Clear
@(RoomTimelineCellIdentifierOutgoingTextMessage) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderInfo) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitle) : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderName) : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitleWithoutSenderName) : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessage) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderInfo) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitle) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithoutSenderName) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageWithPaginationTitleWithoutSenderName) : TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierOutgoingTextMessageEncrypted) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderInfo) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitle) : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncrypted) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderInfo) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitle) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithoutSenderName) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingTextMessageEncryptedWithPaginationTitleWithoutSenderName) : TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)incomingEmoteCellsMapping
{
return @{
// Clear
@(RoomTimelineCellIdentifierIncomingEmote) : TextMessageIncomingBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithoutSenderInfo) : TextMessageIncomingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitle) : TextMessageIncomingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithoutSenderName) : TextMessageIncomingWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitleWithoutSenderName) : TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierIncomingEmoteEncrypted) : TextMessageIncomingBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderInfo) : TextMessageIncomingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitle) : TextMessageIncomingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderName) : TextMessageIncomingWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitleWithoutSenderName) : TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)outgoingEmoteCellsMapping
{
// Hide sender info and avatar for bubble outgoing messages
return @{
// Clear
@(RoomTimelineCellIdentifierOutgoingEmote) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderInfo) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitle) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderName) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitleWithoutSenderName) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierOutgoingEmoteEncrypted) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderInfo) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitle) : TextMessageOutgoingWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderName) : TextMessageOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitleWithoutSenderName) : TextMessageOutgoingWithPaginationTitleBubbleCell.class
};
}
@@ -0,0 +1,54 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
protocol BubbleIncomingRoomCellProtocol: BubbleRoomCellProtocol {
}
extension BubbleIncomingRoomCellProtocol {
// MARK: - Public
func setupBubbleDecorations() {
self.bubbleCellContentView?.decorationViewsAlignment = .left
self.setupDecorationConstraints()
}
// MARK: - Private
private func setupDecorationConstraints() {
self.setupURLPreviewContentViewContraints()
self.setupReactionsContentViewContraints()
self.setupThreadSummaryViewContentViewContraints()
}
private func setupReactionsContentViewContraints() {
self.bubbleCellContentView?.reactionsContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.right
}
private func setupThreadSummaryViewContentViewContraints() {
self.bubbleCellContentView?.threadSummaryContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.right
self.bubbleCellContentView?.threadSummaryContentViewBottomConstraint.constant = BubbleRoomCellLayoutConstants.threadSummaryViewMargins.bottom
}
private func setupURLPreviewContentViewContraints() {
self.bubbleCellContentView?.urlPreviewContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.right
}
}
@@ -0,0 +1,118 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
protocol BubbleOutgoingRoomCellProtocol: BubbleRoomCellProtocol {
}
// MARK: - Default implementation
extension BubbleOutgoingRoomCellProtocol {
// MARK: - Public
func setupBubbleDecorations() {
self.bubbleCellContentView?.decorationViewsAlignment = .right
self.setupDecorationConstraints()
}
// MARK: - Private
private func setupDecorationConstraints() {
self.setupURLPreviewContentViewContraints()
self.setupReactionsContentViewContraints()
self.setupThreadSummaryViewContentViewContraints()
}
private func setupReactionsContentViewContraints() {
guard let bubbleCellContentView = self.bubbleCellContentView, let reactionsContentView = bubbleCellContentView.reactionsContentView, let reactionsContainerView = bubbleCellContentView.reactionsContainerView else {
return
}
// Remove leading constraint
bubbleCellContentView.reactionsContentViewLeadingConstraint.isActive = false
bubbleCellContentView.reactionsContentViewLeadingConstraint = nil
// Setup new leading constraint
let leadingConstraint = self.setupDecorationViewLeadingContraint(containerView: reactionsContainerView, contentView: reactionsContentView)
bubbleCellContentView.reactionsContentViewLeadingConstraint = leadingConstraint
// Update trailing constraint
bubbleCellContentView.reactionsContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right
}
private func setupThreadSummaryViewContentViewContraints() {
guard let bubbleCellContentView = self.bubbleCellContentView, let threadSummaryContentView = bubbleCellContentView.threadSummaryContentView, let threadSummaryContainerView = bubbleCellContentView.threadSummaryContainerView else {
return
}
// Remove leading constraint
bubbleCellContentView.threadSummaryContentViewLeadingConstraint.isActive = false
bubbleCellContentView.threadSummaryContentViewLeadingConstraint = nil
// Setup new leading constraint
let leadingConstraint = self.setupDecorationViewLeadingContraint(containerView: threadSummaryContainerView, contentView: threadSummaryContentView)
bubbleCellContentView.threadSummaryContentViewLeadingConstraint = leadingConstraint
// Update trailing constraint
bubbleCellContentView.threadSummaryContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right
// Update bottom constraint
bubbleCellContentView.threadSummaryContentViewBottomConstraint.constant = BubbleRoomCellLayoutConstants.threadSummaryViewMargins.bottom
}
private func setupURLPreviewContentViewContraints() {
guard let bubbleCellContentView = self.bubbleCellContentView, let contentView = bubbleCellContentView.urlPreviewContentView, let containerView = bubbleCellContentView.urlPreviewContainerView else {
return
}
// Remove leading constraint
bubbleCellContentView.urlPreviewContentViewLeadingConstraint.isActive = false
bubbleCellContentView.urlPreviewContentViewLeadingConstraint = nil
// Setup new leading constraint
let leadingConstraint = self.setupDecorationViewLeadingContraint(containerView: containerView, contentView: contentView)
bubbleCellContentView.urlPreviewContentViewLeadingConstraint = leadingConstraint
// Update trailing constraint
bubbleCellContentView.urlPreviewContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right
}
private func setupDecorationViewLeadingContraint(containerView: UIView,
contentView: UIView) -> NSLayoutConstraint {
let leadingConstraint = contentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.left)
leadingConstraint.isActive = true
return leadingConstraint
}
}
@@ -0,0 +1,24 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
protocol BubbleRoomCellProtocol {
var bubbleCellContentView: BubbleCellContentView? { get }
func setupBubbleDecorations()
}
@@ -22,12 +22,8 @@ class FileWithoutThumbnailBaseBubbleCell: SizableBaseBubbleCell, BubbleCellReact
override func render(_ cellData: MXKCellData!) {
super.render(cellData)
guard let data = cellData as? RoomBubbleCellData else {
return
}
self.fileAttachementView?.title = data.attributedTextMessage.string
self.fileAttachementView?.titleLabel.attributedText = self.suitableAttributedTextMessage
self.update(theme: ThemeService.shared().theme)
}
@@ -31,7 +31,7 @@ final class FileWithoutThumbnailCellContentView: UIView, NibLoadable {
// MARK: Outlets
@IBOutlet private weak var iconImageView: UIImageView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private(set) weak var titleLabel: UILabel!
// MARK: Public
@@ -44,15 +44,6 @@ final class FileWithoutThumbnailCellContentView: UIView, NibLoadable {
}
}
var title: String? {
get {
return self.titleLabel.text
}
set {
self.titleLabel.text = newValue
}
}
// MARK: - Setup
static func instantiate() -> FileWithoutThumbnailCellContentView {
@@ -79,4 +70,3 @@ final class FileWithoutThumbnailCellContentView: UIView, NibLoadable {
self.titleLabel.textColor = theme.textPrimaryColor
}
}
@@ -16,7 +16,7 @@
import UIKit
class FileWithoutThumbnailIncomingBubbleCell: FileWithoutThumbnailBaseBubbleCell {
class FileWithoutThumbnailIncomingBubbleCell: FileWithoutThumbnailBaseBubbleCell, BubbleIncomingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -29,6 +29,8 @@ class FileWithoutThumbnailIncomingBubbleCell: FileWithoutThumbnailBaseBubbleCell
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -16,7 +16,7 @@
import UIKit
class FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell: FileWithoutThumbnailBaseBubbleCell {
class FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell: FileWithoutThumbnailBaseBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -30,6 +30,8 @@ class FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell: FileWithoutThumbna
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -16,7 +16,7 @@
import Foundation
class LocationIncomingBubbleCell: LocationBubbleCell {
class LocationIncomingBubbleCell: LocationBubbleCell, BubbleIncomingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -27,5 +27,7 @@ class LocationIncomingBubbleCell: LocationBubbleCell {
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
self.setupBubbleDecorations()
}
}
@@ -16,18 +16,17 @@
import Foundation
class LocationOutgoingWithoutSenderInfoBubbleCell: LocationBubbleCell {
class LocationOutgoingWithoutSenderInfoBubbleCell: LocationBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderInfo = false
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.left
// TODO: Use constants
// Same as outgoing message
let rightMargin: CGFloat = 34.0
let leftMargin: CGFloat = 80.0
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
self.setupBubbleDecorations()
}
}
@@ -16,7 +16,7 @@
import Foundation
class PollIncomingBubbleCell: PollBaseBubbleCell {
class PollIncomingBubbleCell: PollBaseBubbleCell, BubbleIncomingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -27,7 +27,9 @@ class PollIncomingBubbleCell: PollBaseBubbleCell {
let messageLeftMargin: CGFloat = 48 + bubbleBackgroundSideMargin
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -16,7 +16,7 @@
import Foundation
class PollOutgoingWithoutSenderInfoBubbleCell: PollBaseBubbleCell {
class PollOutgoingWithoutSenderInfoBubbleCell: PollBaseBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -31,6 +31,8 @@ class PollOutgoingWithoutSenderInfoBubbleCell: PollBaseBubbleCell {
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -0,0 +1,112 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageBaseBubbleCell: SizableBaseBubbleCell, RoomCellURLPreviewDisplayable, BubbleCellReactionsDisplayable, BubbleCellThreadSummaryDisplayable, BubbleCellReadReceiptsDisplayable {
// MARK: - Properties
weak var textMessageContentView: TextMessageBubbleCellContentView?
override var messageTextView: UITextView! {
get {
return self.textMessageContentView?.textView
}
set { }
}
// MARK: - Overrides
override func setupViews() {
super.setupViews()
bubbleCellContentView?.backgroundColor = .clear
guard let contentView = bubbleCellContentView?.innerContentView else {
return
}
// TODO: Use constants
let messageLeftMargin: CGFloat = 48
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
self.bubbleCellContentView?.innerContentViewBottomContraint.constant = 5.0
self.bubbleCellContentView?.innerContentViewTrailingConstraint.constant = 34.0
let textMessageContentView = TextMessageBubbleCellContentView.instantiate()
contentView.vc_addSubViewMatchingParent(textMessageContentView)
self.textMessageContentView = textMessageContentView
}
override func update(theme: Theme) {
super.update(theme: theme)
if let messageTextView = self.messageTextView {
messageTextView.tintColor = theme.tintColor
}
// self.setupDebug()
}
// MARK: - Private
private func setupDebug() {
self.bubbleCellContentView?.innerContentView.backgroundColor = .yellow
self.bubbleCellContentView?.layer.borderWidth = 1.0
self.bubbleCellContentView?.layer.borderColor = UIColor.red.cgColor
self.textMessageContentView?.layer.borderColor = UIColor.blue.cgColor
self.textMessageContentView?.layer.borderWidth = 1.0
self.bubbleCellContentView?.readReceiptsContainerView.layer.borderColor = UIColor.yellow.cgColor
self.bubbleCellContentView?.readReceiptsContainerView.layer.borderWidth = 1.0
self.bubbleCellContentView?.reactionsContainerView.layer.borderColor = UIColor.blue.cgColor
self.bubbleCellContentView?.reactionsContainerView.layer.borderWidth = 1.0
self.bubbleCellContentView?.reactionsContentView.backgroundColor = .blue
self.bubbleCellContentView?.threadSummaryContainerView.layer.borderColor = UIColor.purple.cgColor
self.bubbleCellContentView?.threadSummaryContainerView.layer.borderWidth = 1.0
}
}
// MARK: - RoomCellTimestampDisplayable
extension TextMessageBaseBubbleCell: TimestampDisplayable {
func addTimestampView(_ timestampView: UIView) {
guard let messageBubbleBackgroundView = self.textMessageContentView?.bubbleBackgroundView else {
return
}
messageBubbleBackgroundView.removeTimestampView()
messageBubbleBackgroundView.addTimestampView(timestampView)
}
func removeTimestampView() {
guard let messageBubbleBackgroundView = self.textMessageContentView?.bubbleBackgroundView else {
return
}
messageBubbleBackgroundView.removeTimestampView()
}
}
@@ -0,0 +1,38 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
import Reusable
final class TextMessageBubbleCellContentView: UIView, NibLoadable {
// MARK: - Properties
// MARK: Outlets
@IBOutlet private(set) weak var bubbleBackgroundView: RoomMessageBubbleBackgroundView!
@IBOutlet weak var bubbleBackgroundViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var bubbleBackgroundViewTrailingConstraint: NSLayoutConstraint!
@IBOutlet private(set) weak var textView: UITextView!
// MARK: - Setup
static func instantiate() -> TextMessageBubbleCellContentView {
return TextMessageBubbleCellContentView.loadFromNib()
}
}
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="8T9-hj-ply" customClass="TextMessageBubbleCellContentView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="313" height="82"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wRb-1K-GDt" customClass="RoomMessageBubbleBackgroundView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="152" height="82"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" bounces="NO" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="text message" translatesAutoresizingMaskIntoConstraints="NO" id="1WT-up-UFQ" customClass="MXKMessageTextView">
<rect key="frame" x="5" y="5" width="102" height="72"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="MessageTextView"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" link="YES"/>
</textView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="1WT-up-UFQ" secondAttribute="bottom" constant="5" id="EMT-p9-z4L"/>
<constraint firstItem="1WT-up-UFQ" firstAttribute="leading" secondItem="wRb-1K-GDt" secondAttribute="leading" constant="5" id="GhZ-J0-v1m"/>
<constraint firstItem="1WT-up-UFQ" firstAttribute="top" secondItem="wRb-1K-GDt" secondAttribute="top" constant="5" id="OpF-jQ-D8Q"/>
<constraint firstAttribute="trailing" secondItem="1WT-up-UFQ" secondAttribute="trailing" constant="45" id="Y8T-HE-6NX"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="wRb-1K-GDt" firstAttribute="leading" secondItem="8T9-hj-ply" secondAttribute="leading" id="D7Y-mm-KEV"/>
<constraint firstAttribute="bottom" secondItem="wRb-1K-GDt" secondAttribute="bottom" id="JmB-yf-eq5"/>
<constraint firstItem="wRb-1K-GDt" firstAttribute="top" secondItem="8T9-hj-ply" secondAttribute="top" id="Jmy-ue-Qtg"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="wRb-1K-GDt" secondAttribute="trailing" constant="80" id="UwK-6h-MvN"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="bubbleBackgroundView" destination="wRb-1K-GDt" id="TWb-A8-15y"/>
<outlet property="bubbleBackgroundViewLeadingConstraint" destination="D7Y-mm-KEV" id="4le-fw-axb"/>
<outlet property="bubbleBackgroundViewTrailingConstraint" destination="UwK-6h-MvN" id="ZCA-gE-L11"/>
<outlet property="textView" destination="1WT-up-UFQ" id="Zh6-C0-kdV"/>
</connections>
<point key="canvasLocation" x="-1454.3478260869567" y="-392.41071428571428"/>
</view>
</objects>
</document>
@@ -0,0 +1,48 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingBubbleCell: TextMessageBaseBubbleCell, BubbleIncomingRoomCellProtocol {
// MARK: - Overrides
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderInfo = true
self.setupBubbleConstraints()
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
super.update(theme: theme)
self.textMessageContentView?.bubbleBackgroundView?.backgroundColor = theme.roomCellIncomingBubbleBackgroundColor
}
// MARK: - Private
private func setupBubbleConstraints() {
self.textMessageContentView?.bubbleBackgroundViewLeadingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.left
let innerContentViewTrailingMargin = self.bubbleCellContentView?.innerContentViewTrailingConstraint.constant ?? 0
self.textMessageContentView?.bubbleBackgroundViewTrailingConstraint.constant = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.right - innerContentViewTrailingMargin
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingWithPaginationTitleBubbleCell: TextMessageIncomingBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showPaginationTitle = true
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell: TextMessageIncomingWithPaginationTitleBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderName = false
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingWithoutSenderInfoBubbleCell: TextMessageIncomingBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderInfo = false
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageIncomingWithoutSenderNameBubbleCell: TextMessageIncomingBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderName = false
}
}
@@ -0,0 +1,26 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageOutgoingWithPaginationTitleBubbleCell: TextMessageOutgoingWithoutSenderInfoBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showPaginationTitle = true
}
}
@@ -0,0 +1,24 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
class TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell: TextMessageOutgoingWithPaginationTitleBubbleCell {
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderName = false
}
}
@@ -0,0 +1,73 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class TextMessageOutgoingWithoutSenderInfoBubbleCell: TextMessageBaseBubbleCell, BubbleOutgoingRoomCellProtocol {
// MARK: - Overrides
override func setupViews() {
super.setupViews()
bubbleCellContentView?.showSenderInfo = false
self.setupBubbleConstraints()
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
super.update(theme: theme)
self.textMessageContentView?.bubbleBackgroundView?.backgroundColor = theme.roomCellOutgoingBubbleBackgroundColor
}
// MARK: - Private
private func setupBubbleConstraints() {
guard let containerView = self.textMessageContentView, let bubbleBackgroundView = containerView.bubbleBackgroundView else {
return
}
// Remove existing contraints
if let bubbleBackgroundViewLeadingConstraint = self.textMessageContentView?.bubbleBackgroundViewLeadingConstraint {
bubbleBackgroundViewLeadingConstraint.isActive = false
self.textMessageContentView?.bubbleBackgroundViewLeadingConstraint = nil
}
if let bubbleBackgroundViewTrailingConstraint = self.textMessageContentView?.bubbleBackgroundViewTrailingConstraint {
bubbleBackgroundViewTrailingConstraint.isActive = false
self.textMessageContentView?.bubbleBackgroundViewTrailingConstraint = nil
}
// Setup new constraints
let leadingConstraint = bubbleBackgroundView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor, constant: BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.left)
let trailingConstraint = bubbleBackgroundView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0)
NSLayoutConstraint.activate([
leadingConstraint,
trailingConstraint
])
self.textMessageContentView?.bubbleBackgroundViewLeadingConstraint = leadingConstraint
self.textMessageContentView?.bubbleBackgroundViewTrailingConstraint = trailingConstraint
}
}
@@ -16,7 +16,7 @@
import Foundation
class VoiceMessageIncomingBubbleCell: VoiceMessageBubbleCell {
class VoiceMessageIncomingBubbleCell: VoiceMessageBubbleCell, BubbleIncomingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -29,7 +29,9 @@ class VoiceMessageIncomingBubbleCell: VoiceMessageBubbleCell {
bubbleCellContentView?.innerContentViewTrailingConstraint.constant = messageViewMarginRight
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = messageLeftMargin
playbackController.playbackView.stackViewTrailingContraint.constant = playbackViewRightMargin
playbackController.playbackView.stackViewTrailingContraint.constant = playbackViewRightMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -17,6 +17,7 @@
import Foundation
class VoiceMessageIncomingWithPaginationTitleBubbleCell: VoiceMessageIncomingBubbleCell {
override func setupViews() {
super.setupViews()
@@ -16,7 +16,7 @@
import Foundation
class VoiceMessageOutgoingWithoutSenderInfoBubbleCell: VoiceMessageBubbleCell {
class VoiceMessageOutgoingWithoutSenderInfoBubbleCell: VoiceMessageBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
@@ -33,6 +33,8 @@ class VoiceMessageOutgoingWithoutSenderInfoBubbleCell: VoiceMessageBubbleCell {
bubbleCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
playbackController.playbackView.stackViewTrailingContraint.constant = playbackViewRightMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
@@ -77,7 +77,11 @@ class RoomMessageBubbleBackgroundView: UIView {
// MARK: - TimestampDisplayable
extension RoomMessageBubbleBackgroundView: TimestampDisplayable {
func addTimestampView(_ timestampView: UIView) {
self.removeTimestampView()
self.addTimestampView(timestampView, rightMargin: 8.0, bottomMargin: 4.0)
self.timestampView = timestampView
}
@@ -22,6 +22,10 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Registration
- (void)registerIncomingTextMessageCellsForTableView:(UITableView*)tableView;
- (void)registerOutgoingTextMessageCellsForTableView:(UITableView*)tableView;
- (void)registerVoiceMessageCellsForTableView:(UITableView*)tableView;
- (void)registerPollCellsForTableView:(UITableView*)tableView;
@@ -30,8 +34,14 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Mapping
- (NSDictionary<NSNumber*, Class>*)incomingTextMessageCellsMapping;
- (NSDictionary<NSNumber*, Class>*)outgoingTextMessageCellsMapping;
- (NSDictionary<NSNumber*, Class>*)incomingEmoteCellsMapping;
- (NSDictionary<NSNumber*, Class>*)outgoingEmoteCellsMapping;
- (NSDictionary<NSNumber*, Class>*)outgoingAttachmentCellsMapping;
- (NSDictionary<NSNumber*, Class>*)incomingAttachmentWithoutThumbnailCellsMapping;
@@ -275,6 +275,13 @@
NSDictionary *outgoingTextMessageCellsMapping = [self outgoingTextMessageCellsMapping];
[cellClasses addEntriesFromDictionary:outgoingTextMessageCellsMapping];
// Emote
NSDictionary *incomingEmoteCellsMapping = [self incomingEmoteCellsMapping];
[cellClasses addEntriesFromDictionary:incomingEmoteCellsMapping];
NSDictionary *outgoingEmoteCellsMapping = [self outgoingEmoteCellsMapping];
[cellClasses addEntriesFromDictionary:outgoingEmoteCellsMapping];
// Attachment
NSDictionary *incomingAttachmentCellsMapping = [self incomingAttachmentCellsMapping];
@@ -360,6 +367,42 @@
};
}
- (NSDictionary<NSNumber*, Class>*)incomingEmoteCellsMapping
{
return @{
// Clear
@(RoomTimelineCellIdentifierIncomingEmote) : RoomIncomingTextMsgBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithoutSenderInfo) : RoomIncomingTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitle) : RoomIncomingTextMsgWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithoutSenderName) : RoomIncomingTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitleWithoutSenderName) : RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierIncomingEmoteEncrypted) : RoomIncomingEncryptedTextMsgBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderInfo) : RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitle) : RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderName) : RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitleWithoutSenderName) : RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)outgoingEmoteCellsMapping
{
return @{
// Clear
@(RoomTimelineCellIdentifierOutgoingEmote) : RoomOutgoingTextMsgBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderInfo) : RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitle) : RoomOutgoingTextMsgWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderName) : RoomOutgoingTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitleWithoutSenderName) : RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
// Encrypted
@(RoomTimelineCellIdentifierOutgoingEmoteEncrypted) : RoomOutgoingEncryptedTextMsgBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderInfo) : RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitle) : RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitleWithoutSenderName) : RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.class,
};
}
- (NSDictionary<NSNumber*, Class>*)incomingAttachmentCellsMapping
{
return @{
@@ -32,8 +32,8 @@ class PlainRoomTimelineStyle: RoomTimelineStyle {
let cellProvider: RoomTimelineCellProvider
let cellDecorator: RoomTimelineCellDecorator
let cellDecorator: RoomTimelineCellDecorator
// MARK: - Setup
init(theme: Theme) {
@@ -29,4 +29,6 @@ protocol RoomCellLayoutUpdating: Themable {
func setupLayout(forOutgoingFileAttachmentCell cell: MXKRoomBubbleTableViewCell)
func updateLayout(forSelectedStickerCell cell: RoomSelectedStickerBubbleCell)
func maximumTextViewWidth(for cell: MXKRoomBubbleTableViewCell, cellData: MXKCellData, maximumCellWidth: CGFloat) -> CGFloat
}
@@ -0,0 +1,43 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the Lircense.
//
import Foundation
/// `URLPreviewViewSizer` allows to determine reactions view height for a given urlPreviewData and width.
class URLPreviewViewSizer {
// MARK: - Constants
private static let sizingView = URLPreviewView.instantiate()
// MARK: - Public
func height(for urlPreviewData: URLPreviewData, fittingWidth width: CGFloat) -> CGFloat {
let sizingView = URLPreviewViewSizer.sizingView
sizingView.frame.size.height = 1.0
sizingView.preview = urlPreviewData
sizingView.availableWidth = width
sizingView.setNeedsLayout()
sizingView.layoutIfNeeded()
let fittingSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height)
return sizingView.systemLayoutSizeFitting(fittingSize).height
}
}