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