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

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