diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.h b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.h index eba99595c..275c25f7f 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.h +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.h @@ -46,6 +46,11 @@ */ NSAttributedString *attributedTextMessage; + /** + Same as attributedTextMessage but without vertical positioning vertical blank space. + */ + NSAttributedString *attributedTextMessageWithoutPositioningSpace; + /** The optional text pattern to be highlighted in the body of the message. */ diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m index dc658e680..4eefae18d 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellData.m @@ -28,7 +28,7 @@ @implementation MXKRoomBubbleCellData @synthesize senderId, targetId, roomId, senderDisplayName, senderAvatarUrl, senderAvatarPlaceholder, targetDisplayName, targetAvatarUrl, targetAvatarPlaceholder, isEncryptedRoom, isPaginationFirstBubble, shouldHideSenderInformation, date, isIncoming, isAttachmentWithThumbnail, isAttachmentWithIcon, attachment, senderFlair; -@synthesize textMessage, attributedTextMessage; +@synthesize textMessage, attributedTextMessage, attributedTextMessageWithoutPositioningSpace; @synthesize shouldHideSenderName, isTyping, showBubbleDateTime, showBubbleReceipts, useCustomDateTimeLabel, useCustomReceipts, useCustomUnsentButton, hasNoDisplay; @synthesize tag; @synthesize collapsable, collapsed, collapsedAttributedTextMessage, prevCollapsableCellData, nextCollapsableCellData, collapseState; diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataStoring.h b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataStoring.h index 9f227b79a..5ec4b9cb7 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataStoring.h +++ b/Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleCellDataStoring.h @@ -163,6 +163,10 @@ */ @property (nonatomic) NSAttributedString *attributedTextMessage; +/** + Same as attributedTextMessage but without vertical positioning blank space + */ +@property (nonatomic) NSAttributedString *attributedTextMessageWithoutPositioningSpace; /** The raw text message (without attributes) */ diff --git a/Riot/Modules/MatrixKit/Views/MXKCellRendering.h b/Riot/Modules/MatrixKit/Views/MXKCellRendering.h index 60d712910..22ebca772 100644 --- a/Riot/Modules/MatrixKit/Views/MXKCellRendering.h +++ b/Riot/Modules/MatrixKit/Views/MXKCellRendering.h @@ -75,11 +75,9 @@ - (MXKCellData*)renderedCellData; /** - Reset the cell. + Stop processes no more needed when cell is not visible. - The cell is no more displayed. This is time to release resources and removing listeners. - In case of UITableViewCell or UIContentViewCell object, the cell must reset in a state - that it can be reusable. + The cell is no more displayed but still recycled. This is time to stop animation. */ - (void)didEndDisplay; diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index c58e14554..3ba7dab34 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -241,30 +241,18 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat - (NSAttributedString*)attributedTextMessage { - @synchronized(bubbleComponents) - { - if (self.hasAttributedTextMessage && !attributedTextMessage.length) - { - // Attributed text message depends on the room read receipts which must be retrieved on the main thread to prevent us from race conditions. - // Check here the current thread, this is just a sanity check because the attributed text message - // is requested during the rendering step which takes place on the main thread. - if ([NSThread currentThread] != [NSThread mainThread]) - { - MXLogDebug(@"[RoomBubbleCellData] attributedTextMessage called on wrong thread"); - dispatch_sync(dispatch_get_main_queue(), ^{ - self.attributedTextMessage = [self makeAttributedString]; - }); - } - else - { - self.attributedTextMessage = [self makeAttributedString]; - } - } - } + [self buildAttributedStringIfNeeded]; return attributedTextMessage; } +- (NSAttributedString*)attributedTextMessageWithoutPositioningSpace +{ + [self buildAttributedStringIfNeeded]; + + return attributedTextMessageWithoutPositioningSpace; +} + - (BOOL)hasNoDisplay { if (self.tag == RoomBubbleCellDataTagKeyVerificationNoDisplay) @@ -378,18 +366,25 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat [self setNeedsUpdateAdditionalContentHeight]; } -- (NSAttributedString*)makeAttributedString +- (void)buildAttributedString { // CAUTION: This method must be called on the main thread. // Return the collapsed string only for cells series header if (self.collapsed && self.collapsedAttributedTextMessage && self.nextCollapsableCellData) { - return super.collapsedAttributedTextMessage; + NSAttributedString *attributedString = super.collapsedAttributedTextMessage; + + self.attributedTextMessage = attributedString; + self.attributedTextMessageWithoutPositioningSpace = attributedString; + + return; } NSMutableAttributedString *currentAttributedTextMsg; + NSMutableAttributedString *currentAttributedTextMsgWithoutVertSpace = [NSMutableAttributedString new]; + NSInteger selectedComponentIndex = self.selectedComponentIndex; NSInteger lastMessageIndex = self.containsLastMessage ? self.mostRecentComponentIndex : NSNotFound; @@ -416,11 +411,15 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat { currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:[RoomBubbleCellData timestampVerticalWhitespace]]; [currentAttributedTextMsg appendAttributedString:componentString]; + + [currentAttributedTextMsgWithoutVertSpace appendAttributedString:componentString]; } else { // Init attributed string with the first text component currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:componentString]; + + [currentAttributedTextMsgWithoutVertSpace appendAttributedString:componentString]; } [self addVerticalWhitespaceToString:currentAttributedTextMsg forEvent:component.event.eventId]; @@ -456,10 +455,45 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat [currentAttributedTextMsg appendAttributedString:componentString]; [self addVerticalWhitespaceToString:currentAttributedTextMsg forEvent:component.event.eventId]; + + [currentAttributedTextMsgWithoutVertSpace appendAttributedString:componentString]; } } - return currentAttributedTextMsg; + // With bubbles the text is truncated with quote messages containing vertical border view + // Add horizontal space to fix the issue + if (self.displayFix & MXKRoomBubbleComponentDisplayFixHtmlBlockquote) + { + [currentAttributedTextMsgWithoutVertSpace appendString:@" "]; + } + + self.attributedTextMessage = currentAttributedTextMsg; + + self.attributedTextMessageWithoutPositioningSpace = currentAttributedTextMsgWithoutVertSpace; +} + +- (void)buildAttributedStringIfNeeded +{ + @synchronized(bubbleComponents) + { + if (self.hasAttributedTextMessage && !attributedTextMessage.length) + { + // Attributed text message depends on the room read receipts which must be retrieved on the main thread to prevent us from race conditions. + // Check here the current thread, this is just a sanity check because the attributed text message + // is requested during the rendering step which takes place on the main thread. + if ([NSThread currentThread] != [NSThread mainThread]) + { + MXLogDebug(@"[RoomBubbleCellData] attributedTextMessage called on wrong thread"); + dispatch_sync(dispatch_get_main_queue(), ^{ + [self buildAttributedString]; + }); + } + else + { + [self buildAttributedString]; + } + } + } } - (NSInteger)firstVisibleComponentIndex diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index da550e643..f4561638f 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -668,6 +668,13 @@ const CGFloat kTypingCellHeight = 24; // Make extra cell layout updates if needed [self updateCellLayoutIfNeeded:bubbleCell withCellData:cellData]; } + + if ([cell conformsToProtocol:@protocol(Themable)]) + { + id cellThemable = (id)cell; + + [cellThemable updateWithTheme:ThemeService.shared.theme]; + } return cell; } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 2881e29cb..ce081145f 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -2813,6 +2813,61 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } } + else if (roomBubbleCellData.getFirstBubbleComponentWithDisplay.event.isEmote) + { + if (bubbleData.isIncoming) + { + if (bubbleData.isPaginationFirstBubble) + { + if (bubbleData.shouldHideSenderName) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitleWithoutSenderName : RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitleWithoutSenderName; + } + else + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncryptedWithPaginationTitle : RoomTimelineCellIdentifierIncomingEmoteWithPaginationTitle; + } + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierIncomingEmoteWithoutSenderInfo; + } + else if (bubbleData.shouldHideSenderName) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncryptedWithoutSenderName : RoomTimelineCellIdentifierIncomingEmoteWithoutSenderName; + } + else + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierIncomingEmoteEncrypted : RoomTimelineCellIdentifierIncomingEmote; + } + } + else + { + if (bubbleData.isPaginationFirstBubble) + { + if (bubbleData.shouldHideSenderName) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitleWithoutSenderName : RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitleWithoutSenderName; + } + else + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithPaginationTitle : RoomTimelineCellIdentifierOutgoingEmoteWithPaginationTitle; + } + } + else if (bubbleData.shouldHideSenderInformation) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderInfo : RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderInfo; + } + else if (bubbleData.shouldHideSenderName) + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncryptedWithoutSenderName : RoomTimelineCellIdentifierOutgoingEmoteWithoutSenderName; + } + else + { + cellIdentifier = showEncryptionBadge ? RoomTimelineCellIdentifierOutgoingEmoteEncrypted : RoomTimelineCellIdentifierOutgoingEmote; + } + } + } else if (bubbleData.isIncoming) { if (bubbleData.isAttachmentWithThumbnail) diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BaseBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BaseBubbleCell.swift index 76abfe8e5..1365d0e18 100644 --- a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BaseBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BaseBubbleCell.swift @@ -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() { diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.swift b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.swift index 7fc615f8b..a0f7fbd22 100644 --- a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.swift +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.swift @@ -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() } - } diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib index 2472391fc..cdaf09edd 100644 --- a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/BubbleCellContentView.xib @@ -4,6 +4,7 @@ + @@ -17,7 +18,7 @@ - + - + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/RoomCellDecorationAlignment.swift b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/RoomCellDecorationAlignment.swift new file mode 100644 index 000000000..4aa7f7d53 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/RoomCellDecorationAlignment.swift @@ -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 +} diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/RoomCellURLPreviewDisplayable.swift b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/RoomCellURLPreviewDisplayable.swift new file mode 100644 index 000000000..dd3a97d7b --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseBubbleCell/RoomCellURLPreviewDisplayable.swift @@ -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() +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.h b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.h index 27efc0c4a..baf53c7ba 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.h +++ b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.h @@ -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. diff --git a/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.m b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.m index fff04b59a..b0a2fba49 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Common/MXKRoomBubbleTableViewCell.m @@ -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 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 diff --git a/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h b/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h index f461504de..fafce9df7 100644 --- a/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h +++ b/Riot/Modules/Room/Views/BubbleCells/RoomTimelineCellIdentifier.h @@ -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 diff --git a/Riot/Modules/Room/Views/BubbleCells/SizableCell/SizableBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/SizableCell/SizableBubbleCell.swift index 9c802f469..99aa2af4a 100644 --- a/Riot/Modules/Room/Views/BubbleCells/SizableCell/SizableBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/SizableCell/SizableBubbleCell.swift @@ -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 diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutConstants.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutConstants.swift new file mode 100644 index 000000000..2a978f82d --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutConstants.swift @@ -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) +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutUpdater.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutUpdater.swift index b4c2a1c80..4cd22c10b 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutUpdater.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomCellLayoutUpdater.swift @@ -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 { diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellDecorator.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellDecorator.swift index ff71c81bf..7feac51fb 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellDecorator.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellDecorator.swift @@ -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 diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m index 51a4813b4..2a9626796 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m @@ -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*)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*)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*)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*)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 }; } diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/BubbleIncomingRoomCellProtocol.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/BubbleIncomingRoomCellProtocol.swift new file mode 100644 index 000000000..32597da8e --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/BubbleIncomingRoomCellProtocol.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/BubbleOutgoingRoomCellProtocol.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/BubbleOutgoingRoomCellProtocol.swift new file mode 100644 index 000000000..2c548f80b --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/BubbleOutgoingRoomCellProtocol.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/BubbleRoomCellProtocol.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/BubbleRoomCellProtocol.swift new file mode 100644 index 000000000..8567af631 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/BubbleRoomCellProtocol.swift @@ -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() +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailBaseBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailBaseBubbleCell.swift index 65183f299..32dfe9612 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailBaseBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailBaseBubbleCell.swift @@ -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) } diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.swift index 14dd3c7fa..ce0471dad 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Common/FileWithoutThumbnailCellContentView.swift @@ -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 } } - diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingBubbleCell.swift index bdc06c1ec..bcc52efa8 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Incoming/FileWithoutThumbnailIncomingBubbleCell.swift @@ -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) { diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.swift index 67cb6ac3a..4f2283770 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/FileWithoutThumbnail/Outgoing/FileWithoutThumbnailOutoingWithoutSenderInfoBubbleCell.swift @@ -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) { diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingBubbleCell.swift index b0818f550..4a7fb08b5 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Incoming/LocationIncomingBubbleCell.swift @@ -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() } } diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithoutSenderInfoBubbleCell.swift index 8c46e21de..ef2061224 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithoutSenderInfoBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Location/Outgoing/LocationOutgoingWithoutSenderInfoBubbleCell.swift @@ -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() } } diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/MXKRoomBubbleTableViewCell+BubbleStyle.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/MXKRoomBubbleTableViewCell+BubbleStyle.swift similarity index 100% rename from Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/MXKRoomBubbleTableViewCell+BubbleStyle.swift rename to Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/MXKRoomBubbleTableViewCell+BubbleStyle.swift diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Poll/Incoming/PollIncomingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Poll/Incoming/PollIncomingBubbleCell.swift index 61d11e642..c71810ef5 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Poll/Incoming/PollIncomingBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Poll/Incoming/PollIncomingBubbleCell.swift @@ -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) { diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Poll/Outgoing/PollOutgoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Poll/Outgoing/PollOutgoingWithoutSenderInfoBubbleCell.swift index 9d5fba4d7..b16150d6c 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Poll/Outgoing/PollOutgoingWithoutSenderInfoBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/Poll/Outgoing/PollOutgoingWithoutSenderInfoBubbleCell.swift @@ -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) { diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Common/TextMessageBaseBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Common/TextMessageBaseBubbleCell.swift new file mode 100644 index 000000000..f64e0d873 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Common/TextMessageBaseBubbleCell.swift @@ -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() + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Common/TextMessageBubbleCellContentView.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Common/TextMessageBubbleCellContentView.swift new file mode 100644 index 000000000..5a57b62c2 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Common/TextMessageBubbleCellContentView.swift @@ -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() + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Common/TextMessageBubbleCellContentView.xib b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Common/TextMessageBubbleCellContentView.xib new file mode 100644 index 000000000..90fba0291 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Common/TextMessageBubbleCellContentView.xib @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingBubbleCell.swift new file mode 100644 index 000000000..7d133e2f2 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingBubbleCell.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..7b47ab409 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithPaginationTitleBubbleCell.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.swift new file mode 100644 index 000000000..8f2c5fcc6 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithPaginationTitleWithoutSenderNameBubbleCell.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..abeed3435 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithoutSenderInfoBubbleCell.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithoutSenderNameBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithoutSenderNameBubbleCell.swift new file mode 100644 index 000000000..15bbacf0c --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Incoming/TextMessageIncomingWithoutSenderNameBubbleCell.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Outgoing/TextMessageOutgoingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Outgoing/TextMessageOutgoingWithPaginationTitleBubbleCell.swift new file mode 100644 index 000000000..1ffb87c80 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Outgoing/TextMessageOutgoingWithPaginationTitleBubbleCell.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Outgoing/TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Outgoing/TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.swift new file mode 100644 index 000000000..dd9a345e3 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Outgoing/TextMessageOutgoingWithPaginationTitleWithoutSenderNameBubbleCell.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Outgoing/TextMessageOutgoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Outgoing/TextMessageOutgoingWithoutSenderInfoBubbleCell.swift new file mode 100644 index 000000000..992d83ad9 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/TextMessage/Outgoing/TextMessageOutgoingWithoutSenderInfoBubbleCell.swift @@ -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 + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingBubbleCell.swift index f86756e9e..febaf8720 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingBubbleCell.swift @@ -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) { diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithPaginationTitleBubbleCell.swift index 9e8e6513f..bd6f1fb38 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithPaginationTitleBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Incoming/VoiceMessageIncomingWithPaginationTitleBubbleCell.swift @@ -17,6 +17,7 @@ import Foundation class VoiceMessageIncomingWithPaginationTitleBubbleCell: VoiceMessageIncomingBubbleCell { + override func setupViews() { super.setupViews() diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithoutSenderInfoBubbleCell.swift index 1b18d861c..5b0bffd7e 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithoutSenderInfoBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/Cells/VoiceMessage/Outgoing/VoiceMessageOutgoingWithoutSenderInfoBubbleCell.swift @@ -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) { diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/RoomMessageBubbleBackgroundView.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/RoomMessageBubbleBackgroundView.swift index 496cd0a4a..da79211b2 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/RoomMessageBubbleBackgroundView.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Bubble/RoomMessageBubbleBackgroundView.swift @@ -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 } diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.h b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.h index adedaa9d7..73f0d8f8b 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.h +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.h @@ -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*)incomingTextMessageCellsMapping; + - (NSDictionary*)outgoingTextMessageCellsMapping; +- (NSDictionary*)incomingEmoteCellsMapping; + +- (NSDictionary*)outgoingEmoteCellsMapping; + - (NSDictionary*)outgoingAttachmentCellsMapping; - (NSDictionary*)incomingAttachmentWithoutThumbnailCellsMapping; diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m index 6b5339bfa..0a68e6609 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineCellProvider.m @@ -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*)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*)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*)incomingAttachmentCellsMapping { return @{ diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineStyle.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineStyle.swift index f3fba9c4e..e72ce51fb 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineStyle.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/Plain/PlainRoomTimelineStyle.swift @@ -32,8 +32,8 @@ class PlainRoomTimelineStyle: RoomTimelineStyle { let cellProvider: RoomTimelineCellProvider - let cellDecorator: RoomTimelineCellDecorator - + let cellDecorator: RoomTimelineCellDecorator + // MARK: - Setup init(theme: Theme) { diff --git a/Riot/Modules/Room/Views/BubbleCells/Styles/RoomCellLayoutUpdating.swift b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomCellLayoutUpdating.swift index 78f5330f1..2c8b85e83 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Styles/RoomCellLayoutUpdating.swift +++ b/Riot/Modules/Room/Views/BubbleCells/Styles/RoomCellLayoutUpdating.swift @@ -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 } diff --git a/Riot/Modules/Room/Views/URLPreviews/URLPreviewViewSizer.swift b/Riot/Modules/Room/Views/URLPreviews/URLPreviewViewSizer.swift new file mode 100644 index 000000000..84e730d8f --- /dev/null +++ b/Riot/Modules/Room/Views/URLPreviews/URLPreviewViewSizer.swift @@ -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 + } +} diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index 165321909..4b517a310 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -67,3 +67,4 @@ #import "MXKRoomDataSourceManager.h" #import "MXRoom+Sync.h" #import "UIAlertController+MatrixKit.h" +#import "MXKMessageTextView.h"